Sitecore 8 Content Testing via Workflow

Sitecore 8 introduces the ability to manage changes to the presentation layer via workflow. This opens up a whole new world for managing personalization and multivariate testing scenarios via native CMS workflow. Sitecore 8’s out-of-the-box Sample Workflow builds upon prior versions with the added ability to approve content in workflow with a MV test or approve and skip the test. Approving with the test will launch a preview dialog to see what the test may introduce. Approving without the test will remove the MV test from the presentation of the page. This is useful as an approver if you accept the original content but don’t feel an MV test should be published live. This blog post covers how to get started with content testing via workflow.

Versioned Layouts as the Foundation

The primary driving factor that allows content authors to now implement multivariate testing and personalization changes via workflow is the addition of versioned layouts. Prior to Sitecore 8, the presentation settings for a given content page were stored in a shared built-in "__Renderings" field. As a shared field, there is only one value across languages and numbered versions. This means a presentation layer change (e.g. adding a new component) to version 80 in the Spanish language of an item would also reflect on version 20 in the English language of the same item. Sitecore 8 maintains the existing shared "__Renderings" field but introduces a new versioned "__Final Renderings" field.

sc8-mv-workflow-1

Presentation changes that are global and apply to all languages and numbered versions are made to the existing shared field, and new language and numbered version specific changes are applied to the new versioned "__Final Renderings" field. This is where multivariate tests come in with workflow since versions can have different values.

Now that you have a background understanding of how this is possible, let’s create some variations of content that we can test with.

Create Content Variations

Before you create your test, create a few variations of content that we will test. Ensure your solution it setup to use data sources for passing content to the UI.

sc8-mv-workflow-2

Now that we have variations of content, let’s assign a test and push the changes through workflow.

Configure a Test and Submit into Workflow

Let’s now go ahead and set a presentation component to be tested and assign our content variations. In the Layout Details, select a component and click Test, then provide the variations of content. In the screenshot below I’m showing this via the Content Editor however you can also configure tests via the Experience Editor.

sc8-mv-workflow-3

 

Set the data source for the original version and create a test variation with another data source. Like personalization, you can also hide the component via the Hide Component checkbox or swap it with another via the Enable variation of component design checkbox.

sc8-mv-workflow-4

Approve, Preview and Start the Test

As an approver, let’s approve the content item with the test. This launches the Preview and start test dialog to show what the variations may do to the experience.

sc8-mv-workflow-5

NOTE: if you don’t have a MV test configured on your page prior to submitting the content item through workflow, the approval state will NOT provide Approve with Test and Approve without Test actions. Since no MV test is setup, it will simply provide an Approve action which is the equivalent of Approve without Test. So make sure you configure your MV test in the draft state (or a prior state to the “with or without test approvals”).

As you can see in the screenshot above, it will run preview mode and show an image of what each experience looks like to the visitors. If you swap or hide a component you will see those changes. If you adjust content in the component you will also see that. Sitecore will also tell you how many experiences will show on the site and the estimated number of visits required to select a winner.

When you are ready, click the Start test button to finally approve the content and start the test.

If you’re interested in reading more, Sitecore has the following references docs that may be helpful as well:

 

 

Posted in Sitecore

Sitecore Synonym Search with Lucene

Sitecore’s Content Search API comes configured with the standard analyzer by default, however its possible to configure a synonym analyzer if you need this functionality (i.e. searching for a synonym of a word in content finds that result). If you’re not sure what an analyzer is, head over to Adam Conn’s blog to understand it because its a key component of how both indexing and searching work.

Sitecore ships with its own implementation of a synonym analyzer: Sitecore.ContentSearch.LuceneProvider.Analyzers.SynonymAnalyzer. As you can see in Adam’s post referenced above, this analyzer uses the same standard tokenizer as the standard analyzer, and also leverages the same lowercase and stop filters to rule out case and stop words from searches. The key to the synonym analyzer is providing it a list of synonyms, which need to be set in your own custom XML file. The reason for this is that Sitecore includes its own synonym engine implementation that uses XML files to store the synonym mappings.

Configuring the Synonym Analyzer

In Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration.config (or you can patch the patch), change the inner defaultAnalyzer parameter reference from the standard analyzer to the synonym analyzer:

<analyzer type="Sitecore.ContentSearch.LuceneProvider.Analyzers.PerExecutionContextAnalyzer, Sitecore.ContentSearch.LuceneProvider">
  <param desc="defaultAnalyzer" type="Sitecore.ContentSearch.LuceneProvider.Analyzers.DefaultPerFieldAnalyzer, Sitecore.ContentSearch.LuceneProvider">
    <param desc="defaultAnalyzer" type="Sitecore.ContentSearch.LuceneProvider.Analyzers.SynonymAnalyzer, Sitecore.ContentSearch.LuceneProvider">

Now unlike the standard analyzer, the synonym analyzer requires an implementation of an ISynonymEngine as its parameter:

<param hint="engine" type="Sitecore.ContentSearch.LuceneProvider.Analyzers.XmlSynonymEngine, Sitecore.ContentSearch.LuceneProvider">

Sitecore’s implementation of that engine is able to read from XML files, and its requires a path to the XML file as its only parameter:

<param hint="xmlSynonymFilePath">C:\inetpub\wwwroot\yoursite\Data\synonyms.xml</param>

Putting it all together:

<analyzer type="Sitecore.ContentSearch.LuceneProvider.Analyzers.PerExecutionContextAnalyzer, Sitecore.ContentSearch.LuceneProvider">
  <param desc="defaultAnalyzer" type="Sitecore.ContentSearch.LuceneProvider.Analyzers.DefaultPerFieldAnalyzer, Sitecore.ContentSearch.LuceneProvider">
    <param desc="defaultAnalyzer" type="Sitecore.ContentSearch.LuceneProvider.Analyzers.SynonymAnalyzer, Sitecore.ContentSearch.LuceneProvider">
	  <param hint="engine" type="Sitecore.ContentSearch.LuceneProvider.Analyzers.XmlSynonymEngine, Sitecore.ContentSearch.LuceneProvider">
	    <param hint="xmlSynonymFilePath">C:\inetpub\wwwroot\yoursite\Data\synonyms.xml</param>
	  </param>
    </param>
  </param>

Defining Synonyms in XML

Now that you have the synonym analyzer configured to use your custom XML file, you need to use the following basic XML structure:

<?xml version="1.0" encoding="utf-8" ?>
<synonyms>
  <group>
    <syn>fast</syn>
    <syn>quick</syn>
    <syn>rapid</syn>
  </group>
  <group>
    <syn>slow</syn>
    <syn>decrease</syn>
  </group>
  <group>
    <syn>google</syn>
    <syn>search</syn>
  </group>
  <group>
    <syn>check</syn>
    <syn>lookup</syn>
    <syn>look</syn>
  </group>
</synonyms>

All terms listed in the same group are synonyms of each other. So for example, if a content item has the word “quick” in its CMS content but you search for the word “rapid” you will get that content item as a result.

Understanding the Full Context of the Analyzer Configuration

If you want to understand how this little piece fits into the overarching analyzer configuration, its actually quite impressive how Sitecore has abstracted so much to be configured:

  1. The overarching <analyzer> is the PerExecutionContextAnalyzer which is described on the Sitecore 7 Dev blog. Its essentially a overarching “switcher analyzer” that allows a separate analyzer to be used per context.
  2. That analyzer takes a default analyzer and a mapping which can map unique analyzers per culture (also described in the same post above). If the culture matches a mapped analyzer it will use it, otherwise it will fall back to the default analyzer.
  3. The default analyzer is the DefaultPerFieldAnalyzer which looks at the same config file below for field-specific analyzers in the <fieldMap> section.
  4. It too has a default analyzer in the case that an individual field doesn’t have a specific analyzer set. This default analyzer is now our new SynonymAnalyzer which is the final fall back scenario.
Posted in Sitecore

Sitecore Cross-Language Contextual Search

Sitecore 7’s new Content Search API provides a great way to leverage an underlying search provider like Lucene or Solr to search content items with the ability to leverage native filters such as the content language. One thing you may want to consider is how searching works on a multilingual site and how the visitor of the site may be entering in their terms relative to the language context.

Take for example a multilingual site that has English and Spanish content. Say the language switcher on the site is configured for English by default and all content exists in both English and Spanish. If the visitor opts to flip the language to Spanish, all of the content will render in Spanish. This is a great way to target the multilingual visitor by having 1-to-1 mappings of content across languages. Now say the visitor searches for “productos” while they are on the site with the English language as the context language. Depending on how your search code is written, it may search only within the context language, e.g. if you filter by the context language in your query you won’t get a result for the “productos” term:

query.Filter(i => i.Language = Sitecore.Context.Language.Name);

Also, as Mike Robbins points out, you can even search using a CultureExecutionContext so it will dynamically leverage the right analyzer of the selected language.

This is a nice way to search within the selected language that the site is in, but what about that multilingual visitor? What if they speak English and Spanish and don’t know the site will filter to English content? This is where searching across languages can be useful. Here’s what you can do:

  1. Search for the term without a language filter
  2. For each SearchResultItem check to see if the item is in the context language and if it is, return it.
  3. If not, check to see if there’s a language version of the same item in the context language and return that.

Here’s an example with a direct 1-to-1 mapping of content in English and Spanish:

English version Spanish version
products productos

If the context language is English and the visitor search for “productos” the code will then:

  1. Finds the Spanish document as a SearchResultItem
  2. Determines that the SearchResultItem‘s language (Spanish) is not the context language (English)
  3. Tries to get the item in the context language e.g. GetItem(sri.ItemId, contextLanguage)
  4. If the item is not null, display it in the results. In this case it will render “products” in the results.

Here’s another scenario where there is not a true 1-to-1 mapping between languages:

English version Spanish version
<no version exists> productos

Now in this scenario, if the context language is English and the visitor search for “productos” the code will then:

  1. Finds the Spanish document as a SearchResultItem
  2. Determines that the SearchResultItem‘s language (Spanish) is not the context language (English)
  3. Tries to get the item in the context language e.g. GetItem(sri.ItemId, contextLanguage)
  4. The item is null because it does not exist in English, so return nothing since we want to maintain the results in the context language.

Here’s some sample code to get started:

var results = context.GetQueryable<SearchResultItem>().Where(p => p.Content.Contains(term));

foreach(var r in results)
{
    // from the SRI, try to get the underlying item in the context language
    var item = r.GetItem(r.ItemId, contextLanguage);

    if(item != null)
    {
        // if the item is not null it has a counterpart version in the context language, so return it
    }
}
Posted in Sitecore

Sitecore Environment Identification Tip

Today you get a simple quick tip for managing multiple Sitecore instances. If your infrastructure has a lot of servers and is scaled in such a way that you manage content on multiple authoring instances, its certainly possible for you to accidentally log into the wrong instance to author some content. In many environments I’ve worked in, Sitecore applications may be scaled for various reasons such as development, quality assurance, pre-production / testing, and production. If you have two or more authoring instances, one simple thing you can do is configure an environment-specific welcome title for the Sitecore login screen. This may act as a simple gut check when you log into Sitecore to make sure you’re logging into the right instance.

First add a patch config, such as /App_Config/Include/WelcomeTitle.config.

Next add a message that is specific to that server:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="WelcomeTitle">
        <patch:attribute name="value">Production Environment</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

Here are some samples:

env-1

env-2

Oh and don’t forget to change your admin password to something other than “b” 😉

Posted in Sitecore

Sitecore Rich Text Editor CSS Class Tips

The Sitecore Rich Text Editor comes with many customization opportunities, however the most common change made to the RTE is the addition of CSS classes in the dropdown and making the text styles in the editor itself look like the front-end site. Read on to learn how to style the text in the RTE and add CSS classes.

Customize the RTE Text Style

The RTE by default has some very basic text styles that you can override to match the look and feel of your front-end site. First, its important to know that the RTE style comes from a CSS file configured via the web.config:

<!--  WEB SITE STYLESHEET
	CSS file for HTML content of Sitecore database.
	The file pointed to by WebStylesheet setting is automatically included in Html and Rich Text fields.
	By using it, you can make the content of HTML fields look the same as the actual Web Site
-->
<setting name="WebStylesheet" value="/default.css" />

You can simply patch in a change to this setting to set the file to your own custom RTE CSS file, like so:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="WebStylesheet">
	    <patch:attribute name="value">/custom-rte.css</patch:attribute>
	  </setting>
    </settings>
  </sitecore>
</configuration>

Now you can edit your custom CSS file to make the basic text styles of it look like your site. I recommend the following edits to keep it clean and simple:

body {
    color: /* your style here */;
    font-size: /* your style here */;
    font-family: /* your style here */;
    width: 100%;
    height: 100%;
}

h1 {
    /* your styles here */;
}

h2 {
    /* your styles here */;
}

h3 {
    /* your styles here */;
}

a {
    color: /* your style here */;
}

ul {
    /* your styles here */;
}

ol {
    /* your styles here */;
}

li {
    /* your styles here */;
}

Pro tip: cache bust the CSS any time you edit it

Since the CSS file is loaded into the browser in the <iframe> that the RTE uses, it will likely be cached easily. If you edit the CSS file you can easily add a dummy query string to cache bust the CSS file and force it to load a new copy from the server, e.g.

<patch:attribute name="value">/custom-rte.css?v=2</patch:attribute>

Expose CSS Classes in the RTE

Now that you’ve made the text itself look nice like the front-end site, you may want to add a few helper CSS classes to the dropdown to help your editors. One approach is to add a CSS class to the custom RTE CSS file and apply your style properties to it. This will expose the class in the dropdown and when an editor uses it, it will actually apply the styles:

Another option is to apply an empty CSS class with no styles. This is useful for utility classes that might not have an appearance change in the RTE itself:

Once your classes are added to the CSS file, there are displayed in the dropdown in the RTE:

As you can see, once applied, they render thier style properties in the RTE as well:

Posted in Sitecore

Sitecore Items Will Not Publish

There are various scenarios when a Sitecore content author may be warned that an item will not publish. Read on to learn why:

Not in the final workflow step

If you publish now, the selected version will not be visible on the Web site because it is not in the final workflow step.

You will get the above message if the content item is is a workflow state that is not final. This is a friendly warning that you need to approve your content or have someone else that is an approver do so. This is the whole point of workflow, it prevents publishing until the final state.

Today does not fall within its valid date range

If you publish now, the selected version will not be visible on the Web site because today does not fall within its valid date range.

You will get the above message if the content item has publishing restrictions by date. To investigate the restrictions, use the View or Change button on the restrictions of the Publish tab.

It has been replaced by a newer version

If you publish now, the selected version will not be visible on the Web site because it has been replaced by a newer version.

You will get the above message if the content item has a newer version of the item that is approved for publish. Use the versions dropdown menu to select the newer version.

It has been replaced by an older version

If you publish now, the selected version will not be visible on the Web site because it has been replaced by an older version.

You will get the above message if the content item has a newer version that is not in the final state of workflow or that newer version is not marked as publishable. Thus, the older version will replace content when published because the newer version is not applicable for publish.

General Publishing Restrictions (item versions not selected)

If you publish now, the selected version will not be visible on the Web site.

You will get the above message if the content item has publishing restrictions that completely block it. This is likely the case where someone made the item always unpublishable for a reason. To investigate the restrictions, use the View or Change button on the restrictions of the Publish tab.

To learn more about workflow, visit the Sitecore Workflow documentation page.

Posted in Sitecore

Configure Sitecore Web Forms for Marketers with a Standard Connection String

For anyone that has or is going to install Sitecore’s Web Forms for Marketers module, the module comes with it’s own patch config file as you can expect. Depending on the version of Sitecore you are using, it’s either called forms.config or Sitecore.Forms.config. One of the post-install steps of the package is to configure a data provider with a database connection, e.g.
<formsDataProvider
type="Sitecore.Forms.Data.DataProviders.WFMDataProvider,Sitecore.Forms.Core">
. The default configuration of this SQL-based provider is to define a connection string as the first string-based parameter below the provider, e.g.

<formsDataProvider type="Sitecore.Forms.Data.DataProviders.WFMDataProvider,Sitecore.Forms.Core">
  <param desc="connection string">Database=(database);Data Source=(server);user id=(user);password=(password);Connect Timeout=30</param>
</formsDataProvider>

This connection string is deeply embedded within this patch config and does not follow the standard connection string configuration in the ConnectionStrings.config file. Luckily, you can swap this out for a normal token-based connection string in ConnectionStrings.config.

If you de-compile the data provider and look at its constructor, you can see that when no string parameter is provided, it falls back to WebFormsContext.ConnectionString:

wfm1

That base connection string property looks for a standard connection string with a token of "wfm":

wfm2

So the simple way to follow the standard approach to defining the database in ConnectionStrings.config is to define a string with that "wfm" token and ensure there is no parameter to the data provider:

<add name="wfm" connectionString="user id=username;password=password;Data Source=(server);Database=(database)" />
<formsDataProvider type="Sitecore.Forms.Data.DataProviders.WFMDataProvider,Sitecore.Forms.Core">
</formsDataProvider>

Leave a comment if you have any other tips like this.

Posted in Sitecore

Sitecore Context Site Resolution

Sitecore’s API contains an easy way to get the context site in your code, however it has some pitfalls in the case of a multi-site solution with page editor and preview modes. Here is a simple solution to this challenge.

Standard API Context Site Resolution

The typical way to get a context site in Sitecore is simply via Sitecore.Context.Site. This works most of the time, but if you have a multi-site solution and use Page Editor or Preview mode, it may not determine the site the item lives within (due to co-habitating site content trees). Additionally, if you use the Presentation tab’s embedded Preview frame, it will assume the context site is either “website” or “shell” so you really need this solution.

Enhanced Context Site Resolution

Here is a sample method to get the context site. I won’t go into details of the code because it’s all commented, but at a high-level:

  1. Get the item being viewed (the hardest part) — page editor or multiple preview modes
  2. Get all sites
  3. Find the first site for which the item falls within the tree path

public static Sitecore.Sites.SiteContext GetContextSite()
{
	if (Sitecore.Context.PageMode.IsPageEditor || Sitecore.Context.PageMode.IsPreview)
	{
		// item ID for page editor and front-end preview mode
		string id = Sitecore.Web.WebUtil.GetQueryString("sc_itemid");

		// by default, get the item assuming Presentation Preview tool (embedded preview in shell)
		var item = Sitecore.Context.Item;

		// if a query string ID was found, get the item for page editor and front-end preview mode
		if (!string.IsNullOrEmpty(id))
		{
			item = Sitecore.Context.Database.GetItem(id);
		}

		// loop through all configured sites
		foreach (var site in Sitecore.Configuration.Factory.GetSiteInfoList())
		{
			// get this site's home page item
			var homePage = Sitecore.Context.Database.GetItem(site.RootPath + site.StartItem);

			// if the item lives within this site, this is our context site
			if (homePage != null && homePage.Axes.IsAncestorOf(item))
			{
				return Sitecore.Configuration.Factory.GetSite(site.Name);
			}
		}

		// fallback and assume context site
		return Sitecore.Context.Site;
	}
	else
	{
		// standard context site resolution via hostname, virtual/physical path, and port number
		return Sitecore.Context.Site;
	}
}

Posted in Sitecore

Unpublish Sitecore Content

Recently many (many…) people have asked me how to unpublish content in Sitecore. It’s really easy in fact.

Go to the Publish ribbon and select the Change button:

unpublish1

In the dialog, make the item as a whole unpublishable by deselecting the Publishable checkbox:

unpublish2

You will be told by the content editor what to expect:

unpublish3

This item is not publishable, which really means, you can instantiate the publish operation on it, but it will not show on the front-end. So your next step is to actually publish (I know, sounds wrong…) the item and it will disappear from the front-end but remain in the master database.

And there you have it, an easy way to “unpublish” content.

Posted in Sitecore

Dealing with Sitecore Template is Used by at Least One Item Warning

If you’ve ever worked on creating data templates in the Sitecore CMS only to later abandon them with a better replacement, you may come across an issue of existing items based on that template. Read on to learn a quick way to handle this.

First, here’s the issue you will see if you try to delete a template that has at least one item based on it:

delete1

Ugh oh, that’s not going to happen!

delete2

This simply means that another item exists based on this template and therefore you cannot delete it as Sitecore cannot really handle orphaned types well. The solution is to find the Referrers of that template itself to determine which items are based on it. From there you can go to those items and delete them.

delete3

Posted in Sitecore