Wednesday 27 September 2017

Sitecore Powershell - New-Item with specific ID (ForceId not working)

I am writing some Sitecore Powershell scripts to migrate content from an old site to a new re-designed site with different set of templates.

As you would imagine, we should keep the old item IDs instead of creating new IDs in case we also want to maintain the references between the items (if any). If you have used the New-Item cmdlet before, you might have noticed that it supports a parameter: -ForceId.

I noticed that most of the items I created with the ForceId parameter set are having the same ID as the original item however some of them are not. And then I realised that those items were created using a branch template.

While Sitecore PSE does use the ItemManager.AddFromTemplate() method that has several overloads to support creating item with a specific ID, it's logic only works for normal template and not for the branch template. In the current version (4.6) of PSE, it has logic below (shortened to show the point):

// iD is the ID passed to the ForceId parameter

if (type != TemplateItemType.Template)
{
    // Logic to check if it is branch template removed for simplicity
    newItem = parentItem.Add(name, fromPath);
}
else
{
    newItem = ((iD != ID.Null) ? ItemManager.AddFromTemplate(name, fromPath.ID, item, iD) : ItemManager.AddFromTemplate(name, fromPath.ID, item));
}
As you can see from above code, when it is using branch template, it does not actually use the ID we passed to the ForceId parameter.

To make it work, I have written a function to help, lets call it New-ItemCustom:



function New-ItemCustom
{
    <#
        .SYNOPSIS
            Create a new Sitecore item based on template/branch ID. Specify
            the NewItemID to create the item with the specific ID.
    #>
    [CmdletBinding()]
    param 
    (
        [Parameter(Mandatory=$True)]
        $Name,
        [Parameter(Mandatory=$True)]
        [string]$TemplateID,
        [Parameter(Mandatory=$True)]
        $ParentItem,
        [string]$NewItemID = ""
    )
    
    $scTemplateID = New-Object -TypeName "Sitecore.Data.ID" -ArgumentList $TemplateID
    $newItem = $null
    
    if ($NewItemID -ne "") 
    {
        $scItemID = New-Object -TypeName "Sitecore.Data.ID" -ArgumentList $NewItemID
        $newItem = [Sitecore.Data.Managers.ItemManager]::AddFromTemplate($Name, $scTemplateID, $ParentItem, $scItemID)
    }
    else 
    {
        $newItem = [Sitecore.Data.Managers.ItemManager]::AddFromTemplate($Name, $scTemplateID, $ParentItem)
    }
    
    return $newItem
}


Usage:


New-ItemCustom -Name "new item name" -TemplateID "template/branch ID" -ParentItem (Get-Item -Path: .) -NewItemID "Sitecore Item ID here"



Hope this helps.

Wednesday 30 August 2017

Sitecore FXM, Profile and Pattern Matching

I was playing around with FXM and I was able to create placeholders in the non-Sitecore site and put Sitecore content into those placeholders, which is cool!

However it was not so lucky when setting up profiles at the beginning. For some reason (still unknown) the profile (and its keys, profile/pattern cards) I set up the first time did not with with the page filters and nothing got recorded in MongoDB's intractions collection, and of course in Experience Analytics only the visits are shown.

I then wrote a custom pipeline to loop through the profile cards associated with the matched filter and manually score them. This worked, the profile and the profile keys/values were recorded against the interaction however no pattern was recorded.

After studying the code, I realised that what I did in my custom pipeline was already catered for out-of-the-box so I was confused and consulted Sitecore support and they also could not figure out why so I was suggested to create another profile from scratch and remove the custom pipeline.

Surprisingly this worked! I could see profile values as well as pattern card recorded under the interaction and the matched pattern shows in Experience Analytics.

However soon I noticed that there is nothing displayed in Experience Profile. I used to see bunch anonymous records there in another version of Sitecore 8 but I cannot see anything here. Why?

I am using Sitecore 8.2 Update 4 and if you are not as careless as me and are paying attention to the release notes, you would notice that in the release notes of Sitecore 8.2 Update 3 there is a change to how the anonymous contacts are indexed:

    
<settings>

  
  <setting name="ContentSearch.Analytics.IndexAnonymousContacts" value="false"/>
</settings>
Note the comments and the default value was changed from true (prior to 8.2.3) to false. For sites that do not have WFFM (that identifies the contact when needed) nor custom contact identification process you might prefer this to be turned on in order to display the behaviour of the anonymous users. However if you do have the mechanism to identify the contact then this is suggested to be turned off to reduce impact on performance.

So by changing the value to true and make some interactions with the site, you will see the anonymous contacts listed in Experience Profile. Note that this only works for new intractions; if you want to see old data (before this was set to true) you will need to rebuild your reporting database.

I thought I would just blog this in case someone was struggling with this too!

Wednesday 16 August 2017

Sitecore Tracking Field Parsing - the Native Way

When we assign profile cards, goals or attributes (outcomes, events, etc) to a page content item, Sitecore stores the information in the Tracking (actual name __tracking) field under the Advanced section, as XML which looks like below (I am using the Sitecore Helix Utilities demo site for example):


Now if we switch to the raw value, we will see it is stored in XML, which I have formatted it for ease of viewing:
  
<tracking>
   <profile id="{D016ADEF-7737-4289-A85F-FE0055F49C8E}" name="Utilities Persona" presets="evan the eco-owner|100||">
      <key name="Sign-up for service" value="1" />
      <key name="Solar_Enviro Programs" value="5" />
      <key name="Budget_Savings Offers" value="4" />
      <key name="H of H_Community" value="3" />
   </profile>
</tracking>

There might be some cases where you want to parse this and get the information out of it and I have seen many developers parsing it by loading the value into XmlDocument and then read its nodes. This works fine however we would imagine Sitecore should have already had something doing this?

Answer is yes of course :) So, we can use the following code to get a strongly-typed object that represents this information.

// using Sitecore.Analytics.Data;

var pageItem = Sitecore.Context.Item;
var trackingField = new TrackingField(pageItem.Fields["__Tracking"]);

// Loop through all profiles (if multiple profile cards are assigned)
foreach (var profile in trackingField.Profiles)
{
    // Loop through all profile keys for current profile.
    // Here we want only the ones that has value greater than 0, for example.
    foreach (var k in profile.Keys.Where(o=> o.Value > 0))
    {
        // Do something
    }
}

The above example shows how to read profiles. If you have stuff like campaigns, goals, events that you need, in the TrackingField class there are collections for them as well:


Note that goals are same as events so you will find it under the Events IEnumerable.

Hope this helps!

Friday 30 June 2017

Find MongoDB contact and interactions using SC_ANALYTICS_GLOBAL_COOKIE

These days I was working on resolving some issues with Sitecore Analytics and need to find the corresponding contact stored in the Contacts collection database in MongoDB easily.

[Update] 15 Aug 2017: added query output for finding all interactions with provided contact ID.

Sitecore generates the analytics cookie value (in GUID) and stores it in the SC_ANALYTICS_GLOBAL_COOKIE cookie which looks like 95f08e09d56348c9bc285080e91be04b|False. That letters/digits combination is a short, lowered version of GUID which is an important identifier for the contact record that gets stored in MongoDB.

However when you try to find this in MongoDB (using Robomongo which is now Robo 3T, make sure to check the latest version), you will not find it because some parts of the GUID are shifted (i.e. first 8 characters, followed by next two groups of 4 letters).

To make things easier, you could run a C# Program query in LINQPad by pasting the below code in, or visit my fiddle page.

    
    void Main()
    {
       // Value from SC_ANALYTICS_GLOBAL_COOKIE (you can copy the |True or |False part which will be trimed too)
       var guidFromCookie = "95f08e09d56348c9bc285080e91be04b|False".Split(new char[] {'|'})[0]; 
       var mongoDbLUUID = string.Format("{0}-{1}-{2}-{3}-{4}",
                              Flip(guidFromCookie.Substring(0,8)), 
                              Flip(guidFromCookie.Substring(8,4)), 
                              Flip(guidFromCookie.Substring(12,4)),
                              guidFromCookie.Substring(16, 4),
                              guidFromCookie.Substring(20,12));
  
       Console.WriteLine("MongoDB query:");
       Console.WriteLine("db.Contacts.find({" + string.Format("_id:LUUID('{0}')", mongoDbLUUID) + "})");
       Console.WriteLine("All interactions with this contact:");
       Console.WriteLine("db.Interactions.find({" + string.Format("ContactId:LUUID('{0}')", mongoDbLUUID) + "})");
    }

    string Flip(string s)
    {
       var output = "";
       for (var i=s.Length-2; i>=0; i-=2)
       {
           output += s.Substring(i, 2);
       }
 
       return output;
    }

The code will output a query which you can paste directly to Robo 3T.

Thursday 11 May 2017

CSS rule that could break your Sitecore 8.2 Experience Editor

Today when I was using the Experience Editor on my Sitecore 8.2 Update 3 site and I found that nothing of the following works:

  • Assigning renderings to placeholders
  • Some of the buttons in the ribbon does nothing when clicked
  • Etc, etc
I have checked Chrome console logs... no javascript errors, no 500 or 404 under network tab. Checked Sitecore logs... no exceptions were thrown.

What could it be? I have started scratching my head...

Checking here, checking there, asking my colleague and starting removing whatever is on the page one by one.

At the end we figured out that there is one CSS rule:

body > iframe { display: none;}

We removed it and the Experience Editor starts working again. Why?

This rule effectively hide any iframes that are directly under the body tag, whether they are created dynamically by external javascripts or by yourself. Unfortunately Sitecore Experience Editor also has an iframe directly under body for serving the modal dialog. Therefore when this rule is present, the functionality behind the buttons (that pop up a modal dialog) actually works but the dialog is not showing.

We have only tested this on Sitecore 8.2 so not sure if it affects earlier 8.x versions. The iframe used by Sitecore looks like:

  
  <iframe allowtransparency="true" 
         frameborder="0" 
         id="jqueryModalDialogsFrame" 
         src="/sitecore/shell/Controls/JqueryModalDialogs.html">
      ...
  </iframe>

So once we know this, we can get around this by excluding this from the CSS rule:

body > iframe:not(#jqueryModalDialogsFrame) { display: none;}
Hopefully this helps!

Monday 1 May 2017

Sitecore Habitat and Fortis

If you don't know what is Habitat yet, please have a look at its Github: https://github.com/Sitecore/Habitat.

By default, Habitat does not use any mapping tool (such as Glass Mapper, Fortis, or the emerging Synthesis) so keep it simple however it does not mean that Sitecore doesn't prefer using one of those.

I have created a fork of the original Habitat solution and made it Fortis-enabled. A Fortis.Foundation module is added to the foundation layer which is responsible for the registration of Fortis and the code-generation of the Fortis model based on Unicorn.

You can find the repo here: https://github.com/codingdennis/Habitat/tree/feature/fortis-integration.

[Updated 2017-05-03] I have also updated this Habitat to support Sitecore 8.2 Update 3, check https://github.com/codingdennis/Habitat/tree/feature/fortis-integration-sc82u3 if required.

Happy Habitating!

Wednesday 29 March 2017

Sitecore SIM 1.5

There were heaps of development work undergoing since the last release 1.4 Update-3 about a year ago. Alen is in the process of merging of everything from develop to master and we should expect version 1.5 to be released soon which I was lucky enough to also contribute a small portion to it, which allows prefixing the SQL database names so multiple databases could be created for different instances.

Keep an eye on the SIM's GitHub for any updates. You can also find it in Sitecore Marketplace.

Thursday 23 February 2017

Script to flush Sitecore xDB

Many people have been talking about how to flush current visit to xDB without playing around the session timeout. Here is just a script ready for use when I need it.

<%@ Page Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Flush xDB</title>
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Sitecore.Analytics.Tracker.Current != null)
            {
                Sitecore.Analytics.Tracker.Current.EndTracking();
                Session.Abandon();

                Response.Write("xDB flushed.");
            }
        }
    </script>
</head>
<body></body>
</html>

Save as a ASPX page and put it somewhere on the site and browse to it to flush current visit into xDB.

Wednesday 22 February 2017

Sitecore ShowConfig on CD servers

I have to say that this is not recommended, not recommended, not recommended... and you are doing this on your own risk.

So, what is it? We have followed all the recommendation on disabling stuff on CD servers but I am sure sometimes we want to see the famous showconfig results on CD servers so we can investigate the configurations only for CD servers. Because CD should not have Sitecore client and the admin pages, so we have removed them. Then how?

Sounds evil? Yes it is so risk is yours :)

When Sitecore does this in CM server, the showconfig.aspx checks if you are logged in. We cannot do that on CD so we just need to create the same page with code that does not do the security checking and put this, say, EvilShowConfig.aspx somewhere on CD server and remove it as soon as you have collected the configurations.

Save the following content into a text file named EvilShowConfig.aspx and put the file on CD at the location of your choice and browser to it.

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import NameSpace="Sitecore.Configuration" %>
<%@ Import NameSpace="System.Xml" %>
<html>
    <head>
        <title>Very Evil ShowConfig</title>
        <script runat="server">
            protected void Page_Load(object sender, EventArgs e)
            {
                XmlDocument configuration = Factory.GetConfiguration();
                Response.Clear();
                Response.ContentType = "text/xml";
                Response.Write(configuration.OuterXml);
                Response.End();
            }
        </script>
    </head>
    <body></body>
</html>

Tuesday 14 February 2017

Sitecore.ExperienceExplorer.Business.Pipelines.HttpRequest.EnableExperienceModePipeline.Process exception

When building a Sitecore site sometimes you might suddenly see the following exception thrown as soon as you browse to the site:

[NullReferenceException: Object reference not set to an instance of an object.]
   Sitecore.ExperienceExplorer.Business.Pipelines.HttpRequest.EnableExperienceModePipeline.Process(HttpRequestArgs args) +950
   (Object , Object[] ) +73
   Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args) +483
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain) +21
   Sitecore.Nexus.Web.HttpModule.’ (Object  , EventArgs  ) +531
   System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +141
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +91


I discovered this while playing around the Sitecore demo sites that are built on top of Habitat Demo. This issue should only happen on multisite scenario where the Sitecore setting Preview.DefaultSite is set to a site that does not exist.

In my case, I have removed the habitat site and by default the Habitat Demo sets the Preview.DefaultSite value to habitat. Since I do not have it anymore the exception is thrown.

To fix the issue, patch the value of the setting to one of your site's name (which one is up to you).

Hope this helps!

Thursday 9 February 2017

Salesforce Web2Lead Endpoint Change and TLS 1.0

One of my client has issue with utilizing the Salesforce Web2Lead service whether the code no longer creates lead in Salesforce. After looking into it further, there are actually couple of issues.

Firstly I guess most Salesforce solution provider should have already been notified by Salesforce that to provide better service, Salesforce is planning to change the service endpoints for Web-to-Case and Web-to-Lead, as mentioned in the following knowledge base article:

https://help.salesforce.com/articleView?eid=ss-tc&id=Updating-the-Web-to-Case-and-Web-to-Lead-Endpoint-URL&language=en_US&type=1

So all the code/configuration that refers to the old endpoints such as:

  • https://www.salesforce.com/servlet/servlet.WebToLead, or
  • https://www.salesforce.com/servlet/servlet.WebToCase
Will need to change to:
  • https://webto.salesforce.com/servlet/servlet.WebToLead, or
  • https://webto.salesforce.com/servlet/servlet.WebToCase
The old endpoints will no longer work after planned time from Jun 2017.

The second issue is that Salesforce has disabled support for TLS 1.0 (https://help.salesforce.com/articleView?id=000221207&type=1) therefore depending on how the integration is done, you will need to update the code or configurations accordingly.

As we are using backend .NET code (4.5.x) I have added the code to select the highest version when possible right before we call the Salesforce service using HttpClient:

    using (var client = new HttpClient())
    {
        System.Net.ServicePointManager.SecurityProtocol = 
            System.Net.SecurityProtocolType.Tls12 | 
            System.Net.SecurityProtocolType.Tls11 | 
            System.Net.SecurityProtocolType.Tls;
        // ...
    }
 
If the integration is done by using a static form on a web page (with form action) then the user's browser needs to have TLS 1.0+ support. Refer to the link above to know how to enable TLS 1.1+ on supported browsers.

Friday 6 January 2017

Sitecore Habitat Transformer

Sitecore Habitat is a open source Sitecore demo website project that implements the Helix design concepts.

The way the project is designed/developed is recommended by Sitecore these days for building a new Sitecore website. The demo site contains many builtin features and a foundation that can be used in our own project.

The immediate headache(???) when beginning using Habitat as a base of your own project is that it has many Sitecore item paths, namespaces, etc stored under Habitat (path or namespaces) and people usually do a file-based find-and-replace to replace the Habitat string with a custom string for their own project. However by doing this might cause some issues which needs to be addressed manually afterwards.

To make life easier I have created a Powershell script that does this and it can be downloaded from https://github.com/codingdennis/SitecoreHabitatTransformer.

Please see the Readme there for more information on how to use it.