Tuesday, 6 December 2016

Language Fallback on Final Layout

Last time I was struggling with translating items to different languages. I was using the ClayTablet Sitecore Translation Connector module - it basically sends the content to translator, waits till it will be returned, receives the translated content and creates new version in desired language. Sitecore was in Initial Release of 8.1.

Then I realized, that new versions of items in other languages had missing value in Final Renderings field. Unfortunately solution which I was working on, was using this field widely from the beginning - by the Experience Editor. I tried to set the Language Fallback on that field but in vain.

I have successfully reproduced this behaviour on clean instance of 8.1 Initial Release. I have enabled Language Fallback:

    <sites>
      <site name="shell">
        <patch:attribute name="enableItemLanguageFallback">true</patch:attribute>
        <patch:attribute name="enableFieldLanguageFallback">true</patch:attribute>
      </site>
      <site name="website">
        <patch:attribute name="enableItemLanguageFallback">true</patch:attribute>
        <patch:attribute name="enableFieldLanguageFallback">true</patch:attribute>
      </site>
    </sites>

Also I have set the fallback path in the language item:


After that I have added new rendering from Experience Editor:


Unfortunately, I wasn't able to fallback this field from the base language. I have sent my problem to Sitecore Support and they confirmed, that this feature is missing. Sadly I had nothing more to do but manually set these renderings or use a PowerShell to automate it. Fortunately this bug reported by me was labelled as feature request and now it is present on version 8.2 Update-1!


The public reference number was set to 105306.

I have downloaded the package and started to testing it out. Fresh instance was set up by the SIM.
I went to the Final Renderings field:

/sitecore/templates/System/Templates/Sections/Layout/Layout/__Final Renderings

And I have enabled field level fallback:


Everything works ok now - additional "Sample rendering" added from Experience Editor is present now on other languages that have Fallback set:



Also, the Update-1 comes with really great features like wide Azure support. You should definitely check this out!


Thursday, 8 September 2016

Error while using WFFM save action Send Email Campaign Message

Last time while I was playing with EXM and WFFM I have experienced strange issue. I am using Sitecore 8.1 Update-2, EXM 3.2.1 and WFFM 8.1 Update-2. I have added Send Email Campaign Message Save Action to my form, set up message and destination e-mail.



After form submit I got an error:

 5276 15:43:58 INFO  AUDIT (extranet\Anonymous): [WFFM] Form {B9C58542-7A02-4191-94B1-186C87EC74D9} is saving to db   5276 15:43:58 WARN  [WFFM] Object reference not set to an instance of an object.   Exception: System.NullReferenceException   Message: Object reference not set to an instance of an object.   Source: Sitecore.Forms.Custom      at Sitecore.Form.Submit.SendStandardMessage.Send(AdaptedResultList fields)      at Sitecore.Forms.Core.Dependencies.DefaultImplActionExecutor.ExecuteSaving(ID formID, ControlResult[] fields, IActionDefinition[] actionDefinitions, Boolean simpleAdapt, ID sessionID)   5276 15:43:58 WARN  [WFFM] The 'Send Email Campaign Message[id={EB59E095-5B61-4C04-8286-9469DAFD9A13}]' save action failed: We experienced a technical difficulty while processing your request. Your data may not have been correctly saved.   5276 15:43:58 WARN  [WFFM] Web Forms for Marketers: an exception: We experienced a technical difficulty while processing your request. Your data may not have been correctly saved. has occured while trying to execute an action.

NullReferenceException was rather strange for me, so I started deep investigation. I have decompiled some WFFM and EXM classes and I found the problem. Some missing checks caused that issue.

The main cause of that situation is the EXM Message is being in state other than Active. Sitecore Support provided me with package, that solves this glitch.

Public reference number is 120449.

To resolve the issue, I had to:
  1. Put the Sitecore.Support.120449.dll into bin folder
  2. Navigate to the /sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Save Actions/Send Email Campaign Message item and perform the following changes with its fields:
  • Assembly -> Sitecore.Support.120449
  • Class -> Sitecore.Support.Form.Submit.SendStandardMessage
         3. Publish the changes

After that it worked for me. Hope that post will be helpful :)

Saturday, 25 June 2016

Error in event item:saved when migrating old WFFM items by Razl

Last time when I was upgrading another website from version 6.5 to 8.1, I have faced the problem with WFFM forms. I was transferring all content from old database to new one, using Razl. Almost everything went OK, but WFFM Form items.

The error I got, was rather misterious:



18684 11:12:39 ERROR One or more exceptions occurred while processing the subscribers to the 'item:saved' event.
Exception[1]: System.FormatException
Message[1]: Unrecognized Guid format.
Source[1]: mscorlib
   at System.Guid.GuidResult.SetFailure(ParseFailureKind failure, String failureMessageID, Object failureMessageFormatArgument, String failureArgumentName, Exception innerException)
   at System.Guid.TryParseGuid(String g, GuidStyles flags, GuidResult& result)
   at System.Guid..ctor(String g)
   at Sitecore.Analytics.Data.TrackingField.<>c__DisplayClass21.b__20(XElement e)
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at Sitecore.Analytics.Data.TrackingField.ValidateLinks(LinksValidationResult result)
   at Sitecore.Links.ItemLinks.AddLinks(Field field, List`1 links, ItemLinkState linkState)
   at Sitecore.Links.ItemLinks.GetLinks(ItemLinkState linkState, Boolean allVersions, Boolean includeStandardValuesLinks)
   at Sitecore.Links.SqlLinkDatabase.UpdateItemVersionReferences(Item item)
   at Sitecore.Links.ItemEventHandler.OnItemSaved(Object sender, EventArgs args)
   at Sitecore.Events.Event.EventSubscribers.RaiseEvent(String eventName, Object[] parameters, EventResult result)


18684 11:12:39 ERROR Razl service error
Exception: System.AggregateException
Message: One or more exceptions occurred while processing the subscribers to the 'item:saved' event.
Source: Sitecore.Kernel
   at Sitecore.Events.Event.EventSubscribers.RaiseEvent(String eventName, Object[] parameters, EventResult result)
   at Sitecore.Events.Event.RaiseEvent(String eventName, Object[] parameters)
   at Sitecore.Events.Event.RaiseItemSaved(Object sender, ItemSavedEventArgs args)
   at System.EventHandler`1.Invoke(Object sender, TEventArgs e)
   at Sitecore.Data.Engines.EngineCommand`2.RaiseEvent[TArgs](EventHandler`1 handlers, Func`2 argsCreator)
   at Sitecore.Data.Engines.EngineCommand`2.Execute()
   at Sitecore.Data.Engines.DataEngine.SaveItem(Item item)
   at Sitecore.Data.Managers.ItemProvider.SaveItem(Item item)
   at Sitecore.Data.Managers.PipelineBasedItemProvider.ExecuteAndReturnResult[TArgs,TResult](String pipelineName, String pipelineDomain, Func`1 pipelineArgsCreator, Func`1 fallbackResult)
   at Sitecore.Data.Managers.PipelineBasedItemProvider.SaveItem(Item item)
   at Sitecore.Data.Items.ItemEditing.AcceptChanges(Boolean updateStatistics, Boolean silent)
   at Sitecore.Data.Items.EditContext.Dispose()
   at HedgehogDevelopment.Razl.Service.RazlService.SetItemDetails(Guid accessGuid, String database, Guid itemId, ItemDetails itemDetails)

It was really confusing for me. Later, I realized that it was caused by value of field Tracking in Personalization section. In my old database the value was:

<tracking>
     <event name="Field Changed" />
     <event name="Field Completed" />
     <event name="Field Not Completed" />
     <event name="Field Out of Boundary" />
     <event name="Invalid Field Syntax" />
</tracking>

In new version those events need an id parameter, not only name.

I decided to prepare that items by hand and put them in Sitecore package. I really wanted to make it all automated, but on the other hand, that solution wasn't really bad.

So I prepared that field in a way like this (Tracking field in 8.1 is in Advanced section):

<tracking>
    <event id="{AA3AE715-E87D-4B4D-80C7-4290546F770F}" name="Field changed" />
    <event id="{F0113A93-570A-4F69-8C7C-BA08037D1E34}" name="Field Completed" />
    <event id="{7E86B2F5-ACEC-4C60-8922-4EB5AE5D9874}" name="Field Not Completed" />
    <event id="{F3D7B20C-675C-4707-84CC-5E5B4481B0EE}" name="Field Out of Boundary" />
    <event id="{844BBD40-91F6-42CE-8823-5EA4D089ECA2}" name="Invalid Field Syntax" />
</tracking>

And it works :)


Thursday, 9 June 2016

WFFM - Creating custom hidden field with Context Item ID

Last time I was creating article comment functionality using Web Forms For Marketers. I have designed it that all comments was saved as Sitecore items and put into bucket in specified location. One of the problems was to put some information about article we are commenting.

I have chose Sitecore ID to be put inside that comment item. Unfortunately I wasn't able to do it out-of the-box. I needed to write some custom code and I thought that custom WFFM field would be the best idea.

WFFM version I was using was 8.1 rev. 151217 (Update-1). It's code changes very frequently so tutorials found in internet was a bit of outdated (very probably that mine will be soon too ;)).

I decided that the best and pretty easy way, will be create control that will put hidden html field and populate it with context item ID. I was told that the best way to achieve it will be create derivative from SingleLineText field. Tools like ILSpy or dotPeek was very helpful to take a look at the code inside. The value of Sitecore.Context.Item.ID is taken in OnLoad() method. To make it more flexible I have added field that made it switchable.

Here is the code:

using Sitecore.Form.Core.Attributes;
using Sitecore.Form.Web.UI.Controls;
using System.ComponentModel;
using System.ComponentModel.Design;
 
namespace SitecoreCoffee.WFFM.CustomFields
{
    /// <summary>
    /// Hidden Id Field
    /// </summary>
    [Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design"typeof(IDesigner))]
    public class HiddenIdField : SingleLineText
    {
        private bool _isHidden = true;
 
        /// <summary>
        /// Used to switch hidden/visible
        /// </summary>
        [VisualCategory("Configuration")]
        [VisualFieldType(typeof(Sitecore.Form.Core.Visual.BooleanField))]
        [VisualProperty("Is Hidden?"10), DefaultValue("Yes")]
        public string IsHidden
        {
            get
            {
                return _isHidden ? "Yes" : "No";
            }
 
            set
            {
                _isHidden = value == "Yes";
            }
        }
 
        protected override void OnLoad(System.EventArgs e)
        {
            Text = Sitecore.Context.Item.ID.ToString();
 
            if (_isHidden)
            {
                Attributes["style"= "display: none";
            }
        }
    }
} 

If you are using MVC you should also create ViewModel for that field:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
 
namespace SitecoreCoffee.WFFM.CustomFields
{
    /// <summary>
    /// View Model for Hidden Id Field
    /// </summary>
    public class HiddenIdFieldViewModel : Sitecore.Forms.Mvc.ViewModels.Fields.SingleLineTextField
    {
        [DataType(DataType.Text)]
        public override string Value { get; set; }
 
        /// <summary>
        /// Initializes field
        /// </summary>
        public override void Initialize()
        {
            KeyValuePair<string, string> isHidden = Parameters.FirstOrDefault(x => x.Key.ToUpper() == "ISHIDDEN");
 
            if (isHidden.Value.ToUpper() != "NO")
            {
                if (!string.IsNullOrWhiteSpace(CssClass))
                {
                    CssClass += " hidden";
                }
 
                else
                {
                    CssClass = "hidden";
                }
            }
 
            ShowTitle = false;
 
            Value = Sitecore.Context.Item.ID.ToString();
        }
    }
}

The last part is to create Sitecore item. It should be added below /sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types. This item should be based on template /sitecore/templates/Web Forms for Marketers/Field Type and be populated with:
  • Assembly - file name of code assembly
  • Class - class name of field type with full namespace
  • MVC Type - class name of field type view model with full namespace and its assembly name (after comma)


And it can be used in form:



Hope it will be helpful :)

Monday, 6 June 2016

Issue with Final Layout and Experience Editor: The layout for the requested document was not found

Recently I have found some bug with Sitecore 8.1 Update-2 and Experience Editor.

When I add some rendering and save the page, it appears and it's all ok. But when I remove it and save, I am getting this error:

The layout for the requested document was not found



Also all Final Renderings are gone:



I have found out that when I edit 'Final renderings' field by hand, save it, and then remove all contents of this, the same issue appears.


I am resolving it by resetting the Final Layout. I think it may be some inconsistency with checking null or empty string.

I also find out that this case is known issue: https://github.com/Sitecore/Habitat/issues/136
With public reference number 108023

I have asked support about  this issue and I get a fix for that. Unfortunately it didn't help me.
However I also got an information how to workaround this issue.

There is a "Reset blank" checkbox field in __Final Renderings item. It is located in
/sitecore/templates/System/Templates/Sections/Layout/Layout/__Final Renderings
Purpose of this option is to reset field value to default when saving field is set to empty value.



And the information that I got from Support:
This is very handy in situations where it doesn’t make sense to have the field blank. And, as the initial issue led to erasing the contents of the __Final Rendering field, ticking this checkbox allows Sitecore to use the Standard Values of the item's template. And this fixes the issue.
 Hope this will be helpful for someone :)

Wednesday, 23 March 2016

Upgrading Sitecore - my experience

Upgrading Sitecore is one of the things that every Sitecore developer should do. Depending on solution design, it can be very easy or very painful. I would like to present my experience of this process.

Solution that I was upgrading, was developed on Sitecore 7.2 and I was upgrading it to version 8.0.

Application was relatively small. There was one application server and one database server. Requests from clients was passed by nginx proxy server. Architecture looked like this:



After checking how application was built and maintained, I sadly realize that it will be kind of hard time.

There are two ways to upgrade Sitecore:
  • Upgrade version by version, using update packages, to achieve desired state
  • Install clean Sitecore and merge it with existing application code and database content
Knowing that solution had a lot of legacy code and was developed poorly, I decided to take second approach.

1. Cleaning up the solution

Solution had very large technological debt. Developers have made a number of sins Sitecore application can have:
  • Changes in configuration files was made directly in Web.config and default Sitecore patches.
  • Changes in Sitecore functionality was made directly in Sitecore directory (/sitecore)
  • Some files was edited directly on the production application server and were not committed to code repository
To clean up this mess, what I had to do, was:
  • Create new project in solution that will contain all custom configurations and new functionalities
  • Find all references to custom functionalities, such as pipelines or events, in configurations and move it to prepared project
  • Create new project in solution that will contain all overridden Sitecore functionalities
  • Find all references to all overridden functionalities and move it to prepared project
I decided to separate this two things, to make new functionalities more portable, to be used in other projects.

2. Upgrade

After cleaning up the solution, I could go to upgrade. I downloaded new Sitecore and I set it up. Next I downloaded fresh production databases from backup, so I had up to date data. After setting it all up, I was having two instances - fresh Sitecore 8.0 with clean databases, and old Sitecore 7.2 with fresh production data. At that time RAZL, tool from Hedgehog Development, was REALLY helpful. I could easily transfer content into 8.0's databases. Unfortunately that time available version didn't have great features: deep compare and lightning mode (http://hedgehogdevelopment.github.io/razl/comparing#deep-compare), so I had to check branches deeply by hand or just override whole branches.



Some of the custom features that was developed for 7.2 didn't work as expected, so I had to fix them (contact with Support was very helpful). But at the end of the day, application, upgraded to version 8.0, started working!

Some of the main glitches I've been through:

3. Deployment to new servers

Present website was running on Windows Server 2008 and MS SQL Server 2008 R2, so we decided to create fresh servers with Windows Server 2012, IIS 8.0 and MS SQL Server 2014. These servers was used to test if all contents and functionalities work, and afterwards, performance tests. After that these servers will become new production servers.

To make sure that content is up to date, I installed on the new application server, another RAZL instance. I connected it with local Sitecore 8.0 and currently used Sitecore on production. In this way I could easily download new content directly from production. You need to know that connecting RAZL with Sitecore will cause application pool recycle, because new dll and configuration will be installed.

To make sure that application result is transferred from new server, I have added new additional header in applicationHost.config. Thanks to that, I could easily check it from server response:

X-Server: NEW APPLICATION

4. Deployment to production

Regarding that nginx was the first gate to application server, it was possible to bind new application server to this existing server. New domain alias was created, secured with http basic authentication and binded to new application server. It was something like application-sc8.mycompany.com.

New alias domain was added in:
  • IIS website domain bindings
  • Sitecore site configuration (hostName attribute)

In this approach:
  • Changes in domain records was not needed - current domain still points to existing proxy. Only one change was necessary - switch domain point locations on nginx. Production domain now points to new application server and alias domain, used for test, points to old production server - just to check if all content was transferred.
  • Switching the servers was instant.
In the deployment night, we just switched server bindings on nginx, cleared content cache and voila! Application that runs on Sitecore 8, was pushed to production.

Friday, 4 March 2016

Excluding Standard Values items from search results

Last time I was configuring my search index and I found that there are Standard Values items in search results. Quick research in google showed me that I can hide this items by creating computed field, which will mark these items.

Few moments later, after another glimpse at the index configuration, I found my issue. In crawler configuration I forgot to define root of the search:

<locations hint="list:AddCrawler">
  <crawler type="Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch">
    <Database>web</Database>
    <Root>/sitecore/</Root>
  </crawler>
</locations>

I want to search only under the Home item, so I should define it like this:

<Root>/sitecore/content/home</Root>

:)

Well, it was easy one. But then I though how could I exclude Standard Values items from index, without setting root. Solution with creating computed field and then checking it while fetching results, sounds too tricky for me. So I created custom crawler which checks if item is standard values item:

/// <summary>
/// Custom Item Crawler
/// </summary>
public class CustomItemCrawler : SitecoreItemCrawler
{
    protected override void DoAdd(IProviderUpdateContext context, SitecoreIndexableItem indexable)
    {
        var item = indexable.Item;
 
        // - Don't add Standard values Item -
 
        if (!StandardValuesManager.IsStandardValuesHolder(item))
        {
            base.DoAdd(context, indexable);
        }
    }
}

And configuration:

<locations hint="list:AddCrawler">
  <crawler type="MyAssembly.Crawlers.CustomItemCrawler, MyAssembly">
    <Database>web</Database>
    <Root>/sitecore/</Root>
  </crawler>
</locations>

Wednesday, 24 February 2016

Pipelines - Sitecore development basics

Pipelines are very important mechanism in Sitecore. They organize processed tasks in a very clear, classified and highly customizable way.

Pipelines contain processors, which execute specified actions, one after one. Each processor can abort whole pipeline, so subsequent processors won't be executed.

Processor must contain Process() method that returns void and accepts single argument based on class Sitecore.Pipelines.PipelineArgs or its derivative.

public class CustomPipelineProcessor
{
    public void Process(Sitecore.Pipelines.PipelineArgs args)
    {
        // Your logic
    }
}

To make your processor work, you need to bind it to pipeline in configuration file. Let's look at the <httpRequestBegin> pipeline configuration (Sitecore 8.1):

<httpRequestBegin>
  <processor type="Sitecore.Pipelines.PreprocessRequest.CheckIgnoreFlag, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.EnsureServerUrl, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.StartMeasurements, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.IgnoreList, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.SiteResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.UserResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DatabaseResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.BeginDiagnostics, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DeviceResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.CustomHandlers, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.FilterUrlExtensions, Sitecore.Kernel">
    <param desc="Allowed extensions (comma separated)">aspx</param>
    <param desc="Blocked extensions (comma separated)">*</param>
    <param desc="Blocked extensions that stream files (comma separated)">css,js</param>
    <param desc="Blocked extensions that do not stream files (comma separated)">*</param>
  </processor>
  <processor type="Sitecore.Pipelines.HttpRequest.QueryStringResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DynamicLinkResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.AliasResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DefaultResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.FileResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DeviceSimulatorResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.ExecuteRequest, Sitecore.Kernel" />
</httpRequestBegin>

As you can see, there are a list of processors in pipeline. Names are meaningful, there are processors which resolve current site, user, database, etc.

So when we want to put our processor to pipeline, we create configuration patch file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="SitecoreExperiments.Common.Pipelines.CustomPipelineProcessor, SitecoreExperiments.Common" 
                   patch:after="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

In this example patch code, new processor is added to httpRequestBegin pipeline. It is added just after ItemResolver processor, beacuse I set it by this attribute:

patch:after="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"
Setting this is not necessary, I just wanted to show that it can be achieved.

I made sure that my config patch is loaded last, so I named it SitecoreExperiments.Common.Pipelines.config and I put it in directory named Z_SitecoreExperiments in \Website\App_Config\Include. After checking configuration at /sitecore/admin/showconfig.aspx I am sure that my patch works as expected:


Object that is passed to function can be modified by each processor, so subsequent processors can rely on data that is added or resolved previously. We can create our own class that inherits from Sitecore.Pipelines.PipelineArgs and adds custom fields or, for example, use CustomData SafeDictionary property:

public void Process(Sitecore.Pipelines.PipelineArgs args)
{
    args.CustomData.Add("CustomPipelineProcessor""Hello, is it pipeline I've been looking for?");
}

Pipelines can be run from code using method CorePipeline.Run()

var args = new Sitecore.Pipelines.PipelineArgs();
Sitecore.Pipelines.CorePipeline.Run("customPipeline", args);
 
// - result will be "Hello, is it pipeline I've been looking for?"
String result = args.CustomData["CustomPipelineProcessor"].ToString();

Pipeline Profiler

There is also useful tool to diagnose pipelines that run in our solution. It is disabled by default, to enable it rename configuration include file Sitecore.PipelineProfiling.config.disabled to Sitecore.PipelineProfiling.config. This config has two settings:

<setting name="Pipelines.Profiling.Enabled" set:value="true" />
This enables whole profiling.

<setting name="Pipelines.Profiling.MeasureCpuTime" set:value="true" />
This enables CPU time measurement. By default this is disabled.

 It can be accessed by url /sitecore/admin/pipelines.aspx. 




Sunday, 21 February 2016

Changing default page title for Sitecore Desktop, Launchpad and Content Editor

My client really wanted to change default page title for Sitecore Desktop view. I have found that I can change it for Launchpad - by Item /sitecore/client/Applications/Launchpad and field "BrowserTitle" in Core database:



But to change default Desktop name in page title in Sitecore 8 it is more tricky. Using only Sitecore, without changing application code, you can make it by changing /sitecore/system/Dictionary/D/Desktop on Core database. It will change browser title of website/sitecore/shell/default.aspx. I have tested that, and it works but for me it is very strange solution - it changes the proper translation of "Desktop" word!

As an alternative you can change the Title attribute of the FormPage node in the following file:
\sitecore\shell\Applications\Shell.xml - hightlighted as red:

<?xml version="1.0" encoding="utf-8" ?> 
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
  <Shell>
    <Favicon Src="/sitecore/images/favicon.ico" />
    <Stylesheet Src="Shell.css" DeviceDependant="true"/>
    <Stylesheet Src="Startbar.css" DeviceDependant="true"/>
    <Script type="text/JavaScript" src="/sitecore/shell/controls/SitecoreObjects.js" />
    <Script type="text/JavaScript" src="/sitecore/shell/controls/SitecoreWindowManager.js" />
    <Script type="text/JavaScript" src="/sitecore/shell/controls/lib/scriptaculous/scriptaculous.js?load=effects" />
 
    <Script type="text/JavaScript" language="javascript" key="TrackModified">
      scSitecore.prototype.setModified = function(value) {
        this.modified = false;
      }
    </Script>
 
    <FormPage Submittable="false" TrackModified="false" ForceGlobalHeader="true" Title="My Custom Desktop Title">
      <CodeBeside Type="Sitecore.Shell.Applications.ShellForm,Sitecore.Client" FrameName="Shell"/>
      <img ID="Wallpaper" src="" alt=""/>
      
      <Border KeyDown="ShowStartMenu" KeyFilter="c91">
        <Border Class="DesktopArea" ID="Desktop" ContextMenu="ShowContextMenu">
          <Border ID="Links" Click="ClosePopups" DblClick="Launch"/>
        </Border>
        <div class="scStartbar">
          <Startbar />
        </div>
      </Border>
    </FormPage>
  </Shell>
</control>

For Content Editor application you can do similar, in the file \sitecore\shell\Applications\Content Manager.aspx change highlighted string:
<title><%= Translate.Text(WebUtil.GetQueryString("he""My Website Content Editor")) %></title>
I hope that in next versions it will be more simple to customize, just like Launchpad, because some clients are very alerted to such details as title in the browser :)

Wednesday, 17 February 2016

Sitecore Module - Quick Package

Creating packages in Sitecore is quite simple, but sometimes you need to quickly create simple package with one item. For this purpose I have created Sitecore Marketplace package with my new module Quick Package.

This module provides rapid package creation, with just few clicks. It doesn't replace Sitecore Package Designer just lets simple actions be done faster. Module adds context menu item for all Sitecore items:

There is also new button on Developer tab:

By clicking on these elements you will see a popup, where you can set name of created package. By default it is item name which is currently selected and current date and time. You can also uncheck 'Include descendants' checkbox if you want only selected item in package - not whole tree beneath.


I have published it on Sitecore Marketplace: https://marketplace.sitecore.net/en/Modules/Q/Quick_Package.aspx
Full source code is available on github: https://github.com/ReoKzK/Sitecore.SharedSource.QuickPackage


Wednesday, 27 January 2016

Sitecore 8.1 - Creating custom search index

Sitecore provides availability to configure and use custom search indexes. Starting from version 8.1 they are slightly different in configuration. In this article I will provide you with some advices to create your own Sitecore search index.

Differences from Sitecore 8.0

Let's take a quick look at the differences from version 8.0. When you open Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration.config or Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config, you can see that sections where we define included and excluded templates, fields etc. are now wrapped with documentOptions tag:

<documentOptions type="Sitecore.ContentSearch.LuceneProvider.LuceneDocumentBuilderOptions, Sitecore.ContentSearch.LuceneProvider">
Also, names of the methods that includes/excludes specified elements now are different, for example,

<exclude hint="list:ExcludeTemplate">

is now replaced with

<exclude hint="list:AddExcludedTemplate">

That are the things I've conquered while upgrading from older Sitecore versions. Here's the list of changed names:

8.0 and before 8.1 and after
IncludeTemplate AddIncludedTemplate
ExcludeTemplate AddExcludedTemplate
IncludeField AddIncludedField
ExcludeField AddExcludedField
RemoveSpecialFields AddExcludedSpecialField

Creating custom search index

To define our own custom Sitecore custom index we will have to create config patch.

Here is version for Lucene

Here is version for Solr


Let's take a closer look now.

Extending default index configurations

Sitecore comes with default index configurations we can use to build our own index. There are versions for Lucene and Solr as well. To use it we are declaring ref attribute on <configuration> element:

<configuration ref="contentSearch/indexConfigurations/defaultLuceneIndexConfiguration">

<configuration ref="contentSearch/indexConfigurations/defaultSolrIndexConfiguration">

Indexing fields

With this setting:
<indexAllFields>true</indexAllFields> 
we declare that all fields will be indexed. This is by the way default setting in default Lucene index configuration.

We can set it to index only specific fields - indexAllFields should be set to false
<indexAllFields>false</indexAllFields> 
And then we should set our fields explicitly
<include hint="list:AddIncludedField">
  <Title>{96970EAB-F8C6-4DE2-A124-CDAC2DFD0C90}</Title> <!-- Title -->
  <Content>{EC539DFA-07C1-49A4-A094-87EACF5ED9BB}</Content> <!-- Content -->
</include>

Our index will also contain computed fields that come from default lucene index configuration. If we don't want them, then we should base our index on edited lucene index.

Including templates

The most important setting is where we define included templates. Only these templates will be indexed:
<include hint="list:AddIncludedTemplate">
    <Article>{C2198DA4-C89F-45D9-AF1A-E24C97F83398}</Article>
    <News>{24EBFFC0-27F0-4F18-A425-74D62F21609E}</News>
    <About>{804B99A2-B18E-4B86-A862-4D0270D4DEAB}</About>
</include>

I have included three example templates, they are declared with:
<templateName>Template ID</templateName>

It is also important to set up root path for the crawler:
<crawler type="Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch">
  <Database>web</Database>
  <Root>/sitecore/content/home</Root>
</crawler>

As we have complete index configuration lets save it in /App_Config/Include/OUR_CUSTOM_CONFIGS_FOLDER folder with file name format Sitecore.ContentSearch.Lucene.Index.OUR_INDEX_NAME.config. After that let's rebuild our new index.

Previewing indexes

After our index is built we can preview it. For Lucene indexes we can preview it with external tool named Luke (https://code.google.com/archive/p/luke/). It opens folder with index, usually located at Data/indexes folder:

Luke

For Solr indexes we basically open Solr Admin panel, by default set up on port 8983. We can access it by going to url http://localhost:8983/solr/

Solr admin panel


Querying index

As we have index built, we can run queries to get desired elements from index. Sitecore provides ContentSearch library and it is the proper way of searching index in Sitecore. It's completely independent from the implementation so switching index provider won't require code changes (in most cases of course ;)).

Here's a sample code that reads our custom index and searches for specified phrase:

var index = ContentSearchManager.GetIndex("SitecoreExperiments_index");
string phrase = "search phrase";
 
using (var context = index.CreateSearchContext())
{
    var searchQuery = context.GetQueryable<SearchResultItem>()
        .Where(x => x.Language.Equals(Sitecore.Context.Language.Name))
        .Where(x => x.Content.Contains(phrase));
 
    var resultItems = searchQuery.ToList();
    var totalCount = searchQuery.Count();
}

Sample project that provides custom search index and search service is available on my Helix project at GitHub - https://github.com/ReoKzK/SitecoreCoffee/tree/master/src/Foundation/Search/code