9 December 2014

How to access profiles, profile keys through code

Last month I went to the Sitecore DMS fundamentals training as I wanted to have a taste of what and how marketing team will use all the tools available. Some really interesting materials and tools that we are coding for but unfortunately don't really use too often...

During the training session, we had a chat about Profile card, Profile keys and how we are assigning those to a content item. The example given there was for a Cycling Shop. We could create some profile keys for "Beginner", "Amateur" and "Professional". Obviously, you will have different product that will be for Professional: Helmets, Bikes and even GoPro (why not...). Well that gave me an idea for this blog posts and the following on:
  1. How to access those keys and Profile card when we access the item.
  2. How to retrieve Related items based on profile keys - we will continue with that on a later post...
Before starting, I just wanted to point you to a few blogs and posts which helped me a lot:


So first let see how the data are stored in the sitecore item:

When you assign the Profile card and keys in Sitecore you will  click on the "Edit Profile Card associated with the item":

  

Next you will certainly edit the profile you want to assign to the content item and edit the values


Once you have done that, you will notice that those data are stored on the Content Item. The field you are looking for is (enable the standard fields): Tracking ( - or __tracking)

 If you view it with the raw values, you will see that all the data are stored in XML format:

I have pasted the formated XML here for readability:


 < profile id="{1E05D43D-BF07-403D-893D-552FCFBF2BD0}" name="MyTest" presets="general|100||">
  < key name="Digital" value="2" />
  < key name="Traditional" value="2" />
 < /profile>

Since those data are in a field, that means we can access them throuh code... Here are a few methods to show you how you can retrieve the ContentProfiles or just the ContentProfiles name or if you prefer to get the profile Keys... The important thing here is to get the Field "__tracking" and cast it to a Sitecore.Analytics.Data.TrackingField. This field will give you all the required information you need for Campaigns, events, and Profiles... From this field, you should now be able to extract the different profiles associated with the item and then from each profile you can access every keys...

        /// 
        /// Return the list of all profiles for the content Item
        /// 
        /// 
        /// 
        public static IEnumerable GetProfiles(Item item)
        {
            if (!AnalyticsSettings.Enabled)
            {
                return new List();
            }

            Field field = item.Fields["__tracking"];
            if (field == null)
                return new List();

            ContentProfile[] profiles = (new TrackingField(field)).Profiles;
            if (profiles == null)
                return new List();

            return profiles.ToList();
        }

        /// 
        /// Return the list of all profile names associated with the content Item
        /// Where Keys are defined. This is to make sure we are not returning the profiles that are not assigned
        /// 
        /// 
        /// 
        public static IEnumerable GetProfileNames(Item item)
        {
            if (!AnalyticsSettings.Enabled)
            {
                return new List();
            }

            Field field = item.Fields["__tracking"];
            if (field == null)
                return new List();

            ContentProfile[] profiles = (new TrackingField(field)).Profiles;
            if (profiles == null)
                return new List();

            return profiles.Where(p => GetProfileKeys(p,1).Count()>0).Select(i => i.Name).ToList();
        }


        /// 
        /// Get the keys contains in the Content Profile with a minimum score
        /// 
        /// 
        /// 
        /// 
        public static IEnumerable GetProfileKeys( ContentProfile profile, int minimumScore)
        {
            var matchedKeys = new List();

            foreach (ContentProfileKeyData profileKey in profile.Keys)
            {
                if (profileKey.Value >= minimumScore)
                {
                    matchedKeys.Add(profileKey);
                }
            }
            return matchedKeys;
        }

        /// 
        /// Get the key names contains in the Content Profile with a minimum score
        /// 
        /// 
        /// 
        /// 
        public static IEnumerable GetProfileKeyNames(ContentProfile profile, int minimumScore)
        {
            var matchedKeys = new List();

            foreach (ContentProfileKeyData profileKey in profile.Keys)
            {
                if (profileKey.Value >= minimumScore)
                {
                    matchedKeys.Add(profileKey.Name);
                }
            }
            return matchedKeys;
        }

        /// 
        /// Get the Key Names that are define in the Item profiles with a minimum score
        /// 
        /// 
        /// 
        /// 
        public static IEnumerable GetProfileKeyNames(Item item, int minimumScore)
        {
            List keyNames = new List();

            IEnumerable profiles = GetProfiles(item);
            foreach (var profile in profiles)
            {
                keyNames.AddRange(GetProfileKeyNames(profile, minimumScore));
            }
            return keyNames.Distinct();
 
        }

Now on the next post, we will see how we could use that to get the related content items based on Profile Keys...

3 December 2014

Sitecore 7.x MVC and placeholder key with Capital Letter

Another post today about Sitecore MVC and Placeholder Key. We have this project using Sitecore MVC where the placeholders are defined with capital letters, Eventhough the Page Editor recommendation guide (page 14 to 15) does not say that using Capital Letters will create issues in MVC.... well.... it does. So even if not said directly on the guide: use lower case for your placeholder name... But just in case it is too late and you need to page editor, you can read the following...

In MVC, adding a placeholder in the renderings with capital letters will not create any problem when binding any rendering to this placeholder. However, Sitecore will not resolve your Placeholder settings correctly and you will not see the "Add Here" buttons neither the "allowed controls"

To summarise, if you do something like:

        @Html.Sitecore().Placeholder("SideColumn")

You will end up withthe SideColumn placeholder not even displayed, even if you add capital letters in the Placeholder Settings:



That would not really bother me as I would need to rename all placeholder into lower case and voila!!! But I wanted to find out if there would be an easier way and something that would allow me to have capital letter in the Name... When trying to see where this is falling, you have to check how the Chrome Data are rendered. If you look at the pipeline "GetChromeData", you can see that there is a processor for Placeholder:

      < getChromeData>
        < processor type="Sitecore.Pipelines.GetChromeData.Setup, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetChromeData.GetFieldChromeData, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetChromeData.GetWordFieldChromeData, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetChromeData.GetRenderingChromeData, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetChromeData.GetEditFrameChromeData, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel" />
      < /getChromeData>


When following the trail from the GetPlaceholderChromeData you will find that the code is calling another pipeline "GetPlaceHolderRenderings"
                        GetPlaceholderRenderingsArgs getPlaceholderRenderingsArgs = new GetPlaceholderRenderingsArgs(text, layout, args.Item.Database)
                        {
                            OmitNonEditableRenderings = true
                        };
                        CorePipeline.Run("getPlaceholderRenderings", getPlaceholderRenderingsArgs);

with the pipeline looking like:
      < getPlaceholderRenderings>
        < processor type="Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetPlaceholderRenderings.GetPredefinedRenderings, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetPlaceholderRenderings.RemoveNonEditableRenderings, Sitecore.Kernel" />
        < processor type="Sitecore.Pipelines.GetPlaceholderRenderings.GetPlaceholderRenderingsDialogUrl, Sitecore.Kernel" />
      < /getPlaceholderRenderings>



Continuing the trail... go to the first processor: GetAllowedRenderings. In there you will find out how the PlaceholderItem is getting retrieved
    using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
    {
     item = Client.Page.GetPlaceholderItem(args.PlaceholderKey, args.ContentDatabase, args.LayoutDefinition);
    }


If you look inside this method you will see that those placeholder Item are retrieved from the cache:
 PlaceholderCache placeholderCache = PlaceholderCacheManager.GetPlaceholderCache(database.Name);
 Item item = placeholderCache[placeholderKey];
 if (item != null)
 {
  return item;
 }
 int num = placeholderKey.LastIndexOf('/');
 if (num >= 0)
 {
  string key = StringUtil.Mid(placeholderKey, num + 1);
  item = placeholderCache[key];
 }
 return item;

You will note that the placeholderCache.IsKeyCaseSensitive is false, and this return null if you are passing the key as capital letters... So if we look at how the cache is build:
public virtual void Reload()
{
 lock (FieldRelatedItemCache.Lock)
 {
  this.itemIDs = new SafeDictionary();
  string query = string.Format("{0}//*[@@templateid = '{1}']", this.ItemRoot.Paths.FullPath, this.ItemTemplate);
  Item[] array = this.Database.SelectItems(query);
  if (array != null)
  {
   Item[] array2 = array;
   for (int i = 0; i < array2.Length; i++)
   {
    Item item = array2[i];
    string text = item[this.FieldKey];
    if (!string.IsNullOrEmpty(text))
    {
     string cacheKey = this.GetCacheKey(text);
     if (!this.itemIDs.ContainsKey(cacheKey))
     {
      this.Add(text, item);
     }
    }
   }
  }
 }
}

with the method GetCacheKey where you can see that if the IsKeyCaseSensitive is false the cache key will be lower case...
protected virtual string GetCacheKey(string key)
{
 if (this.IsKeyCaseSensitive || string.IsNullOrEmpty(key))
 {
  return key;
 }
 return key.ToLowerInvariant();
}


Also inside the Add method there is also a call to the GetCacheKey... So
 string cacheKey = this.GetCacheKey(key);
 lock (FieldRelatedItemCache.Lock)
 {
  this.itemIDs[cacheKey] = item.ID;
 }

That means the key is stored as capital Letter if you have define your field in the placeholder settings as capital letter. So what happens where we try to retrieve the item from the cache is that we are passing the Key as Capital letter but it is stored as lower case...
public virtual Item this[string key]
{
 get
 {
  Item result;
  lock (FieldRelatedItemCache.Lock)
  {
   ID itemId;
   if (!this.itemIDs.TryGetValue(key, out itemId))
   {
    result = null;
   }
   else
   {
    Item item = this.Database.GetItem(itemId, Context.Language, Version.Latest);
    if (item == null)
    {
     this.itemIDs.Remove(key);
    }
    result = item;
   }
  }
  return result;
 }
}


So I went around this issue by adding a custom pipeline action on the GetChromeData pipeline to convert the Placeholder Key to Lower Invariant if the Key for the cache manager is not sensitive...
namespace MyProject.Business.Sitecore.Pipelines
{
    public class GetPlaceholderKeyForChromeData : GetPlaceholderChromeData
    {
        public override void Process(GetChromeDataArgs args)
        {
            if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
            {
                PlaceholderCache placeholderCache = PlaceholderCacheManager.GetPlaceholderCache(args.Item.Database.Name);
                if (!placeholderCache.IsKeyCaseSensitive)
                {
                    string keyString = args.CustomData["placeHolderKey"] as string;
                    args.CustomData["placeHolderKey"] = keyString.ToLowerInvariant();
                }
            }
        }
    }
}

With the following config patch
      
< getchromedata>
        < processor patch:before="processor[@type='Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel']" type="MyProject.Business.Sitecore.Pipelines.GetPlaceholderKeyForChromeData, MyProject.Business">
      < /processor>
< /getchromedata>


Overwritting Placeholder settings on specific templates

On a previous post, I was talking about how to create a placeholder which will allow certain controls only. For that we had a 2 Columns Renderings (Main Column and Side Column) which had the following Markups and placeholders



    < div class="row">  
        < h1 class="col-sm-12">@Html.Sitecore().Placeholder("imagebanner")< /h1>  
    < /div>  
      
    < div class="row">  
         < h1 class="col-sm-12">@Html.Sitecore().Placeholder("contenttitle")< /h1>  
    < /div>  
    < div class="row">  
        < div class="col-sm-9 main-column">  
            < article class="col-sm-12">  
                @Html.Sitecore().Placeholder("contentcolumn")  
            < /article>  
        < /div>  
        < div class="col-sm-3 sidebar">  
            @Html.Sitecore().Placeholder("sidecolumn")  
        < /div>  
    < /div>  


As we saw, it was quite easy to define our Sitecore placeholder settings and allow a few controls:



Well this was really nice and handy if your templates using the 2 Columns Rendering should allow all 4 renderings: Rich Text, Image, Navigation, Related News. BUT... What if some of your templates should only allow 1 rendering? In the news content page for instance, you want to allow only the related news panel. Creating a new rendering with a different placeholder key??? no way.... Well, good thing sitecore thought about this. You can overwritte the placeholder through the presentation:

1- Define a new placeholder in Sitecore. This new placeholder will be exactly the same as per the first one Except:
  • Having a different Key: the key must be unique, so you have to use another key
  • Instead of allowing the 4 renderings, we will only allow 1: Related News Panel


2- Edit the template - or item you want to have different settings in the placeholder:

In this example we will edit a specific item but you can do it at the template level:
  • Select your item.
  • Go to the presentation
  • Select the placholder settings 
  • Click edit
   
  •  When editing, you can select "Select Existing Settings" on the Left hand side and then Select our new placeholder settings. Make sure the Placholder Key correspond to the key you were using previously


You can now save everything and go to the page editor. You will notice that on the placeholders you will not see "SideColumn" but "NewsItemSidePanel". Then if you add a new panel, you will notice only the related news panel is available:



28 November 2014

Configuration in SubFolder in the Include folder

I have been writing a previous post about the importance of Name for configuration files in the include folder. My usual habit was to prefix them with z. so all my custom files will be located at the last place (by alphabetical order). This means that those will be the last to be parsed which I can be sure to place any event or pipeline processor where I wanted. So my Include folder will looks like the following:


After reading a great post today from @jammykam, I wanted to share this:
You can create a folder and add all your custom files inside. From the Sitecore documentation (page 4), sitecore will first parse all the config file and then go recursively through subfolders:

When Sitecore reads the include files, it traverses the file system recursively. Sitecore first enumerates all the *.config files in the /App_Config/Include folder and then recursively enumerates all the sub-folders. You should take this order into account when you create sub-folders in the /App_Config/Include folder.
So creating a subfolder and moving the custom files inside will have the same effect than prefixing all my custom config:



27 November 2014

Sitecore 7.5 and Installing Packages through handler is broken


I was upgrading a 7.2 to 7.5 today and I noticed something unexpected that I wanted to share today.

As maybe a few of you, I am using TDS to generate my update packages for my deployments. This is awesome and We have a .ashx on our QA environment that automatically install those packages and publish items afterwards. Well, this was quite nice and working fine on Sitecore 7.2.

However, after upgrading the site to 7.5 I noticed that the solution was not compiling against the new Sitecore DLL.. And the reason was: SaveInstallationMessages() method was removed from the UpdateHelper on the Sitecore 7.5. So the following line was failing:

                    UpdateHelper.SaveInstallationMessages(entries, text);


If you compare the previous version of the Install() method from Sitecore.Update.InstallUpdatePackage

protected string Install()
{
 string result;
 using (new ShutdownGuard())
 {
  this.logEntries = new List();
  PackageInstallationInfo installationInfo = this.GetInstallationInfo();
  string text = null;
  List entries = null;
  try
  {
   this.WriteMessage(string.Format("{0} package: {1}", (installationInfo.Action == UpgradeAction.Preview) ? "Analyzing" : "Installing", installationInfo.Path), null, Level.INFO, false);
   entries = UpdateHelper.Install(installationInfo, this, out text);
  }
  catch (PostStepInstallerException ex)
  {
   entries = ex.Entries;
   text = ex.HistoryPath;
   throw ex;
  }
  finally
  {
   UpdateHelper.SaveInstallationMessages(entries, text);
  }
  result = text;
 }
 return result;
}

With the new version:

// Sitecore.Update.InstallUpdatePackage
protected string Install()
{
 string result;
 using (new ShutdownGuard())
 {
  this.logEntries = new List();
  PackageInstallationInfo installationInfo = this.GetInstallationInfo();
  string text = null;
  this.logMessages = new List();
  try
  {
   this.WriteMessage(string.Format("{0} package: {1}", (installationInfo.Action == UpgradeAction.Preview) ? "Analyzing" : "Installing", installationInfo.Path), null, Level.INFO, false);
   this.logMessages = UpdateHelper.Install(installationInfo, this, out text);
   base.InstallationHistoryRoot = text;
  }
  catch (PostStepInstallerException ex)
  {
   this.logMessages = ex.Entries;
   base.InstallationHistoryRoot = ex.HistoryPath;
   throw ex;
  }
  finally
  {
   this.SaveInstallationMessages();
  }
  result = text;
 }
 return result;
}

You can notice the SaveInstallationMessages is now defined on the control itself. This is a bit annoying as we cant re-use it in our handler anymore.
Since the UpdateHelper.Install is still outing the history path I could go around the issue by adding the SaveInstallationMessages() into our handler directly but that means duplication of methods. I would have prefered the old static method, but I could not find any other way for now...

        protected string Install(string package)
        {
            var log = LogManager.GetLogger("LogFileAppender");
            string result;
            using (new ShutdownGuard())
            {
                var installationInfo = new PackageInstallationInfo
                {
                    Action = UpgradeAction.Upgrade,
                    Mode = InstallMode.Install,
                    Path = package
                };
                string text = null;
                List entries = null;
                try
                {
                    entries = UpdateHelper.Install(installationInfo, log, out text);
                }
                catch (PostStepInstallerException ex)
                {
                    entries = ex.Entries;
                    text = ex.HistoryPath;
                    SC.Diagnostics.Log.Error("Automated Deployment error " + ex.StackTrace, "Automated deployment");
                    throw;
                }
                finally
                {
                    this.SaveInstallationMessages(entries, text);
                }

                result = text;
            }

            return result;
        }

        public string SaveInstallationMessages(System.Collections.Generic.List entries, string historyPath)
        {
            string text = System.IO.Path.Combine(historyPath, "messages.xml");
            FileUtil.EnsureFolder(text);
            using (System.IO.FileStream fileStream = System.IO.File.Create(text))
            {
                XmlEntrySerializer xmlEntrySerializer = new XmlEntrySerializer();
                xmlEntrySerializer.Serialize(entries, fileStream);
            }
            return text;
        }

14 November 2014

Sitecore 7.5 Image resizing changes


I am sure a few people already know about this but I just wanted to put it out there in case some f us are looking for it:

From Sitecore 7.5, the Image resizing is not working the way it was.

On previous version of Sitecore you would have been familiar with the resizing, passing the parameters in the URL:

http://mywebsite/~/media/imagesample.ashx?w=150
This will output the resized version of the image with a width of 150px. The usual parameters used for resizing are:

w = Width (in pixels)
h = Height (in pixels)
mw = MaxWidth (pixels)
mh = MaxHeight (pixels)
bc = Background Color
sc = Scale (floating point)
thn = Display as thumbnail


Well, as this was still the case in 7.2, I assumed it would be the same in 7.5... and obviously it id not... My bad as I did not read the release notes for this version. Thanks to Sitecore support who pointed me to the link... Here is the abstract that relates to the issue:

Media request protection
  • The new media request protection feature restricts media URLs that contain dynamic image-scaling parameters so that only server-generated requests are processed. This ensures that the server only spends resources and disk space on valid image scaling requests.
  • Sitecore Corp. wants to give credit to Cognifide (Adam Najmanowicz and Marek Musielak, www.cognifide.com) for the discovery of this vulnerability, for their cooperation and providing the initial ImageGuard solution.
  • When the feature is enabled, Sitecore automatically signs image URLs that are rendered by the <renderField> pipeline and adds a hash value to the query string. When processing an incoming media request, image resizing/scaling is skipped if any of the relevant query string parameters in the image URL have been altered or any extra resizing parameters have been appended to the URL. In these cases, Sitecore returns the original, unaltered image. Requests for the original image (without any resizing/scaling parameters) work as usual and are not restricted.
  • If you have code in your solution that manually appends image scaling parameters to image URLs without passing the parameters to the <sc:image> control, you must rewrite the code to append a hash value using one of the helper methods or rewrite it to use the <sc:image> control. In XSLT renderings, you can use sc:SignMediaUrl(url) or sc:GetSignedMediaUrl(…). In C# code, you can use HashingUtils.ProtectAssetUrl(url) which appends a hash value to the provided URL, or you can use GetAssetUrlHash(url) to only return the hash value for the provided URL.
  • You must ensure that any static image URLs (for example, in CSS or aspx files) that contain image scaling parameters are updated to include the corresponding hash value. To make it easy to update these type of static URLs, a new /sitecore/admin/MediaHash.aspx is available, which lets you enter a media URL and, with the click of a button, generate the corresponding hash value.
  • Image URLs where resizing parameters are manipulated or added using JavaScript will no longer work because you cannot calculate the hash values from JavaScript. If you need this type of functionality, you will have to find a different approach, such as implementing a service that can calculate the correct hash value.
  • This feature is configured in the /App_Config/Include/Sitecore.Media.RequestProtection.config file.

So if like me you used to pass the width o height in the URL, then you may have to revisit a few view rendering to ensure that you are now passing the Hash value.

So if you have a Model (Glass Map) like the following
using System;
using MyProject.Common.Constants;
using MyProject.Dal.Models.Base;
using Glass.Mapper.Sc.Configuration.Attributes;
using Glass.Mapper.Sc.Fields;

namespace MyProject.Models.ImageCarousel
{
    [SitecoreType(TemplateId = TemplateIds.DataElements.CarouselImage, AutoMap = true)]
    public class CarouselImage : SitecoreBase
    {
        [SitecoreField(FieldId = FieldIds.DataElements.CarouselImage.ImageBanner)]
        public virtual Image ImageBanner { get; set; }

        [SitecoreField(FieldId = FieldIds.DataElements.CarouselImage.DestinationUrl)]
        public virtual Link DestinationUrl { get; set; }

        [SitecoreField(FieldId = FieldIds.DataElements.CarouselImage.Description)]
        public virtual string Description { get; set; }
    }
}

Then you will be able to update your cshtml view to use:
        < img src="@Sitecore.Resources.Media.HashingUtils.ProtectAssetUrl(string.Format("{0}?w=150", Model.imagebanner.src))"  />

31 October 2014

Sitecore 7.2 MVC, Personalisation issue with WFFM


Not long ago, I faced an issue when using personalization. It is usually straight forward to setup but somehow this time it did not work. This was quite annoying and get me going for a bit of time. So I wanted to share it in case anybody out there would faced the same issue.

So for the project we were using:
  • Sitecore 7.2 MVC, 
  • GlassMapper
  • WFFM for all the forms on the website. 
As you can imagine most of the presentation elements were View renderings, except a few controller renderings. When setting up the personalisation rules, everything seems to be working perfectly at first until the testing team reported an issue:
When personalisation rules were set on an item, none of them was triggered. Looked like the rules engine was not working correctly. 







After investigating the issue it seems like it was only when personalisation was applied on Controller rendering. After trying the rule engine on a fresh Sitecore install, everything was working perfectly. So I started to take out our customisation elements one by one. After commenting all pipeline actions I could think of having anything to do with the issue and no luck, the issue was still there. Until "light bulb": let's check the Showconfig.aspx page:


And there you go, the pipeline action where the rule engine is triggered is the "CustomizeRendering" from the Sitecore.Mvc.Analytics.config


As you can see from the pipeline, One processor is intercepting the GetRenderer before it was hitting the rules engine. The pipeline action is from the Sitecore.Forms.MVC.config file. Looking at the code in this processor you will see the following:

if (args.Rendering.RenderingItem.ID != IDs.FormMvcInterpreterID)
{
    return base.GetRenderer(rendering, args);
}
Tuple< string, string> controllerAndAction = this.GetControllerAndAction(rendering, args);
if (controllerAndAction == null)
{
    return null;
} 


This will obviously intercept the Controller rendering and return the Conntroller and action prior evaluating the Rules...

If you are looking at the configuration file: Sitecore.Forms.MVC.config then you will realized that the intend is to place this processor before the GetControllerRenderer to ensure that the form controller will be executed before a normal ControllerRenderer:


When Sitecore is parsing the Configuration files the Sitecore.MVC.Analytics.config is parsed after the Sitecore.Forms.MVC.config... Which means that the GetFormControllerRenderer will get executed before the Rule engine...

Solution:
rename the config file to be "t_Sitecore.Forms.MVC.config". This will ensure that the processor will happen after the rules engine kicks in:

29 October 2014

Restricting renderings selection for placeholder

Today, I just wanted to talk about a simple thing but quite helpful when starting to develop with Page Editor: How can you specify which rendering can be inserted in a placeholder. This is really useful for Side panels for insance. You have created different types of panel (renderings) and now you want the to allow content editor to insert only those through the Page Editor such as the follownig:



Well, this is quite simple:

1- Placeholder in your code

Let's say you have a 2 columns rendering that will placed int your main layout. This 2 columns rendering will have 2 laceholders: one for the Main content and one for the Side Column. Something like:

< div class="row">
    < h1 class="col-sm-12">@Html.Sitecore().Placeholder("imagebanner")< /h1>
< /div>

< div class="row">
     < h1 class="col-sm-12">@Html.Sitecore().Placeholder("contenttitle")< /h1>
< /div>
< div class="row">
    < div class="col-sm-9 main-column">
        < article class="col-sm-12">
            @Html.Sitecore().Placeholder("contentcolumn")
        < /article>
    < /div>
    < div class="col-sm-3 sidebar">
        @Html.Sitecore().Placeholder("sidecolumn")
    < /div>
< /div>


2- Sitecore elements

On the sitecore side, the only thing you will need to define the Placeholder Settings under the path: /sitecore/layout/Placeholder Settings/xxx

You can noticed the Placeolder Key match the definition of the placeholder in our code:
@Html.Sitecore().Placeholder("sidecolumn")

You can also notice that you can select the Allowed Controls. Those will be the controls that are display to the Content Author when adding a new element in the placeholder. Once this is defined, Sitecore will automatically make the mapping. and now on the Page Editor you will see the "Add" buttons when you select the placeholder...In a future post I will describe how you can overwrite this placeholder settings for specific template...

13 October 2014

Chrome and issue with User Manager


This entry will be quite quick to write. But still worth having it here as this might help a few people who like me had to investigate an issue with the User Manager...

Most of our clients love to use Chrome to edit their content. Eventhough everything seems to be working fine, from time to time there are few things here and there. Here is a good one:

When creating a user using the User Manager and Chrome (Sitecore 7.2), you will get the following issue:


Obviously if you are like me (big fan of Firefox), then you may not have this issue :)

Anyway, after reviewing the log files and trying to debug... I came across this article in the Sitecore Knowledge Base. The issue is well described as well as the fix:



 1- Open the /Website/sitecore/shell/Applications/Security/SelectRoles/SelectRoles.xaml.xml file.

 2- Replace the following block of code in the update() function:

    if(typeof(option.innerText) != 'undefined')
    {
      optionValue = option.innerText;
    }
    else
    {
      optionValue = option.textContent;
    }

    with the following:

if(typeof(option.textContent) == 'undefined')
    {
      optionValue = option.innerText;
    }
    else
    {
      optionValue = option.textContent;
    }

3- Clear the browser cache.

29 September 2014

AD module - Reset Password and Port Number


Active Directory Module. This is a nice module to play with. It is quite powerful and quite easy to configure following the documentation provided.

On a recent project we had the situation where we needed to allow the user to change their password. That sounds like the easiest task in the world and somehow whatever we tried we ended up with the following issue:


Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: System.DirectoryServices
   at System.DirectoryServices.DirectoryEntry.Invoke(String methodName, Object[] args)
   at System.Web.Security.ActiveDirectoryMembershipProvider.ChangePassword(String username, String oldPassword, String newPassword)
   at Sitecore.Security.Accounts.MembershipUserWrapper.ChangePassword(String oldPassword, String newPassword)
   at Sitecore.Shell.Applications.Security.SetPassword.SetPasswordPage.OK_Click()

Nested Exception

Exception: System.Runtime.InteropServices.COMException
Message: Configuration information could not be read from the domain controller, either because the machine is unavailable or access has been denied. (Exception from HRESULT: 0x80070547)

This error was logged from the front end but also when trying to change the user password from the Sitecore Desktop Interface. We have tried quite a few things with user permission... nothing work until we added the port number to the connection string:

  < add connectionstring="LDAP://adconnection:389/OU=Users,DC=mycomany,DC=external" name="MyAdConnString"> 

So just in case you are getting similar issues, it is worth adding the port number to the connection string... Hoping that wil save a few hours of debugging...

14 September 2014

Retrieve Datasource from a Controller Rendering

If you are using Sitecore MVC then I am sure you have been using Controller Rendering. When using personlisation you are most likely either switch the Rendering or simply change the Datasource to point at different items. In your controller code, you may have tried the following:

SitecoreContext.GetCurrentItem< Item>()

With the SitecreContext being the Glass.Mapper.Sc.ISitecoreContext...

The GetCurrentItem in the controller will always return the Context Item, not the Datasource. So you need to actually retrieve the Datasource with Code. What you can use is something similar to:

        /// 
        /// Get either the data source or the context item
        /// 
        private Item Datasource
        {
            get
            {
                var dataSource = SitecoreContext.GetCurrentItem< Item>();
                string datasourceId = RenderingContext.Current.Rendering.DataSource;
                if (Sitecore.Data.ID.IsID(datasourceId))
                {
                    dataSource = SitecoreContext.GetItem< Item>(new Guid(datasourceId));
                }

                return dataSource;
            }
        }

From the above code you can see that this will either return the Context Item if the Datasource is not set, OR the datasource item if set.

Now, I am sure you will have to create multiple Controller Renderings in your solution. So it could be a great idea to create an abstract class Controller and all your controller will be based on this:

namespace MySite.Controllers
{
    public abstract class BaseController: SitecoreController
    {
        protected readonly ISitecoreContext BaseSitecoreContext;

        public BaseController(ISitecoreContext sitecoreContext)
        {
            this.BaseSitecoreContext = sitecoreContext;
        }

        /// 
        /// Get either the data source or the context item
        /// 
        public Item Datasource
        {
            get
            {
                var dataSource = BaseSitecoreContext.GetCurrentItem< Item>();
                string datasourceId = RenderingContext.Current.Rendering.DataSource;
                if (Sitecore.Data.ID.IsID(datasourceId))
                {
                    dataSource = BaseSitecoreContext.GetItem< Item>(new Guid(datasourceId));
                }

                return dataSource;
            }
        }
    }
}


You can now use the base Controller to derive your custom controller:
    public class CalculatorController : BaseController
    {
        private readonly ISitecoreContext sitecoreContext;

        public CalculatorController(ISitecoreContext sitecore)
            : base(sitecore)
        {
             // Do whatever
        }
    }

2 September 2014

Email function in Sitecore

Just a quick reminder for those who wants to send an email from Sitecore. There are times where you have to write your own method and this is completely understandable. However, dont forget to check if there is already a method that does it for you... I have seen a SendEmail Method where the SMTP, username, password and port were defined on the item fields. Well for this specific example, you have a method that already send email in sitecore:

Sitecore.MainUtil.SendMail(System.Net.Mail.MailMessage message)

This method is using the System.Net.Mail.SmtpClient to send the email which what most of us will actually implement on custom code. So why not using it... Here is the Method:

  /// 
  /// Sends the mail.
  /// 
  /// The message.
  public static void SendMail(System.Net.Mail.MailMessage message)
  {
   string mailServer = Settings.MailServer;
   System.Net.Mail.SmtpClient smtpClient;
   if (string.IsNullOrEmpty(mailServer))
   {
    smtpClient = new System.Net.Mail.SmtpClient();
   }
   else
   {
    int mailServerPort = Settings.MailServerPort;
    if (mailServerPort > 0)
    {
     smtpClient = new System.Net.Mail.SmtpClient(mailServer, mailServerPort);
    }
    else
    {
     smtpClient = new System.Net.Mail.SmtpClient(mailServer);
    }
   }
   string mailServerUserName = Settings.MailServerUserName;
   if (mailServerUserName.Length > 0)
   {
    string mailServerPassword = Settings.MailServerPassword;
    System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(mailServerUserName, mailServerPassword);
    smtpClient.Credentials = credentials;
   }
   smtpClient.Send(message);
  }



So you should be able to use it straight away as per the following:

var myMessage = new MailMessage(from, to, subject, body);
  Sitecore.MainUtil.SendMail(myMessage);


RE-EDITED the post: obviously you need to setup your SMTP in the web.config for this to work:
      < !--  MAIL SERVER
            SMTP server used for sending mails by the Sitecore server
            Is used by MainUtil.SendMail()
            Default value: ""
      -->
      < setting name="MailServer" value="" />
      < !--  MAIL SERVER USER
            If the SMTP server requires login, enter the user name in this setting
      -->
      < setting name="MailServerUserName" value="" />
      < !--  MAIL SERVER PASSWORD
            If the SMTP server requires login, enter the password in this setting
      -->
      < setting name="MailServerPassword" value="" />
      < !--  MAIL SERVER PORT
            If the SMTP server requires a custom port number, enter the value in this setting.
            The default value is: 25
      -->
      < setting name="MailServerPort" value="25" />


I hope that will help...

20 July 2014

301 Redirect Handling

In previous post we saw that we could create a Custom Field and store values we wanted in it. In our case we took a simple example to store data for a 301 redirect. We saw the use of a button on the field to regenerate the data whenever we wanted... Also for that example we chose to store the data in Json format. This was totally arbitratory and you can choose to store your data in whatever format you need. Well let's try to finish the code to see how we can integrate that plus pipeline action (like here) to handle the 301 redirects.

1- We need to define our templates

We will need 3 templates
  • The Redirection Status: 301, 302... this will help us to define the status code and the status text to pass to the Response. The template will looks like:


  • The Domain Redirection Item: This item will store the Json list objects as per our previous post as well as the domain we want the redirection to be applicable. Here we are thinking about 301 for multi-sites. Indeed, if your sitecore instance has multiple sites and you are setting up a redirection for /applications, this might be a valid  redirection for Site 1 but /applications could exist on Site 2... So better to plan for it. Now, when we had a look at the Domain Dictionary, we saw that in the configuration of your site you will have the attribute "dictionaryDomain". well we will use the same attribute to find our site domain for redirection. The template should look like the following (note the second field is our custom field):

  • Finally we will need a Redirect Item template. This template will have 3 fields: the requested URL (single line text), Redirect To: droptree that can point to a media or content item... and Status Code: droptree pointing at our Redirection Status folder as per above image...\

 2- Custom Field

well we already saw that part... under our "Redirections", we will have our Domain Item that will store the domain and the Json Data... Under this domain item, we will define all the redirection items.
Our Custom Field, will have a button which will trigger an action to get all the children and build the Json List of objects... You can follow this post to setup the field.

So we now that the data we are going to use on the HTTP Request Begin pipeline is stored on the domain item, so theoretically, we just need to retrieve this item, get the data, deserialise and get the relevant redirect if any... then proceed with redirection

3- Configuration

Easy part, we just need a processor on the HTTPRequestBegin Piepline:

      < httpRequestBegin>
        < processor type="MySite.Business.Sitecore.Pipelines.PermanentRedirect, MySite.Business" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
      < /httpRequestBegin>

4- Code

Now we have our data, we have our pipeline configure, let's intercept the request and handle some 301 shall we?
Create your class PermanentRedirect based on the HttpReqiestProcessor...

namespace MySite.Business.Sitecore.Pipelines
{
    /// 
    /// This processor will check for 301 redirects
    /// 
    public class PermanentRedirect : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (Context.Database == null || args.Url.ItemPath.Length == 0 || Context.Site == null)
                return;
        }
    }
}

Now let's think about what we will need:

we will need a method to set the Response: status, status code, header for the location to redirect to...
So let's create that method. Don't forget, you can modify the response from the Args.Context:

        /// 
        /// Set the Header for the reponse
        /// 
        /// 
        /// 
        /// 
        /// 
        private void SetResponse(string redirectTo, string status, int statusCode, HttpRequestArgs args)
        {
            args.Context.Response.Status = status;
            args.Context.Response.StatusCode = statusCode;
            args.Context.Response.AddHeader("Location", redirectTo);
            args.Context.Response.End();
        }

The next thing we will need is a URL resolver. If you remember on our Json object we will store the RedirectTo value as Sitecore ID (to either a content or a media). So we would need a method to get the item and get either the content URL or a media URL if it is pointing at a document... Notes, that we could have tried to store the nice URL on the Json object but the jSon object is generate from the Custom Field button which would have the Sitecore Context Site as Shell so you would have to pass your website context as UrlOptions. Also you want to check that the item you are redirecting to exsits in your delivery site so it is always better to resolve this item on the pipeline action...

        /// 
        /// This will resolve either the media URL or the Content URL
        /// 
        /// 
        /// 
        private string GetSitecoreUrl(Item redirectToItem)
        {
            string mediaLibraryPath = Settings.GetSetting(SitecoreSettings.MediaPath).ToLower();

            // if the item is not a media item
            if (!redirectToItem.Paths.Path.StartsWith(mediaLibraryPath))
            {
                return LinkManager.GetItemUrl(redirectToItem);
            }

            // media item
            var mediaItem = (MediaItem)redirectToItem;
            var mediaUrl = MediaManager.GetMediaUrl(mediaItem);
            return StringUtil.EnsurePrefix('/', mediaUrl);

        }


So now we have our methods, we can start with the main business logic. The first thing we want is to get the root item for our redirection. This root item is the parent of all the Domain Redirection Items. This will allow us to go retrieve all Domain Redirection Items, and select only the ones matching our context.Site.DictionaryDomain:

            String domain = Context.Site.DictionaryDomain;
            if (string.IsNullOrEmpty(domain))
                return;

            // Root for the redirect folder
            Item rootFolder = Context.Database.GetItem(new SC.Data.ID(ItemId.System.Redirection.RedirectionRootFolder));
            if (rootFolder == null)
                return;

            if(!rootFolder.HasChildren)
                return;

            var validDomainItems = rootFolder.Children.Where(x => x.TemplateID.ToGuid() == new Guid(TemplateIds.System.RedirectionDomain)
                                                && x[FieldIds.System.RedirectionDomain.SiteDomain].ToLower() == domain.ToLower());


            if (!validDomainItems.Any())
                return;


Finally we can now loop through all the valid Domain Redirect Item, Get the Json Data field, Deserialize and find the relevant item to redirect to:

                    List redirectItems = (List)Newtonsoft.Json.JsonConvert.DeserializeObject(dataField, typeof(List));
                    if(redirectItems.Any(x=> x.redirectFrom.ToLower().Contains(requestedPath.ToLower())))
                    {
                        RedirectItem item = redirectItems.First(x =>  x.redirectFrom.ToLower().Contains(requestedPath.ToLower()));

                        // Only get the URL at this point to ensure permission, site context...
                        var redirectToItem = SC.Context.Database.GetItem(item.redirectTo);
                        if (redirectToItem == null)
                            continue;

                        string redirectToUrl = this.GetSitecoreUrl(redirectToItem);

                        SetResponse(redirectToUrl, item.status, item.statusCode, args);
                        return;
                    }



So all together, that could looks like something:
namespace MySite.Business.Sitecore.Pipelines
{
    /// 
    /// This processor will check for 301 redirects
    /// 
    public class PermanentRedirect : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (Context.Database == null || args.Url.ItemPath.Length == 0 || Context.Site == null)
                return;

            if (!Settings.GetSetting(SitecoreSettings.ValidSites).ToLower().Contains(Context.Site.Name))
                return;

            String domain = Context.Site.DictionaryDomain;
            if (string.IsNullOrEmpty(domain))
                return;

            // Root for the redirect folder
            Item rootFolder = Context.Database.GetItem(new SC.Data.ID(ItemId.System.Redirection.RedirectionRootFolder));
            if (rootFolder == null)
                return;

            if(!rootFolder.HasChildren)
                return;

            var validDomainItems = rootFolder.Children.Where(x => x.TemplateID.ToGuid() == new Guid(TemplateIds.System.RedirectionDomain)
                                                && x[FieldIds.System.RedirectionDomain.SiteDomain].ToLower() == domain.ToLower());


            if (!validDomainItems.Any())
                return;

            // get the actual request
            var requestedUrl = HttpContext.Current.Request.Url.ToString();
            var requestedPath = HttpContext.Current.Request.Url.AbsolutePath;

            // Get XML from the field... 
            foreach (var redirectDomainItem in validDomainItems)
            {
                string dataField = redirectDomainItem[FieldIds.System.RedirectionDomain.RedirectionData];
                if (string.IsNullOrEmpty(dataField))
                    continue;

                try
                {
                    //Deserialize the List
                    List redirectItems = (List)Newtonsoft.Json.JsonConvert.DeserializeObject(dataField, typeof(List));
                    if(redirectItems.Any(x=> x.redirectFrom.ToLower().Contains(requestedPath.ToLower())))
                    {
                        RedirectItem item = redirectItems.First(x =>  x.redirectFrom.ToLower().Contains(requestedPath.ToLower()));

                        // Only get the URL at this point to ensure permission, site context...
                        var redirectToItem = SC.Context.Database.GetItem(item.redirectTo);
                        if (redirectToItem == null)
                            continue;

                        string redirectToUrl = this.GetSitecoreUrl(redirectToItem);

                        SetResponse(redirectToUrl, item.status, item.statusCode, args);
                        return;
                    }
                }
                catch (Exception ex)
                {
                    SC.Diagnostics.Log.Warn("Could not deserialize the Json for redirection Object - ex: " + ex.StackTrace , this);
                }
 
            }
        }

        /// 
        /// Set the Header for the reponse
        /// 
        /// 
        /// 
        /// 
        /// 
        private void SetResponse(string redirectTo, string status, int statusCode, HttpRequestArgs args)
        {
            args.Context.Response.Status = status;
            args.Context.Response.StatusCode = statusCode;
            args.Context.Response.AddHeader("Location", redirectTo);
            args.Context.Response.End();
        }

        /// 
        /// This will resolve either the media URL or the Content URL
        /// 
        /// 
        /// 
        private string GetSitecoreUrl(Item redirectToItem)
        {
            string mediaLibraryPath = Settings.GetSetting(SitecoreSettings.MediaPath).ToLower();

            // if the item is not a media item
            if (!redirectToItem.Paths.Path.StartsWith(mediaLibraryPath))
            {
                return LinkManager.GetItemUrl(redirectToItem).Replace("/site content", "");
            }

            // media item
            var mediaItem = (MediaItem)redirectToItem;
            var mediaUrl = MediaManager.GetMediaUrl(mediaItem);
            return StringUtil.EnsurePrefix('/', mediaUrl);

        }
    }
}



You can now try to call your redirect URL and see the redirection happening...

7 July 2014

Custom FIeld


Today I just wanted to show how you can create a custom field. Even if you will not really use this everyday. I found it quite handy from time to time. You can use custom fields for lots of reason: getting data from third party (Youtube, Google Map...) and store it in the field. You can also use it to display your field value in a certain way: preview your video... I guess it all depends on where your imagination can take you.

So on this example, I did not wanted to tackle something too tricky and connecting to third party... So what I was thinking about doing something like having this field to store a list of Json Object that could be used for a 301 redirect... Let say for example that you want to create a 301 redirect system that would look like:


Ok, so obviously when you will create your pipeline action to intercept the HttpRequestBegin to resolve the 301, you could just loop through all the items and find the item relevant to your request. Well, it is totally possible. Also considering a full site rebuild with a large amount of pages you would need to loop through 1000 links (I made up the number...). Anyway for our purpose today, the approach we will take is to define the list of JSon objects that will represent all our redirect items and store it on the parent item in our custom field. In our pipeline action we will then need to get the field value and deserialize the data and find the relevant redirect item. Here is how the field would appear


Well as you can image, this list will not be updated unless a content editor add a new item... That is why in that case a custom field is quite handy... especially with a nice little button at the top to regenerate the data when you will need to.

1- Defining the field type.

 To define the field type, you need to go to the CORE DB in the desktop. Under the System folder, you need to locate the item: Field Type. Create a new folder underneath which you can call Custom Field Types.
Under this newly created folder, you can create an Item from template Template field type. You can see the value to the control field: myfields:JsonField


2- Defining the menu item: button appearing at the top of the field

To define the menu item, you need to create a new folder under the field type you just created. The template to use is /Common/Folder. Under this folder, you will need to create a anew item from the template Menu item. The Display name field will be used for the text that will be displayed on the button. Also note the value placed on the Message field: myfields:regeneratejson.



3- Configurations

As you may have noticed we created some reference to controls: myfields:...
We now need to map it to the code we will write. So create a new config file in the Include folder. In this configuration file, we are going to define the Control to point it to a .net class that we will create.

    < controlsources>
      < source assembly="MySite.Business" mode="on" namespace="MySite.Business.Sitecore.Fields" patch:after="source[@namespace='ComponentArt.Web.UI']" prefix="myfields" />
    < /controlsources>


As you can see the myfields prefix will be mapping to the Namespace MySite.Business.Sitecore.Fields. Now, I am sure you are wondering where is the class defined. Well "myfields:JsonField", your .NET class will need to be called JsonField.

4- our code

OK, now we have the config in place, we can create our class... Namespace is defined above and the class name: JsonField. The field will be quite simple: we just need a free text on which will store Text... If you look into the namespace: Sitecore.Shell.Applications.ContentEditor you will some base control that you can base your control on. In our case we will choose to Sitecore.Shell.Applications.ContentEditor.Text as base type. Our first draft of the code will be similar to

namespace MySite.Business.Sitecore.Fields
{
    public class JsonField : SC.Shell.Applications.ContentEditor.Text
    {
        protected override bool LoadPostData(string value)
        {
            value = SC.StringUtil.GetString(new string[1]
            {
                value
            });

            if (this.Value == value)
                return false;

            this.Value = value;

            return true;
        }
    }
}


you can verify that you field display as a text box and the value you place in the field will be saved correctly.

5- Button action

The button action. Well in the definition of the menu item (button), we defined the Message field: myfields:regeneratejson
If you decompile the code for the base control: Sitecore.Shell.Applications.ContentEditor.Text, you will see that there are a few method you can override. One of them is the HandleMessage. This will allow you to intercept the regeneratejson coming from the button and redirect your code to your own method to hande the action:

        public override void HandleMessage(Message message)
        {
            base.HandleMessage(message);
            switch (message.Name)
            {
                case "myfields:regeneratejson":
                    this.RegenerateData();
                    break;
            }
        }


In our case, you can note that we wanted to loop through all children and serialised the data to build our Json list of redirect item... So first, we need to ensure we can access the actual current Item. This can be done as per the following:

        public string ItemID
        {
            get
            {
                return base.GetViewStateString("ItemID");
            }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                base.SetViewStateString("ItemID", value);
            }
        }
Now we have the item we can build our method to loop through children and build our serialisation. Here is the full code:

namespace MySite.Business.Sitecore.Fields
{
    public class JsonField : SC.Shell.Applications.ContentEditor.Text
    {
        public string ItemID
        {
            get
            {
                return base.GetViewStateString("ItemID");
            }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                base.SetViewStateString("ItemID", value);
            }
        }

        public override void HandleMessage(Message message)
        {
            base.HandleMessage(message);
            switch (message.Name)
            {
                case "myfields:regeneratejson":
                    this.RegenerateData();
                    break;
            }
        }

        protected void RegenerateData()
        {
            var masterDB = SC.Data.Database.GetDatabase("master");
            if (masterDB == null)
            {
                SheerResponse.ShowError("Could not get master DB", "Error: Could not retrieve the Master Database");
            }

            var item = masterDB.GetItem(ItemID);
            if (item == null)
            {
                SheerResponse.ShowError("Could not resolve the current Item", "Error: Could not resolve the current Item");
            }

            if(!item.HasChildren)
                return;

            List list = new List();
            foreach (var child in item.Children)
            {
                RedirectItem rItem = new RedirectItem((Item)child);
                if (rItem.isValid)
                {
                    list.Add(rItem);
                }
            }

            string serialization = Newtonsoft.Json.JsonConvert.SerializeObject(list);

            this.Value = serialization;
        }

        protected override bool LoadPostData(string value)
        {
            value = SC.StringUtil.GetString(new string[1]
            {
                value
            });

            if (this.Value == value)
                return false;

            this.Value = value;

            return true;
        }
    }
}


You can now try to view the field and click on the button:


3 July 2014

SPIF error when synchronising documents


In a recent project, we had a Sitecore/Sharepoint integration task. After looking at the different options, we have decided to use the SPIF. Looking at the documentation, that would definitely do what we needed and provided even more API possibilities.

So we started the implementation and everything looked quite nice:
  • Connecting to the Sharepoint server
  • Create the Sharepoint connector item in Sitecore
  • And even retrieving the documents.
Unfortunately, we encountered an error:

For one of the library we were trying to sync, none of the document were getting into sitecore. After looking at the log file  we had the following exception:

ManagedPoolThread #10 00:00:00 ERROR Sharepoint Provider can't process tree.
Integration config item ID: {23C8604D-6761-4FD8-B3AD-A0E3BB880AD8}, Web:http://sharepoint.test.com/ List: {7C8A645E-A085-46DE-A059-168B8F480641}
Exception: System.InvalidOperationException
Message: There is an error in XML document (44, 2760).
Source: System.Xml
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
   at Sitecore.Sharepoint.Data.WebServices.SharepointLists.Lists.GetListItems(String listName, String viewName, XmlNode query, XmlNode viewFields, String rowLimit, XmlNode queryOptions, String webID)
   at Sitecore.Sharepoint.ObjectModel.Connectors.ItemCollectionConnector.GetItems(BaseList list, ItemsRetrievingOptions options)
   at Sitecore.Sharepoint.ObjectModel.Entities.Collections.ItemCollection.GetEntities()
   at Sitecore.Sharepoint.Data.Providers.SharepointProvider.ProcessTree(ProcessIntegrationItemsOptions processIntegrationItemsOptions, SynchContext synchContext)
   at Sitecore.Sharepoint.Data.Providers.SharepointProvider.ProcessTree(ProcessIntegrationItemsOptions processIntegrationItemsOptions, Item integrationConfigDataSource)
 
Nested Exception
 
Exception: System.Xml.XmlException
Message: '[1]', hexadecimal value 0x02, is an invalid character. Line 44, position 2760.
Source: System.Xml
   at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
   at System.Xml.XmlTextReaderImpl.ParseNumericCharRefInline(Int32 startPos, Boolean expand, StringBuilder internalSubsetBuilder, Int32& charCount, EntityType& entityType)
   at System.Xml.XmlTextReaderImpl.ParseNumericCharRef(Boolean expand, StringBuilder internalSubsetBuilder, EntityType& entityType)
   at System.Xml.XmlTextReaderImpl.HandleEntityReference(Boolean isInAttributeValue, EntityExpandType expandType, Int32& charRefEndPos)
   at System.Xml.XmlTextReaderImpl.ParseAttributeValueSlow(Int32 curPos, Char quoteChar, NodeData attr)
   at System.Xml.XmlTextReaderImpl.ParseAttributes()
   at System.Xml.XmlTextReaderImpl.ParseElement()
   at System.Xml.XmlTextReaderImpl.ParseElementContent()
   at System.Xml.XmlLoader.LoadNode(Boolean skipOverWhitespace)
   at System.Xml.XmlLoader.ReadCurrentNode(XmlDocument doc, XmlReader reader)
   at System.Xml.XmlDocument.ReadNode(XmlReader reader)
   at System.Xml.Serialization.XmlSerializationReader.ReadXmlNode(Boolean wrapped)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderLists.Read30_GetListItemsResponse()
   at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer53.Deserialize(XmlSerializationReader reader)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)

That kept us going for days. Sitecore support and Sitecore team help us debugging the issue and provided us with a fix. Basically what happen is, there are some invalid chars (for XML parsing) on the documents we are trying to retrieve from Sharepoint. This was causing the XML parsing to fail and none of the documents were getting created in Sitecore. You can find the issue with the resolution in the knowledge base item.

Solution: 1- download the dll patch from the knowledge base item 2- Modify the web.config file by adding the following configuration into the < system.web> section:
< webservices> 
  < soapextensiontypes> 
    < add group="0" priority="1" type="Sitecore.Support.SharePoint.XmlCleanupSoapExtension, Sitecore.Support.411647" /> 
  < /soapextensiontypes> 
< /webservices>

14 June 2014

Splash Page

I had an interesting query today. The query was for the possibility to have a Splash Page while the website was not announced. The functionality will be to either only redirect the Homepage to the Splash page so the http://mydomain.com will go to the SPlash Page. This would allow a content author to get the rest of the site reviewed for content and get it ready for go live. The second part of the functionality would be to hide the entire content behind the Splash Page until go live.

So here is the approach: we will use the HttpRequestBegin Pipeline to intercept the request and check on if the Splash Page is Enable and is it for the entire Site...

1 Starting with our Pipeline action

First you want to avoid applying the action to certain page and site. If the current Site is not your website then exit the action. In a similar matter, you do not want to execute the action if your request is the Splash page or you will end up with an infinite loop.
            if (Context.Item.TemplateID.ToGuid() == new Guid(TemplateIds.Pages.SplashPage))
                return;

            if (Context.Site.Name.ToLower() != Settings.GetSetting(SitecoreSettings.SiteName).ToLower())
                return;

Next, you want to resolve your homepage, site root... By the way for this exercise, the Splash Page Settings are defined in the Root Item...
            string rootPath = Context.Site.RootPath;
            var rootItem = Context.Database.GetItem(rootPath);
            if (rootItem == null)
                return;

            string homePath = string.Format("{0}/{1}", Context.Site.RootPath, Context.Site.StartItem);
            var homepage = Context.Database.GetItem(homePath);
            if (homepage == null)
                return;

Then, once you have the root item let's get the properties for the Splash Page: Enable, Splash Page Target, Enable for Entire Site
            string enableSplashPage = rootItem[FieldIds.Base.SplashPage.EnableSplashPage];
            string enableSplashPageEntireSite = rootItem[FieldIds.Base.SplashPage.EnableForEntireSite];
            string splashPage = rootItem[FieldIds.Base.SplashPage.PageLink];

Then the rest is quite simple:
namespace MyProject.Business.Sitecore.Pipelines
{
    /// 
    /// This processor will check if the current item has a Redirect field and setup correctly
    /// Redirect the user to the redirect link setup...
    /// 
    public class SplashPageRedirect : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (Context.Item == null || Context.Database == null || args.Url.ItemPath.Length == 0 || Context.Site == null)
                return;

            if (Context.Item.TemplateID.ToGuid() == new Guid(TemplateIds.Pages.SplashPage))
                return;

            if (Context.Site.Name.ToLower() != Settings.GetSetting(SitecoreSettings.SiteName).ToLower())
                return;

            string rootPath = Context.Site.RootPath;
            var rootItem = Context.Database.GetItem(rootPath);
            if (rootItem == null)
                return;

            string homePath = string.Format("{0}/{1}", Context.Site.RootPath, Context.Site.StartItem);
            var homepage = Context.Database.GetItem(homePath);
            if (homepage == null)
                return;

            Profiler.StartOperation("Resolve splash page Redirect item.");

            string enableSplashPage = rootItem[FieldIds.Base.SplashPage.EnableSplashPage];
            string enableSplashPageEntireSite = rootItem[FieldIds.Base.SplashPage.EnableForEntireSite];
            string splashPage = rootItem[FieldIds.Base.SplashPage.PageLink];

            if (enableSplashPage != "1")
            {
                Profiler.EndOperation();
                return;
            }

            if (!SC.Data.ID.IsID(splashPage))
            {
                Profiler.EndOperation();
                return;
            }

            if (homepage.ID.Guid == Context.Item.ID.Guid || enableSplashPageEntireSite == "1")
            {
                var newItem = Context.Database.GetItem(new SC.Data.ID(splashPage));
                if (newItem == null)
                {
                    Profiler.EndOperation();
                    return;
                }

                Context.Item = newItem;
                Profiler.EndOperation();
                return;
            }
        }
    }
}



Now we have our Pipeline action, lets hook it up with the configuration file:
      < httprequestbegin>
        < processor patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" type="MyProject.Business.Sitecore.Pipelines.SplashPageRedirect, MyProject.Business" />
      < /httprequestbegin>


Finally, just to give the idea of the fields on the Root Item: