Tuesday, December 13, 2016

Sitecore SSO Part 2

This is the second article of the SSO blog posts. You can find the first one here

Sharing Authentication cookie across 2 Sitecore instances:

As I mentioned in my first post, we now want to share the authentication cookie across 2 different sitecore instances (6.5 and 8.1)
Sitecore 8.1 and 6.5 behave differently when dealing with authentication. 
Both use 2 cookies for authentication 
  • .ASPXAUTH (name specified in web.config) 
  • sitecore_userticket
Every version deals with authentication cookie in a different way
  • Sitecore 6.5: When user logs in, both cookies are created. It only uses the .ASPXAUTH cookie for authentication. If you delete or modify the content of sitecore_userticket cookie using a browser plugin, it will not be regenerated. Sitecore is not affected if you modify the content of sitecore_userticket  cookie
  • Sitecore 8.1: When user logs in, both cookies are created. It only uses sitecore_userticket cookie for authentication. If it is missing or modified, then you are considered logged out even if you have the other cookie. sitecore_userticket content is different than Sitecore 6.5 cookie. So sharing it won't make logged in users in Sitecore 6.5 logged in Sitecore 8.1.  .ASPXAUTH cookie can be deleted safely and it will be recreated with every request. You will get an ASP.Net Exception though, if you modify the content of the .ASPXAUTH cookie.
To summarize
  • sitecore_userticket: Different across versions. Important only for Sitecore 8.1 
  • .ASPXAUTH: Same across versions. Important only for Sitecore 6.5
The solution I implmented to solve this was:
  1. Share .ASPXAUTH cookie across instances as both have the same top domain
  2. Recreate the sitecore_userticket in Sitecore 8.1 if .ASPXAuth cookie exists.

Sharing .ASPXAUTH cookie:

You will have to share the same machine key specified in the web.config in BOTH sites:
<machineKey validationKey="xxxxxxxxxxxxxxx"  decryptionKey="xxxxxxxxxx" validation="SHA1" decryption="AES" />
Make the Authentication cookie domain the top domain used by BOTH instances 

<authentication mode="None">
<forms name=".ASPXAUTH" cookieless="UseCookies" domain="[Your top domain here]"/>
</authentication>

Recreating sitecore_userticket  Cookie:

I modified a solution is specified here. The solution specified in the link is good to share the ticket between 2 instances of the same version. In this case sharing the cookie is enough between the domains. But as the content of the cookie is not the same across the old and new versions of sitecore, we have to regenerate it again by adding the code below in the Global.asax file in the NEW sitecore instance. (This was a question I asked on stack exchange here)

protected void Application_EndRequest(object sender, EventArgs e)
{
    var authCookie = HttpContext.Current.Response.Cookies["sitecore_userticket"];

    if (!Request.IsAuthenticated || authCookie == null)
    {
        // when checking response cookies, cookie is created if not exists, so delete now
        HttpContext.Current.Response.Cookies.Remove("sitecore_userticket");
        return;
    }

    //we don't need to make it cross domain as it will be different for every instance 
    //due to version differences.
    //create the ticket cookie. Every Sitecore instance will generate it the way it expects.
    authCookie.Value = TicketManager.CreateTicket(HttpContext.Current.User.Identity.Name, string.Empty);
}

Conclusion:

Now you will have the sign on shared across the 2 versions. This solution may still have some minor issues but all the major challenges are fixed.

Sitecore SSO Part 1

This blog post is about SSO across 2 different instances of Sitecore while sharing the same top domain i.e. cookies can be shared across the 2 instances and both instances can access the same DB

I was recently tasked with implementing SSO across two different sitecore sites that are sharing the same top domain. Every instance has different version (8.1 and 6.5) and every instance has its own (core/master/web) databases.
To implement SSO you will have to:
  1. Share authentication/authorization/profiles info
  2. Share authentication cookie across sites
I am splitting them to 2 posts for the sake of clarity

Sharing Authentication/Authorization/Profile info:

Sharing Databases:

Sitecore uses the normal ASP.Net tables schema and builds upon the membership/roles/profiles provider. These providers use core database by default as specified in the Web.Config. To be able to share this existing info, we will need to make changes to the configuration of the new site. So first we need to add a connection in App_Config\ConnectionStrings.config file in the new instance to point to the existing core database of the old site. Let's call this connection oldcore
<add name="oldcore" connectionString="[old site core database connection string]" />
Then we go to the web.config in the new site and point the profile/membership/roles tags to the new oldcore db. Changes are highlighted in yellow (I removed irrelevant configuration for clarity)

<membership defaultProvider="sitecore" hashAlgorithmType="SHA1">
      <providers>
        <clear />
      .....
        <add name="sql" type="System.Web.Security.SqlMembershipProvider" connectionStringName="oldcore".... />
     .........
      </providers>
    </membership>
    <roleManager defaultProvider="sitecore" enabled="true">
      <providers>
        <clear />
        ........
        <add name="sql" type="System.Web.Security.SqlRoleProvider" connectionStringName="oldcore" applicationName="sitecore" />
        .......
      </providers>
    </roleManager>
   <profile defaultProvider="sql" enabled="true" inherits="Sitecore.Security.UserProfile, Sitecore.Kernel">
      <providers>
        <clear />
        <add name="sql" type="System.Web.Profile.SqlProfileProvider" connectionStringName="oldcore" applicationName="sitecore" />
        .....
      </providers>
      .........
    </profile>

This way we only shared the needed info not the whole core database which wouldn't have worked due to version differences

Sharing Domains and User Profiles:

Domains and User profiles in the add user dialog (sitecore 6.5). Client specific info obscured

Domains info are saved in the file App_config\security\domains.config. So this will need to be copied over to the new instance. 
Custom user profiles templates are in the core db in the folder: /sitecore/templates/System/Security
The user profile items themselves are in the core db in the folder /sitecore/system/Settings/Security/Profiles
The easiest way to copy the templates and the items over to the new instance is to create a sitecore package and install them in the new instance.

Profiles Versions incompatibility: 

Due different versions Profiles modified in Sitecore 8.1 cannot be loaded in Sitecore 6.5. While profiles created in Sitecore 6.5 are loaded in Sitecore 8.1
The solution for this was hacky. I had to decompile the new Sitecore.Security.UserProfile from Sitecore 8.1 Sitecore.Kernel.dll and then create in a project in the old sitecore version and then inherit from UserPorfile class in the Sitecore 6.5 Sitecore.Kernel.dll. Then fix all compilation error and remove all the properties the already exist in the old class

namespace UserProfile
{
public class UserProfile : Sitecore.Security.UserProfile
{
         .......
        }
}

and then you will need to modify the web.config in the old Sitecore to be 

<profile defaultProvider="sql" enabled="true" inherits="UserProfile.UserProfile, UserProfile">
I removed some redundant code but didn't go and spend much time to remove as much as possible of redundant code (You can find it here). You can optimize this part. But currently this code allowed Sitecore 6.5 to load profiles modified in Sitecore 8.1
Also there is another problem of caching in each instance where the user profile will load from cache so you will need to reload the profile every time you deal with it.

Conclusion:

Now both sitecore instances share the same login and now we all we need is to share the authentication cookie.

Wednesday, October 12, 2016

Sitecore SXA first impressions

First Impressions

When I started working with Sitecore CMS, one of the most confusing parts was the lack pre-built components for simple stuff like adding page content, Facebook comments. This was due to my background in DotNetNuke what comes with lots of first party modules to be used out of the box, I assumed this would be the case with Sitecore. It is the case now with the introduction of Sitecore experience accelerator (SXA).

What is Sitecore Experience Accelerator (SXA)?

SXA is a set of re-usable templates and components that allows you to get your website quickly up and running. In order to achieve this goal SXA has these types out of the box:
Part of SXA components
  • Components: varies from content to composite to media components, etc. They all can be dragged and dropped on place holders. Some components can also host other placeholders (They automatically solve the problem of dynamic placeholders that exist in current Sitecore layouts. In existing projects we have a custom solution for this)
  • Partial Designs: Designs that are to be shared across different types of pages like copyright footer or a top menu or search box
  • Page Designs: Designs that will be used across certain page templates. These designs have either page components or partial designs. There is a mapping between which template should use which page design in the ribbon of the experience editor. Of course you can add components directly on the page but page designs allows you to maintain consistency among all pages with the same template
  • Rendering Variants: Allows the admin to create rendering variants of components that include both html and CSS changes.These variants can be selected in the experience editor in the properties of the component.
  • Creative Exchange Export/Import: Allows the admin to export the whole design to send it to web designers to modify the look and feel of HTML+CSS and the re-imported again to the website.
From what I saw, SXA looks very promising with simple sites can be totally be done with little to no custom development. But from my experience, you will never know till you use SXA in a real life project.

Important links:

SXA at the time of writing is version 1.1 and requires either Sitecore 8.1 or 8.2. 

License Considerations:

SXA requires extra license key "Sitecore.SXA". This key is not in Sitecore license files generated before August,25th 2016. To be able to use SXA, you will need to get a sitecore license file that is generated after this date. Also this means extra money to be paid by the companies purchasing Sitecore.

Final Thoughts and Questions:

  1. This is a really promising technology. It can reduce site development time and comes with lots of useful components out of the box.
  2. This is a new technology that involves some risk and learning curve to master.
  3. I was not able to make the export/import work as expected. I think I missed one of the steps.
  4. Custom development will be needed to meet your requirements that are not fulfilled by the existing SXA components but it is not mentioned in the webinar other than that we should inherit controllers from certain class. And it is not that clear from the documentation.
  5. I don't think we can limit the content of the toolbar depending on the template but I am not sure. This feature will be useful as we may need to limit content authors from adding certain components in certain templates

Saturday, January 30, 2010

Modified DNN with RTL support release

As promised here the release of DNN version 5.02.01 modified to support RTL languages as a proof of concept of my suggestions. Here is a summary of modifications that are explained more in Part 1 & Part 2 for proposal to support RTL in DNN (without messing up the LTR layout). You can download this release from here.

Release Notes

1. API Changes
  1. Added read only property IsRightToLeft in Localization class to determine if the current culture is right to left.
  2. Added an extension method GetLabelModeString to LabelMode enumeration. When LabelMode is left and text direction is rtl the method returns right instead of left and when right the method returns left. If the text direction is left it has no effect when direction. This method should be used instead of ToString for supporting both LTR and RTL directions. This method returns string in lower case.
  3. Changed the PropertyGrid class to use the method described in point 2 above instead of using ToString method.
  4. Changed the behavior of AddStyleSheet in CDefault class to check the existance of RTL files and inculde them along side the normal CSS file in case of RTL culture. For example if AddStyleSheet is called to add file x.css, it will check for existance of x.rtl.css and include it if exists and RTL culture.
  5. Added dnn.direction() method in dnn.js. It returns rtl in case direction is RTL.
2. Changes in WebSite
  1. Added RTL CSS files according to point 4 above.
  2. Fixed calculations for Menu absolute positioning in dnn.controls.dnnmenu.js
  3. Fixed layout of Search controls in Search.js file. (background-image is always aligned left in CSS. In case of RTL direction, it needs to aligned right using background-position.
  4. Numerous fixes in the website to support both RTL and LTR layout. You can find them by searching through the solution for Modified By Michael Fayez in the code.
  5. Added the changes suggested here for adding rtl/ltr plugin to FCK editor (with slight modifications).
  6. I didn’t use CSS flipping as it didn’t work on Opera. I used flipped images from the original ones when needed.
3. Notes
  1. The attached file is only a proof of concept and is not supported by any means.
  2. I don’t claim any copyright for these modifications. (Thanking me would be nice). 
  3. The RTL modification of default skin could be better by flipping images of borders to have the shadows mirrored. Though the current solution passes.
  4. Only default skin and default menu are fixed.
  5. In some places like breadcrumb, the layout looks not mirrored in RTL mode this is because of flow direction of English in an RTL language the layout would be mirrored automatically. So this is not an issue at all.
  6. I didn't make any modifications to the core modules of DNN. They need to be checked one by one to fix them. I know that there are problems in forum module in RTL. The repository module layout engine uses a single template from html file per module which may not be adequate in case of supporting both direction LTR and RTL and so on.
  7. I don’t guarantee nor know if the DNN team would embrace these changes or supporting RTL layout. (Currently they fix RTL issues one by one without resolving to the root cause of the problem).
  8. This release is not thoroughly tested. My changes may cause bugs in some scenarios unknown to me.
  9. I only changed the pages and controls that made the layout wrong in the pages i visited while testing. I didn’t fix ALL DNN user controls with hardcoded alignments.
  10. This code is not upgradable. When I started working on modifying DNN for RTL, latest DNN version was 5.02.01. Version 5.02.02 was released during my work. Copying the changes to the new version was too much to do.
  11. The RTL style is having some problems in IE 7 (and I assume IE 6 too), while working fine on other browsers. I don’t think this is a major issue for a proof of concept.
  12. The default theme for FCK editor has curved dotted end in its toolbars. They look ugly in RTL mode. They should be flipped but I didn’t try to find a solution for this. As a workaround you can use the sliver theme which has a straight dotted vertical line at the end of toolbars.
4. Installation
  1. Set up DNN using the normal method.
  2. Copy the file portal.rtl.css from _default portal file to the folder of your created portals. (I didn’t find a way for the automatic copy of this file every time a new portal is created and didn’t want to spend more time in this point). This is VERY important as without it the page won’t have direction:rtl style and all JavaScript calculations that depends on dnn.direction() will not work.
  3. Add an RTL culture in languages page and compare the layouts of both RTL and LTR layouts by switching cultures using the flags in the top.
5. Things to consider

I suggested that RTL CSS is loaded along the normal css. But this duplicates the number of CSS files loaded and download by browser in RTL layout as almost no CSS file is suitable for both LTR and RTL directions so it duplicates the overhead of downloading. The other solution would be that RTL CSS file is loaded instead of the normal CSS file and duplicate the normal CSS file styles while modifying it for RTL layout. Maybe this is a better solution and easily doable by modifying the attached code.

Thursday, January 21, 2010

Proposal for DNN support of RTL layout Part 2

Please read the first part of this post first to better understand this post.
In my first post, I didn’t speak about the number of RTL script language users. They are about 600 millions (300 million in Arabic states (including economically important Persian gulf states), 170 millions in Pakistan, 75 millions in Iran, 30 millions in Afghanistan, etc.). This makes about 10% of the human population use RTL scripts mostly using the Arabic script which is used for many languages not just Arabic. Hebrew script users are 5 millions. Thaana script users in Maldives are 350,000 users.I have no info about Syriac user community but I think they won't be more than 3 millions but not sure.

I want also to clarify part of my previous post that maybe was not clear enough. The RTL CSS should override any property that affects horizontal direction not only float, text-align but also padding, margin, border any other property that affects the horizontal layout as in RTL layout it should be mirrored.
Now, I will continue explaining how to handle images and JavaScript in RTL layout.

Handling images in RTL mode

Usually images shouldn’t be horizontally flipped in RTL mode except:
  1. Images that specifies horizontal direction. For example in the image below the required image should be flipped to point to the text box not to point to the empty space beside the empty textbox.


  1. Images that are displayed in sequence horizontally as in RTL these images should be flipped to give the same effect. A real life example here is XDNewBlue skin. The skin is having messy layout in RTL mode because of its asymmetric layout. Here I will only show the container in RTL and LTR modes. The container header has 3 consecutive images which when rendered in reverse order were not correct. You will need to reverse second image to have the same layout of LTR flipped horizontally. In this case the image file is specified in a CSS file so the flipped image should be referenced in the RTL CSS file.

Images should be flipped either using:
  1. Client side horizontal flipping: this way is done using flipping images using CSS and remove the need to having a separate mirrored image file. After doing some search, I found a solution here. This solution is very good and would work by adding the CSS class below to the element to be flipped. This class should be empty in normal CSS and only having the text below in the RTL CSS file. Unfortunately this solution while it worked on Internet Explorer, Firefox, Safari and Chrome, it didn’t work on Opera browser. If you find any solution for Opera, please send it to me in comments. Please also note that if you apply this style to an element with text, the text will be mirrored which would not make it readable.
.flip-horizontal {
-moz-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
filter: fliph; /*IE*/ }
  1. Server side flipping: A more trivial and cross browser way but will require a separate copy of every flipped image file. I suggest if the original image called x.jpg, the flipped one should be called x.rtl.jpg. This is just a naming convention and will not imply automatic loading. You will have to handle programmatically substituting images with flipped ones in case you detect the culture is RTL culture.

JavaScript files in RTL mode

Usually JavaScript code don’t have problems with RTL layout. The catch here is that you should not specify any inline style properties in JavaScript files that affect horizontal layout as possible as you can. Use instead classes specified in CSS files, so they would be correct for both LTR and RTL layout or you will have to programmatically check for RTL and then reverse the style.
To determine the direction client side:
According to Part 1 of this post direction propery is put in the portal.rtl.css file. So the only way to get this value in either use GetComputedStyle in Firefox or currentStyle for Internet Explorer. The function below is inspired from here. The code should be put in js/dnn.js file.
dnn.direction = function() {
if (typeof (dnn.__direction) == "undefined") {
var hElement = document.getElementsByTagName('html')[0];
if (window.getComputedStyle)
dnn.__direction = window.getComputedStyle(hElement, null).direction;
else if (oElement.currentStyle)
dnn.__direction = hElement.currentStyle.direction;
}
return dnn.__direction;
}
Explicit handling of RTL layout in JavaScript
The only case I can think of now is handling absolute positioning set programmatically. Usually this is done in menus. Below the image of DNN menu in RTL layout



As the co-ordinates are not mirrored for RTL layout, the code adjusts the absolute position of menu to the left offset of the button. The solution here for RTL layout is if in RTL layout then it should position the menu = offsetLeft of button - menu width + offsetWidth of button and then the menu will look like the image below

Catches:

Real testing RTL layout should ALWAYS be done using an RTL script. In the screenshots above though text is aligned right but text itself is written from left to right which is not the case with RTL scripts (I took screenshots in English for simplicity). The RTL script is written from right to left. This text direction could affect in some cases the layout. For Example the image below is for Save/Cancel buttons are messed up because Latin script is written left to right regardless of alignment.

As you see the save icon is beside cancel and the cancel icon is beside register. But when the text is really in RTL Arabic script, we see in the image below that everything is OK without doing any change.

Recommendations:

  1. Changes to core DNN library and core modules should be done to handle both LTR and RTL layouts automatically.
  2. In snowcovered sold modules, there should be some info about if the displayed module is tested in RTL layout with the default to No.
  3. Every release of DNN or DNN core modules should be developed with RTL layout in mind and should be tested by a developer that knows a language that is written in RTL script ( There would be a lot of them especially Egyptians, Pakistanis and Israelis)
I didnt discuss the RTL localization of FCK editor. There is a post about it here.
Next week, I will publish the DNN code described here as a proof of concept.

Thursday, January 14, 2010

Proposal for DNN support of RTL layout Part 1

DotNetNuke or (DNN in short) is an open source CMS that is built over ASP.Net. It is also extensible using modules providing a powerful programming platform. Unfortunately it has some issues with right to left (RTL) layout. In this blog post I am proposing the steps to be done in DNN to fully support RTL and LTR in the same portal out of the box.

For any CMS to fully support localization it needs:

  1. Static content localization: currently supported by DNN using Resx files and allows admins to modify them using the languages page in admin menu. admins can translate the resx files manually or just use a language pack if they can find a good and complete one.
  2. Dynamic content localization: DNN Corp is currently working on it. They put an infrastructure for dynamic content localization in version 5.2 but still not usable for the end-user. If you cannot wait for it to be complete you can use for example Magic content instead of HTML module, Ealo package for menu, breadcrumb localization and NBStore instead of DNN Store module. All these modules support dynamic content localization
  3. Right to left layout: for scripts that is written in right to left direction. Currently there is no official support for RTL in DNN. You will have to manually add in portal.css html {direction:rtl;} to make layout flipped. Most of the forms of DNN works good but some forms like login and manage profile don’t have a correct layout along with the default DNN menu in the default skin. Though the layout issue is not a blocking bug, it makes the layout looks wrong and amateurish for the end user.

The scripts that uses right to left layout are:

  1. Arabic script: which is used for writing Arabic, Persian, Urdu, Punjabi, Sindhi, etc.
  2. Hebrew script: which is used for writing Hebrew and Jewish languages like Yiddish, Ladino, etc.
  3. Scripts that are used by minority languages that nobody knows or cares about: like Thaana, Syriac, N'Ko, etc.
Only Arabic and Hebrew scripts have economic value especially in the Persian Gulf area and in Israel.

Root causes of messing up the RTL layout in web applications:

  1. In table cell alignment when a cell content is aligned left it remains aligned left event in RTL direction and when aligned right it remains aligned right in RTL direction. It should be mirrored. So when in a perfect world when i say when in RTL mode an html element is having

    <table align="left">


    it should align right and vice versa. unfortunately this is not the case.
  2. When using float left and float right css styles, they behave like point 1
  3. When assuming that images are displayed in specific order horizontally, this order is reversed in RTL mode. So some images will need to be flipped to look as good as in LTR mode (will give an example for this when discussing image RTL localization).
  4. The absolute positioning always starts from top left of the page regardless of the direction of the page (RTL or LTR). Usually this affects absolute positioning when an html element is placed using calculated co-ordinates to align left with another element. In RTL mode you should update the calculation to align right(will give an example for this when discussing JavaScript RTL localization).

These four reasons made some great Javascript frameworks like ExtJs unusable in RTL layout without much customization.

Though this is out of topic, Microsoft did a really good job on supporting RTL layout out of the box in  Windows forms. When a control is positioned using a left anchor in RTL layout it is positioned right though the anchor is still left. All of the standard windows forms controls behaves well in RTL mode. (though not all third party windows forms controls support RTL mode like Devexpress for example but this is not the time to discuss this here)

Straightforward steps to make DNN portal have an RTL layout:

  1. modify portal.css and add

html {direction:rtl;}


  1. Open ~/DesktopModules/AuthenticationServices/DNN/login.ascx and modify all table align="left" to align="right" and vice versa.
Unfortunately the straight forward approach fails due to:
  1. Usually my customers want a multi lingual portal (English/Arabic) using dynamic content localization as described above. Adding the direction:rtl in portal.css will lead to displaying English language in RTL mode which is not correct.
  2. The default menu is still in LTR mode. 
  3. The manage profile tab is messed up because of hardcoded style of text-align: left in Profile.ascx. (though the RTL issue is supposed to be resolved but apparently the developer that fixed it doesn’t know how RTL languages would look like because he left behind text-align: left which messed up the RTL layout)

So now we have a dead end for a trivial quick solution.

The good solution for the RTL problem:

Requirements:
  • The solution should fix the problem of RTL without messing the layout of the LTR languages on the same multi-lingual portal.
Detecting RTL culture server side:

We need to detect the RTL language culture server side to do some actions for handling RTL layout as I will discuss later. The best way to do this is using Thread.CurrentThread.CurrentUICulture.TextInfo.IsRightToLeft property which is deeply buried in the properties of the properties of the properties of the current thread object.

I suggest adding a shared/static readonly property to DotNetNuke.Services.Localization.Localization class to return the IsRightToLeft property

Public Shared ReadOnly Property IsRightToLeft() As Boolean
    Get
        Return Thread.CurrentThread.CurrentUICulture.TextInfo.IsRightToLeft
    End Get
End Property


Handling the embedded styles in tags:

After digging into the DNN code, I found an enumeration called DotNetNuke.UI.WebControls.LabelMode. this enumeration is used by the FieldEditorControl to display the register and the user profile control. Labels have the LabelMode left and controls have the LabelMode right. And then the FieldEditorControl calls the ToSring().ToLower() for the enumeration to get the text to embed in style left or right (these are the two properties we care about now for RTL layout). So the good news is that if we override the ToString() of the enumeration to render left property if not RTL then "left" else "right" and vice versa, all the forms that uses this control will be adjusted automatically without handling them case by case. The bad news is that there is no support for overriding ToString() method for enumerations :). So we will need an extension method to get label mode text and get the correct string according to text layout and this extension method should be used instead of ToString()

Public Module LabelModeHelper
       <Runtime.CompilerServices.Extension()> _
       Public Function GetLabelModeString(ByVal mode As LabelMode) As String
           If mode <> LabelMode.Left AndAlso mode <> LabelMode.Right Then Return mode.ToString().ToLower()
           If mode = LabelMode.Left Then
               Return If(Localization.IsRightToLeft, "right", "left")
           End If
           If mode = LabelMode.Right Then
               Return If(Localization.IsRightToLeft, "left", "right")
           End If
           Return String.Empty
       End Function
   End Module

There remains another issue; handling LabelMode in RTL is fixed in three places Membership, Profile and User user controls. the same code is copy pasted 3 times in the user control. I write it here for convenience

Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
           'Get the base Page
            Dim basePage As PageBase = TryCast(Me.Page, PageBase)
            If basePage IsNot Nothing Then
                'Check if culture is RTL
                If basePage.PageCulture.TextInfo.IsRightToLeft Then
                    UserEditor.LabelMode = LabelMode.Right
                Else
                    UserEditor.LabelMode = LabelMode.Left
                End If
            End If
        End Sub

The code above is not based on a good design as it was written 3 times over and over again!!!! I commented it to make my code work as it would conflict with it. In my code I am trying to imitate the perfect case of Windows forms as I mentioned above. Thus developers would LabelMode.Left without bothering to think how it should be rendered in RTL mode.

For styles embedded in ascx align="left" or text-align:left, and also for right all these makes RTL layout looks odd. So all these should be deleted except if really needed and use the following code <%= LabelMode.Left.GetLabelModeString %> for hard coded part of "left" or move the embded alignments like float, text-align to CSS files which I will discuss now how to localize them.

Handling CSS in RTL mode:

The CSS files would contain values of float:left , float:right, text-align:left, etc. These values should be reversed in RTL mode. The solution I propose here is to immitate ScriptManager control in ASP.Net for loading javascript files. For example when ScriptManager loads x.js file, if ASP.Net is in debug mode and there exists a file called x.debug.js, this file will be loaded instead of x.js which would be loaded in release mode.

I suggest that all css would be optimized for LTR mode for example a file called x.css and if there is style in it that would make RTL looks odd, it would be overridden in another CSS file with the same name for example x.rtl.css  with the same classes but with only the parts that  make a conflict overridden and loaded only when the language is detected to have an RTL layout. So in this case of RTL language both files would be loaded unlike what the ScriptManager does.

To achieve this we need to modify the code in the AddStyleSheet method in CDefault class in the core DNN library to check if:

  1. An RTL language is used for current culture
  2. and there exists a file with the same name of the CSS file being added to the page ending with .rtl.css

if both conditions are fulfilled then the .rtl.css class is loaded immediately after the file being added.

Public Sub AddStyleSheet(ByVal id As String, ByVal href As String, ByVal isFirst As Boolean)
    'Find the placeholder control
    Dim objCSS As Control = Me.FindControl("CSS")

    If Not objCSS Is Nothing Then
        'First see if we have already added the <LINK> control
        Dim objCtrl As Control = Page.Header.FindControl(id)

        If objCtrl Is Nothing Then
            Dim objLink As New HtmlLink()
            objLink.ID = id
            objLink.Attributes("rel") = "stylesheet"
            objLink.Attributes("type") = "text/css"
            objLink.Href = href

            If isFirst Then
                'Find the first HtmlLink
                Dim iLink As Integer
                For iLink = 0 To objCSS.Controls.Count - 1
                    If TypeOf objCSS.Controls(iLink) Is HtmlLink Then
                        Exit For
                    End If
                Next
                objCSS.Controls.AddAt(iLink, objLink)
                If Localization.IsRightToLeft Then
                    AddRTLStyleSheet(id, href, objCSS, iLink + 1)
                End If
            Else
                objCSS.Controls.Add(objLink)
                If Localization.IsRightToLeft Then
                    AddRTLStyleSheet(id, href, objCSS)
                End If
            End If
        End If
    End If
End Sub

Private Sub AddRTLStyleSheet(ByVal id As String, ByVal href As String, ByVal objCSS As Control, Optional ByVal index As Integer = -1)
    Dim rtlhref As String = GetRTLStyleSheetName(href)
    If String.IsNullOrEmpty(rtlhref) Then Return
    Dim objLink As New HtmlLink()
    objLink.ID = id + "rtl_"
    objLink.Attributes("rel") = "stylesheet"
    objLink.Attributes("type") = "text/css"
    objLink.Href = rtlhref
    If index <> -1 Then
        objCSS.Controls.AddAt(index, objLink)
    Else
        objCSS.Controls.Add(objLink)
    End If

End Sub

Private Function GetRTLStyleSheetName(ByVal href As String) As String
    Dim physicalFilePath As String = Server.MapPath(href)
    If Path.GetExtension(physicalFilePath).ToLower <> ".css" Then
        Return Nothing
    End If
    physicalFilePath = physicalFilePath.Substring(0, physicalFilePath.Length - 4) + ".rtl.css"
    If File.Exists(physicalFilePath) Then
        Return href.Substring(0, href.Length - 4) + ".rtl.css"
    End If
    Return Nothing
End Function

I have tested the code above and it loaded portal.rtl.css, skin.rtl.css and index.rtl.css which are files that I made. It may not be fully optimized but it is a start at least.

portal.rtl.css is the file that should contain html {direction:rtl;} not in portal.css.

The rtl css files should be only overriding properties in  css classes in the normal css files that are only related to rtl only. No need to copy/paste the stylesheet and make changes. This way you will double size of css and will have to maintain 2 css files. Below is an example for this.

In portal.css file there is class

.branding-bottom li { list-style: none; margin: 0 10px 0 0; padding: 0; display: block; width: 170px; float: left; }

In portal.rtl.css there should be

.branding-bottom li { float: right; margin: 0 0 0 10px; }

In my next post, I will discuss the 2 remaining aspects of RTL localization; localizing images and JavaScript files. I will publish my modified code as a proof of concept but won’t be suitable for production.

I hope that DNN corp incorporate these changes to make DNN more powerful than it already is.

Acknowledgements: I copied the style of the grey code boxes form here

Wednesday, January 13, 2010

My first post

This my first time to start blogging. I found myself tempted to start a blog to share my ideas with the developer community. For what I see now, I will always blog about programming and software development. Talking about politics could be dangerous :) so I hope I won’t speak about them