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.

Thursday, 1 December 2016

Sitecore Bug - Archiving Specific Version of an Item

A colleague is trying to use the built-in Sitecore functionality to archive a specific version of the item. The way it is done is:

  1. Navigate to a content item and selected an old version of the item
  2. From the Review tab, in the ribbon, Schedule group, select the Archive Version Now menu item from the Archive dropdown.

Now the problem is that Sitecore will always select the latest version and pops up the dialog:


5 is the total number of versions I have but I want to archive version 2, which is the selected version. We have tested this in Sitecore 7.5 and 8.0 Update 3 (not tested on later versions) and they all behave the same.

The issue seems to be related to how the menu item is configured in the core database. So open Sitecore desktop and switch to core database and then in the Content Editor, navigate to the following item: 

/sitecore/content/Applications/Content Editor/Menues/Archive/Archive Version Now

Check the Message field:


The value contains the (id=$Target) parameter so it is always selecting the latest version of the item (that is clicked from content tree).

To fix the issue, just remove (id=$Target) from the field value, save.

Now go back to the master database, find the item and select version 2. Click on the Archive Version Now menu item (Review / Schedule / Archive dropdown) again and ta..la... it says:


Hit OK and the selected version is archived.

If you wish to restore the version, go to Sitecore desktop / All Applications / Archive and choose the desired item to restore.




Monday, 17 October 2016

Possible temporary solution for Windows 10 freezing after short idle

I actually cannot remember when this happened but my Windows 10 starts to freeze after I just left the computer for a very short time, probably less than 2 or 3 minutes.

Initially I thought it is my SSD where the windows is installed that has issue but sooner I noticed that as long as I keep using the computer then it does not freeze.

There are discussions, for example:

http://www.theregister.co.uk/2016/08/16/windows_10_anniversary_update_ssd_freeze/
http://www.pcworld.com/article/3107273/windows/windows-10-anniversary-update-freezing-on-you-microsofts-looking-into-it.html

More reading reveals that this could only happen to users that are booting Windows from a SSD drive (still unsure) which is my case.

And a representative from Microsoft suggested the following:
http://answers.microsoft.com/en-us/windows/forum/windows_10-performance/windows-10-may-freeze-after-installing-the/5a60d75d-120a-4502-873c-8bfec65c82d0?page=1&auth=1

To me they looks too complex and I like simple stuff so below is my temporary solution until Microsoft have a fix for it.

Copy the following code and save it in a batch file (e.g. anti-freeze.bat) on the booting SSD drive and when you think you are going to leave the computer for a while, just double click to bat file to preventing Windows from freezing.

@echo off

if "%1"=="done" goto start
start "" /min %0 done
exit

:start
cls
echo Press Ctrl + C to quit...

cmd /c start notepad
timeout /T 5 /NOBREAK
taskkill /F /IM notepad.exe
timeout /T 5 /NOBREAK

goto start

What this does is it fires a windows command prompt, make itself minimized and then open/close notepad every 5 seconds. This works for me and I have left my PC on for the whole night and it is still alive.

Let me know if it works for you too! Have a nice day.

Thursday, 6 February 2014

Annoying issue with ASP.NET ImageButton inside UpdatePanel

I personally do not like to use UpdatePanel at all however in some cases I need to maintain the code written by others. One annoying problem I encountered today is that in IE10 or IE 11 the ImageButton placed inside an UpdatePanel when clicked just does nothing and the following error is thrown in IE's error console:

Sys.WebForms.PageRequestManagerServerErrorException: Input string was not in a correct format.

I reckon this is a bug within the framework and an easy fix is just to change the code to use LinkButton instead and put the image inside.

Friday, 6 December 2013

Repeat a string N times in C#

Sometimes I want to repeat a given string several times for formatting purposes. The following code can be either made into an extension method with slight changes or a utility method:

string.Join("", Enumerable.Repeat("a", 8).ToArray());

The code above repeat "a" 8 times and returns "aaaaaaaa".

Tuesday, 22 October 2013

Hating hosts file?

Sometimes I found that it is pretty annoy to keep editing the hosts file to add a new development domain on my local machine and a colleague told me that there are several domains (and their sub-domains) that automatically loop back to 127.0.0.1 (localhost), for example, 42foo.com, localtest.me, lvh.me, etc.

I personally like localtest.me as it seems more logical to me. So now when I am developing a new site, I could use something like newsite.dev.localtest.me directly without adding an entry in hosts file.

Wednesday, 16 October 2013

Checking null for Sitecore.Data.ID

The Sitecore.Data.ID class exposes a IsNull property which the documentation says it checks whether the instance is null. However I discovered that when the ID variable is actually null it throws Object reference set to null instance of object exception.

So it is better to the following code in this case (perhaps always?):
Sitecore.Data.ID.IsNullOrEmpty(myID)

Sunday, 13 October 2013

Linq.js - where to filter on sub collections

Linq.js (you can find more info on CodePlex - http://linqjs.codeplex.com) provides convenient way manipulating your javascript collections (mainly arrays) and exposes it as LINQ that we are familiar with in .NET world.

One issue I found that it is hard to find information on how to achieve it is that when I want to apply the where clause on sub collections.

For example, given the following array:

var cars = [
  {id: "F001", make: "Ford", model: "Fiesta", 
    colors: [
      {id: 1, name: "Red"},
      {id: 2, name: "Blue"}
    ]
  },
  {id: "T001", make: "Toyota", model: "Corolla",
    colors: [
      {id: 1, name: "Red"},
      {id: 3, name: "Silver"}
    ]
  },
  {id: "T002", make: "Toyota", model: "Yaris",
    colors: [
      {id: 1, name: "Red"},
      {id: 3, name: "Silver"},
      {id: 4, name: "White"}
    ]
  }
];

It is pretty easy to find cars that are made by Toyota (note that the code examples below are using linq.js 3.x:


var toyotaCars = Enumerable.from(cars)
                           .where("c=> c.make == 'Toyota'");

And we will get Corolla and Yaris.

However, when I want to find cars that are available in blue and how do I suppose to do that?

People might have tried the following already:


var blueCars = Enumerable.from(cars)
                         .where("c=> c.colors.name == 'Blue'");

Or

    .where(function(c) {return (c.colors.name == 'Blue');})


Unfortunately this is not how it works and you will get nothing. Obviously linq.js is trying to work as close as to .NET LINQ but nothing is perfect. So to achieve what we want is the following:


var blueCars = Enumerable.from(cars)
    .where(function(o) {
      return (Enumerable.from(o.colors)
                        .where("c => c.name == 'Blue'").any());
    });

Here you go, we will get Ford Fiesta!