November 25, 2014

Tridion: Image handling in Responsive design.

In responsive websites, we usually keep different breakpoints for different devices & different breakpoints require different image sizes. There are different ways to use images in responsive layouts.
  • Same image on all Devices
  • Manual upload of device specific Image
  • Generate images on Fly.
  • Tridion CID
Same image on all devices: This is simplest of all as point of development, let user upload single image and same image is published on delivery. But there are few points which needs to be taken care for this.

UI Designer has to maintain the aspect ratio in his designs across Desktop, tablet, mobile or whatever breakpoints you have. e.g

Desktop:  200  : 100
Tablet:      100 :  50
Mobile:     50:    25

so if you see the aspect ration has been maintained at 2:1.  so Maintaining aspect-ratio is very important as it can go haywire if the objects start scaling based on the screen dimension. To maintain aspect ratio of the objects, select Auto for either width or height of the object in the Primary view

Tridion side there is no special change is required. normal image publishing.

Cons: Mobile has to download the same big image, which could be bandwidth issue, also in sites where lots of images are used, this approach on tablet/mobile would not be considered best

Manual upload of device specific Image:  There are different reasons where device specific images are required:
  • Designers are not totally versed with it. so ratios are not maintained  across devices
  • Performance and bandwidth issue
  • For mobile instead of whole image only part of image is required for more cleared and focused view.
these images could be manually-generated or from some DAM System.

Tridion Implementation:

Create a category with Keywords having device information. 
Category : Device Type
Keywords - large, medium, small

Create a embedded schema "Responsive Multimedia" as shown below
ScreenSize refer above category.

For this we created a generic helper class in razor, it generate the Data-Interchange tags in images. We are using Foundation Framework for handle responsiveness of site  here. 

<img data-interchange="[@Publication.MultimediaUrl/@mobile, (default)], [@Publication.MultimediaUrl/@tablet, (medium)], [@desktop, (large)]"></img>

Now you can read the component and read the values of uploaded images and generate the image tag with data-interchange as shown above. 

Generate images on Fly  Other approach is to upload a single image and generate the rest of the device specific images on Fly using custom code. But design should maintain aspect ratio of the images. 

Tridion code: we have written a c# tbb to handle it, here is the code for it. you can customize it 

[TcmTemplateTitle("AddAssets")]
    public class AddAssets : TemplateBase
    {
        
        public override void Transform(Engine engine, Package package)
        {
            Initialize(engine, package);
            var items = m_Package.GetEntries();
            var dynamicbinaries = new Dictionary<string, string[]>();
            foreach (var i in items)
            {
                Logger.Info(i.Key + ":" + i.Value);

                if (i.Key != "GenerateDynamic") continue;
                Logger.Info("Generate Dynamic parsing:");

                var arr = i.Value.GetAsString();
                var binaries = arr.Split(',');

                dynamicbinaries.Add(binaries[0], binaries);
           }

            var allowedContentTypes = new Dictionary<ContentType, ImageFormat>()
                                   {
                                       {ContentType.Bmp, ImageFormat.Bmp},
                                       {ContentType.Jpeg, ImageFormat.Jpeg},
                                       {ContentType.Png, ImageFormat.Png},
                                       {ContentType.Gif, ImageFormat.Gif}
                                   };
            foreach (var contentType in allowedContentTypes.Keys)
            {
                foreach (var item in package.GetAllByType(contentType))
                {
                    if (dynamicbinaries.ContainsKey(item.Properties[Item.ItemPropertyTcmUri]))
                    {
                        InsertRelatedBinaries(item, allowedContentTypes[contentType], dynamicbinaries[item.Properties[Item.ItemPropertyTcmUri]]);
                    }
                }

            }
        }

        private void GenerateDeviceSpecificBinaries(Item item, ImageFormat imageFormat, string[] binary)
        {

            var mmc = new Component(new TcmUri(item.Properties[Item.ItemPropertyTcmUri]), m_Engine.GetSession());

            var binaryContent = mmc.BinaryContent;
            var binaryBytes = binaryContent.GetByteArray();
            using (var bitmap = new Bitmap(new MemoryStream(binaryBytes)))
            {
                var tabPercentage = Int32.Parse(m_Package.GetValue("TabPercentage"));
                var mobilePercentage = Int32.Parse(m_Package.GetValue("MobilePercentage"));
                var resizedBitmap = ResizeBitmap(bitmap, tabPercentage);
                AddBitmapAsBinary(item, imageFormat, mmc, binaryContent, resizedBitmap, "2", binary[2]);//Tablet Images

                resizedBitmap = ResizeBitmap(bitmap, mobilePercentage);

                AddBitmapAsBinary(item, imageFormat, mmc, binaryContent, resizedBitmap, "3", binary[1]); // Mobile Images
                resizedBitmap.Dispose();
            }
        }

        private void AddBitmapAsBinary(Item item, ImageFormat format, Component mmc, BinaryContent binaryContent, Bitmap resizedBitmap, string variant, string filename)
        {
            var resizedStream = new MemoryStream();
            resizedBitmap.Save(resizedStream, format);
            var binary = this.m_Engine.PublishingContext.RenderedItem.AddBinary(
                resizedStream, filename, variant, mmc,
                binaryContent.MultimediaType.MimeType);
            resizedStream.Dispose();
        }

        private Bitmap ResizeBitmap(Bitmap sourceBitmap, int per)
        {
          
                var sourceWidth = sourceBitmap.Width;
                var sourceHeight = sourceBitmap.Height;

                float nPercentW = per;
                float nPercentH = per;

                var  destWidth = (int)(sourceWidth * (nPercentW / 100));
                var  destHeight = (int)(sourceHeight * (nPercentH / 100));
           

            var resultBitmap = new Bitmap(destWidth, destHeight);
            var graphicsd = Graphics.FromImage(resultBitmap);
            graphicsd.InterpolationMode = InterpolationMode.HighQualityBicubic;

            graphicsd.DrawImage(sourceBitmap, 0, 0, destWidth, destHeight);
            graphicsd.Dispose();

            return resultBitmap;
        }

    }
}

Tridion CID  Contextual Image delivery  is a product from SDL which helps in transforming the images in various ways such as
  • Resize with Cropping
  • Resize without Cropping
  • Trim image
  • Change Image format
Please read this very good article for further explanation of this module. What Does Contextual Image Delivery Mean for SDL Tridion Images? 

September 14, 2014

Single Sign On Integration with SDL Tridion and CA Siteminder


In today’s world most of the products, be it a financial product like credit  card, media or electronics item,  do not come from just one vendor but from collaboration between different partner companies. All partners play  a unique role in marketing of such product and need access to related data.

Practically, it is not possible to provide access to all the partners to the Active Directory or LDAP and here comes SSO for the rescue which enables all the partners to use a common policy server to authenticate themselves.
SDL Tridion can be easily integrated with SSO servers like CA Siteminder and IBM Tivoli.

Following figures describes SSO flow till authentication.
Fig: Flow Chart of SSO Request

Installation & Configuration: Setting up SSO requires lots of configurationThe first step for configuring SSO for Tridion is installing Site Minder agent on CM server which intercepts all the request coming to Tridion CMS and authorize the user
The web agent interacts with Policy Server in order to authorize user based on the credential provided by user. Once authenticated, unique user id is set in header variable of each request which is then used by Tridion to identify the user if SSO is configured.
Authorization can be done at Tridion CMS security level where based on business requirement different access and permissions can be given to users on CMS items like publication etc.

Installing CA Siteminder agent on CMS server 
  1. Execute ca-wa-12.5-cr02-win64.exe.
  2. Follow the instructions according to installation wizard
  3. In the Host Registration dialog box, select ‘Yes’ to register a host and click Next.
  4. Complete the following fields in the Admin Registration dialog box, then click Next- Provide the required admin credentials
  5. Admin User Name
  6. Admin Password
  7. Confirm Admin Password 
  8. Enabled Shared Secret Rollover – Unchecked
  9. In the Trusted Host Name and Configuration Object dialog box, enter webserver name as trusted host name. 
  10. In the Policy Server IP Address dialog box enter the IP or  policy server VIP:
  11. Choose FIPS Compatibility Mode (Default) and click Next.
  12. Accept the default location of the host configuration file, SmHost.conf or click Choose to select a different location. Click Next.
  13. In the Select Web server(s) dialog box, select the option for the Microsoft IIS 7.5 and click Next.
  14. Select the virtual sites(SDL Tridion 2011) that need to be configured with this web agent and click Next.
  15. Enter the ACO name <webserver>_agent_config and click Next:
  16. In the WebAgent Enable Option, check the YES box and click Next.
  17. In the Web Server Configuration Summary dialog box. Confirm that the configuration settings are correct, then click Install.
  18. Click done when the installation is complete. The system restarts
Permission to user to modify encrypted configuration sections
  1. Open command prompt and go to where PSTools is located
  2. Run:    psexec -i -s cmd.exe
  3. This should open another new command prompt window
  4. In the new command prompt window, navigate to where aspnet_regiis is (should be in c:\Windows\Microsoft.Net\Framework64\v4.0.30319  
  5. Run:   aspnet_regiis -pa "TridionRsaKeyContainer" "Domain\UserName"
  6. Follow the same steps for user NT AUTHORITY\IUSR
Configuration Console:

Fig: SSO Configuration in CMS
Set header name which will be used to retrieve user name after authentication.

Some Useful links:

  • http://docs.sdl.com/LiveContent/content/en-US/SDL%20Tridion%202011%20SP1%20full%20documentation-v1/GUID-407B4F89-B8D0-4799-A16B-147D89B044F6
  • https://support.ca.com/cadocs/0/CA%20SiteMinder%20r6%200%20SP6-ENU/Bookshelf_Files/PDF/siteminder_wa_config_enu.pdf
Issues and Trouble shooting

In my previous experience with SSO the unique user GUID that is set by Siteminder in the header of response was not getting persisted and hence SSO was not working fine with Tridion. To resolve this issue one can try putting a custom HTTP Module in request pipeline and check if the GUID is getting persisted, and in case it is not you will have to explicitly set the header variable.
NOTE: One important point to take care of (especially in case you are making entries manually in web.config)  make sure that HTTP module entry in web.config for your custom http module comes after the entry of CA Siteminder.

<add name="CASiteMinderWebagentModule" preCondition="integratedMode,bitness64" />
<add name="customhttpmodule" type="<>, Version=1.1.0.0, Culture=neutral, PublicKeyToken=<>" />
Some point worth noting is that we were not able to run the Tridion Client applications like Template Builder and Content porting using SSO and hence it was explicitly set on windows authentication.

August 12, 2014

Paste From Word Plugin:UI Extension

In Tridion 2011 the paste-special feature doesn't work perfectly. The ask was to copy/paste articles directly from the Microsoft Word, as Tridion OOB feature was not working so client's whole process was impacted. 

so we come up with this plugin  Paste Special Cross Browser Extension  which helped them to copy paste from word without those problematic word tags. it was written on top of  excellent CKeditor (without reinventing :) )
Plugin Code Structure:


  • CKeditor
  • Client
  • Config

With this plugin it is possible to paste content from Microsoft Word and maintain original content formatting and also paste as a plain text. The plugin adds the Paste from Word and Paste as Text button which makes it possible to paste clipboard data. This Paste Special Cross Browser Extension removes the existing Paste Special Functionality from the CME/Experience Manager and replaces it with a cross browser solution.

CKeditor: Contains CKeditor’s java script, CSS and HTML files (PasteAsText and PasteFromWord)  

Client: Contains PasteSpecialFormatArea javascript file that is the main javascript file where we define the command for the UI extension buttons.

For e.g.

Extensions.PasteAsText.prototype.isAvailable = function PasteAsText$isAvailable(target){              
    return true;
};
Extensions.PasteAsText.prototype.isEnabled = function PasteAsText$isEnabled(target){
    return true;
};

Extensions.PasteAsText.prototype._execute = function
PasteAsText$_execute(target)
{
popUpData="";
var host = window.location.hostname;
var newwindow = window.open("http://"+host+"/WebUI/Editors/PasteSpecialFormatArea/ckeditor/PasteAsText.html",'FormatArea','height=356,width=750');
     
var unloadFunc = function () { setEditorData(); };
if (newwindow) { // null if a pop-up blocker does not create the window
if (newwindow.addEventListener) {
newwindow.addEventListener('unload', unloadFunc, false);
} else {
newwindow.attachEvent('onunload', unloadFunc);
}
}

function setEditorData()
{
if(popUpData != "")
{
target.editor.applyHTML(popUpData);
}
}
};

function setPopupText(data)
{
popUpData = data;
}


Config: Contains the config file that has the configuration for the UI extension button and also all the dependency files path.

Deployment
  
1.     Copy the plugin folder “BCGFormatArea” inside the Editor folder in the Tridion installation path “InstallationPath\Tridion\web\WebUI\Editors”.

2.     Now in IIS create a virtual directory with the name “BCGFormatArea” inside Editor Directory as shown in the below screen shot.


3.     Now open the “System.config” file in the path “C:\Program Files (x86)\Tridion\web\WebUI\WebRoot\Configuration” and add a new editor configuration setting inside the “<editors default="CME">” element tag 


Here is the configuration setting:

<editor name="BCGFormatArea" xmlns="http://www.sdltridion.com/2009/GUI/Configuration">
<installpath> InstallationPath\Tridion\web\WebUI\Editors\BCGFormatArea\</installpath>
<configuration>config\BCGFormatArea.config</configuration>
<vdir>BCGFormatArea</vdir>
</editor>
 Note: Make sure the virtual directory name in the editor setting <vdir> is matched with the created one.
4.     Now make the IIS reset and open the CMS you will see two new “Paste As Text” and “Paste From Word” buttons added in the component view as shown in the below screenshot.



5.     Click the button to open the popup.

How it works:

Most intersting methods to notice  are _execute, setEditorData, setPopupText.

_execute: It first trigger the popup "PasteAsText.html". which in turns intialize the ckEditor. 

so now when user enter the content in the text box and press ok, following code gets executed:

 function GetContents() {
                // Get the editor instance that you want to interact with.
                var editor = CKEDITOR.instances.editor1;                
window.opener.SetPopupText(editor.getData());
closeWin();
            }

if you notice it just set the text via calling formatArea's SetPopupText method. 
and closeWin(), which again triggers the unloadFunc method of FormatArea. which sets the data to the CME textbox.

July 24, 2014

Tridion CMIS connector for aDAM - External content Library

We had a requirement to connect aDam with Tridion 2013. I started the POC after reading docs, posts       ( from Eric & Bart).

External Content Library aka ECL was introduced in Tridion 2013 to connect external content/multimedia sources with Tridion Interface e.g youtube, vimeo, flicker, facebook, aDam, Media Manager. Its a great feature.
When content starts displaying in the CME, it looks like native components.

Prerequisite

Tridion: 
  • Assembly: Tridion.ExternalContentLibrary.V2
  • Admin Privilege
Terminologies:

Mount Point: Tridion folder which you mount to access external content. all external content is listed under this folder. 

ECL Stub Schema: When a provider is mounted in Tridion, it generates a Stub schema , and all stub components (from external sources) displayed in this mountPoint are type of this stub schema.(because you need a schema to represent a component in Tridion)

Interface: Interfaces in following figure are used in development.








aDam
  • aDam Studio installed
  • Rest Service to access its content - in aDam 5.0 there was no open API/oData etc were available. In current 5.0 version there is no official API comes with aDam installer. but aDam guys provided this Sample Rest Service which can be extended, as i discussed with them, API will be released in next versions.
Few Terminologies
  • Classification: its basically taxonomy of aDam, and content in CME would also categorized by classification. 
  • Record: Assets are called Record in aDam. 
Development.  
Lets move to development side, i will keep code short as possible with a view to brevity and clarity.

Here i am declaring that my connector will show two types of item in the interface, first one is a folder, which is collection of items, in aDam case its Classification. Rest methods are standard implementations.

AdamEclProvider : IContentLibrary 
it initialize the AddIn, this is the class which is configured in ExternalContentLibrary.xml.

[AddIn(
    "AdamProvider",
    Description = "Adam External Content Library Provider",
    Publisher = "Raj Future Technologies",
    Version = "1.0.0.0")]
    public class AdamEclProvider : IContentLibrary

    {
public IList<IDisplayType> DisplayTypes
        {
            get
            {
                return new[]
                    {
                        HostServices.CreateDisplayType( "clf", "Classification", EclItemTypes.Folder),
                        HostServices.CreateDisplayType( "ast", "Assets", EclItemTypes.File),
                    };
            }

        }

     }//Class end


AdamContext : IContentLibraryContext:

This call has code for connecting to external sources and retrieval of the content. 

internal class AdamContext : IContentLibraryContext

    {

/*this method is triggered when user click on MountPoint, its fetches the classifications from the Adam and show there
now when that particular folder is clicked, sub classicifations and assest are displayed. */

public IFolderContent GetFolderContent(IEclUri parentFolderUri, int pageIndex, EclItemTypes itemTypes)
        {
                       
            List<IContentLibraryListItem> result = new List<IContentLibraryListItem>();
            if (parentFolderUri.ItemType == EclItemTypes.MountPoint && itemTypes.HasFlag(EclItemTypes.Folder))
            {
//AdamHelper is kind of facade to get the information from service
                            var classifications = AdamHelper.GetClassifications();
                foreach (var classification in classifications)
                {
                    result.Add(new AdamListClassification(parentFolderUri.PublicationId, classification.id.ToString(), classification.Name));                
                
                }
                
           canSearch = true;
            }
            else if (parentFolderUri.ItemType == EclItemTypes.Folder && parentFolderUri.SubType == "clf" && itemTypes.HasFlag(EclItemTypes.File))
            {
               // throw new Exception("iam here");
                var records = AdamHelper.GetRecords(parentFolderUri.ItemId);
                
                result.AddRange()//
            }
                return AdamEclProvider.HostServices.CreateFolderContent(parentFolderUri, result, false, canSearch);


        }
//fetch particular item. 
public IContentLibraryItem GetItem(IEclUri eclUri)
        {
                       if (eclUri.ItemType == EclItemTypes.File && eclUri.SubType == "ast")
            {
                var record = AdamHelper.GetRecord(eclUri.ItemId);
                return new AdamAsset(eclUri.PublicationId, record, eclUri.ItemId);
            }
            throw new NotSupportedException();

        }
//fetch item list
 public IList<IContentLibraryItem> GetItems(IList<IEclUri> eclUris)
        {
///code goes here to call the aDam helper
}

} //Class end

//This class represent one type of asset, so if you have image, video you have to implement it for  each of the asset.

public class AdamAsset : IContentLibraryMultimediaItem
    {
public AdamAsset(int publicationId, RecordData record, string parentId)
        {
            _parentId = parentId;
            _record = record;
            _id = AdamEclProvider.HostServices.CreateEclUri(publicationId, AdamEclProvider.MountPointId, record.id.ToString(), "ast", EclItemTypes.File);

        }

public string DisplayTypeId
        {
            get { return "ast"; }

        }

}
}//Class end

//Represent a classification

public class AdamClassification : IContentLibraryListItem, IContentLibraryItem 

    {

    }

Note: You have to set WebReady property to "Yes" from Files tab to view the asset in Tridion.

with above code you will able to display the asset in CMS and will able to include it in the native Tridion components, for publishing those there would be additional template code. which i did not do as scope of my POC and different direction of the project.


Official Adam Connector: Official aDam connector is also available from SDL which is licensed Separately. You can check with SDL for it.

Let me know if you have any questions or need any help, feel free to comment below.