Sunday, 22 October 2017

Sitecore Configuration Layer Config - Layers.config

In Sitecore v9, Sitecore divides configuration files into layers, which in its simplicity gives you the ability to control in which order, the config files are loaded. This is handled in a new config file named Layers.config. The Layers.config file is located at the website/App_config/Layers.config and contains four layers for areas in Sitecore to manage. The layers are:

  • Custom
  • Environment
  • Modules
  • Sitecore

The “Custom” layer is used for your custom implementation configuration files and customizing existing Sitecore standard configuration files. The Custom area folder in the App_Config is called “Include”.

The “Environment” layer is used for dividing configurations into environment specific configurations (Development, QA, Production etc).

The “Modules” layer is used for configuration files for Sitecore modules (EXM, Sitecore Publishing Service etc.).

The “Sitecore” layer is used for standard Sitecore Component configuration files (Content Search, Experience Analytics, Owin etc.).

In the Layers.config file each <layer> element has a “name” attribute and an “includeFolder” attribute defining the path to the configuration folder of the layer. The <layer> element contains a <loadOrder> element defining the load order of the configuration files specified for that specific layer. Each configuration element (patch file) is added into the <loadOrder> element and has a “path” attribute (defining the path to the configuration area) and a “type” attribute (defining whether it is a File or a Folder specific element).
<layers>
  <layer name="Sitecore" includeFolder="/App_Config/Sitecore/">
    <loadOrder>
      <add path="CMS.Core" type="Folder" />
      <add path="AntiCSRFModule" type="Folder" />
      …
    </loadOrder>
  </layer>
  <layer name="Custom" includeFolder="/App_Config/Include/" />
  …
</layers>

In case of <layer includefolder="/App_Config/Include/" name="Custom"> Sitecore loads all the configuration files placed in the “Include” folder. By default, Sitecore loads the configuration files as in previous versions of Sitecore (first, Sitecore reads the files in the root of the “Include” folder alphabetically. Secondly, Sitecore loads the configuration files alphabetically in order of the subfolders alphabetically). This, because no load order has been specified. 

If you want to specify the load order of the “Custom” area configuration folders and files located in the “Include” folder, you simply add the <loadOrder> element to the layer. From here, you specify the “path” and “type” attribute to the <add> element (the patch file) E.g.:

  <layer name="Custom" includeFolder=”/App_config/Include” >
    <loadOrder>
      <add path=”MySitecoreBaslineExtensions” type=”Folder” />
      <add path=”MyCustomSolutionHttpRequestBeginExtensions” type=”Folder” />
      <add path=”Folder1/MySitecoreProcessorExtension.config” type=”File” />
    </loadOrder>
  </layer>

After loading the configurations specified in the <loadOrder> element, Sitecore loads the remaining files and folders like Sitecore normally do.

From the Layers.config file, you can disable all patch files in a specific layer or you can disable a single file/folder. This is done by adding the mode=”off” attribute to the <layer> element or at the patch file element it self.

Disabling a layer:
<layer name="Custom" includeFolder=”/App_config/Include” mode=”off”>
  <loadOrder>
    <add path=”MySitecoreBaslineExtensions” type=”Folder” />
    <add path=”MyCustomSolutionHttpRequestBeginExtensions” type=”Folder” />
    <add path=”Folder1/MySitecoreProcessorExtension.config” type=”File” />
  </loadOrder>
</layer>

Disabling a file:
  <layer name="Custom" includeFolder=”/App_config/Include”>
    <loadOrder>
      <add path=”MySitecoreBaslineExtensions” type=”Folder” />
      <add path=”MyCustomSolutionHttpRequestBeginExtensions” type=”Folder” />
      <add path=”Folder1/MySitecoreProcessorExtension.config” type=”File” mode=”off” />
    </loadOrder>
  </layer>

The default load order is Sitecore, Modules, Custom and Environment. This load order should not be changed due potential conflicts.

If you need to customize existing Sitecore configuration files best practice is still to create your own configuration files. But now you can add/place it in the Custom or Environment layers of the Layers.config file.

The Layers.config file:
  
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <layers>
    <layer includefolder="/App_Config/Sitecore/" name="Sitecore">
      <loadOrder>
        <add path="CMS.Core" type="Folder" />
        <add path="AntiCSRFModule" type="Folder" />
        <add path="Owin" type="Folder" />
        <add path="Owin.Authentication" type="Folder" />
        <add path="ContentSearch" type="Folder" />
        <add path="ContentSearch.Azure" type="Folder" />
        <add path="Marketing.Xdb.Sql.Common" type="Folder" />
        <add path="XConnect.Client.Configuration" type="Folder" />
        <add path="Marketing.Xdb.ReferenceData.Service" type="Folder" />
        <add path="Marketing.Xdb.ReferenceData.SqlServer" type="Folder" />
        <add path="Marketing.Xdb.ReferenceData.Client" type="Folder" />
        <add path="Marketing.Operations.xMgmt" type="Folder" />
        <add path="Marketing.Operations.Xdb.ReferenceData" type="Folder" />
        <add path="Marketing.xDB" type="Folder" />
        <add path="Marketing.Tracking" type="Folder" />
        <add path="Marketing.Assets" type="Folder" />
        <add path="Tracking.Web.RobotDetection" type="Folder" />
        <add path="Mvc" type="Folder" />
        <add path="Tracking.Web.MVC" type="Folder" />
        <add path="ItemWebApi" type="Folder" />
        <add path="Buckets" type="Folder" />
        <add path="SPEAK" type="Folder" />
        <add path="Services.Client" type="Folder" />
        <add path="SPEAK.Components" type="Folder" />
        <add path="Marketing.Client" type="Folder" />
        <add path="Marketing.Xdb.MarketingAutomation.Reporting" type="Folder" />
        <add path="Marketing.Xdb.MarketingAutomation.Operations" type="Folder" />
        <add path="Marketing.Xdb.MarketingAutomation.Locators" type="Folder" />
        <add path="Marketing.Xdb.MarketingAutomation.Tracking" type="Folder" />
        <add path="XConnect.Segmentation.xMgmt" type="Folder" />
        <add path="Speak.Applications" type="Folder" />
        <add path="LaunchPad" type="Folder" />
        <add path="ListManagement" type="Folder" />
        <add path="Marketing.Automation.Client" type="Folder" />
        <add path="Marketing.Automation.ActivityDescriptors.Client" type="Folder" />
        <add path="ExperienceForms" type="Folder" />
        <add path="Experience Editor" type="Folder" />
        <add path="MVC.ExperienceEditor" type="Folder" />
        <add path="MVC.DeviceSimulator" type="Folder" />
        <add path="ContentTesting" type="Folder" />      
        <add path="ExperienceProfile" type="Folder" />
        <add path="Update" type="Folder" />
        <add path="Contact.Enrichment.Services.Client" type="Folder" />
        <add path="DetectionServices.Location" type="Folder" />
        <add path="FederatedExperienceManager" type="Folder" />
        <add path="DeviceDetection.Client" type="Folder" />
        <add path="ExperienceAnalytics" type="Folder" />
        <add path="CampaignCreator" type="Folder" />
        <add path="ExperienceContentManagement.Administration" type="Folder" />
      </loadOrder>
    </layer>
    <layer includefolder="/App_Config/Modules/" name="Modules" />
    <layer includefolder="/App_Config/Include/" name="Custom" />
    <layer includefolder="/App_Config/Environment/" name="Environment" />
  </layers>
</configuration>



Thursday, 5 October 2017

Sitecore Application_Start and <initialize> Pipeline

For some time ago, I needed to build up some cache on Sitecore application start up. Sure, in a clean web application I could use the Global.asax, but in the world of Sitecore, the Global.asax has been made private. Instead of changing the Global.asax file, you should use the initialize pipeline. The initialize pipeline is started at application start (you find the initialize pipeline in Sitecore.config).

In this case of building up some caching on Sitecore application start, I create a new config include file and add the my new processor:
 
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
<pipelines>
    <initialize>
      <processor type="MyExtensions.Pipelines.Caching.CachePrefill, MyExtensions" xdt:Transform="Insert" />
    </initialize>
  </pipelines>
</Sitecore>
</configuration>

Do the coding. Notice, you don’t need to inherit from any other namespaces. 
namespace MyExtensions.Pipelines.Caching
{
    public class CachePrefill
    {
        public void Process(PipelineArgs args)
        { 
            Sitecore.Diagnostic.Log.Info("MyExtensions: Running CachePrefill.Process method", this);
            
            //do my magic cachePrefill();
            
            Sitecore.Diagnostic.Log.Info("MyExtensions: Done running CachePrefill.Process method", this);
        }
    }
}

Nothing more, nothing less.

Thursday, 31 August 2017

Sitecore WFFM Create Custom Field

Creating custom WFFM Form field is simple and only requires three steps. In the following example, I just want to create a new Singleline field suggesting the country, where the visitor is located.

First you need to create a WFFM Field using the “/sitecore/templates/Web Forms for Marketers/Field Type” template. The new WFFM Field should be located at “/sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types” in the Master database. In this case, I added at “/Custom”. Fill in the Assembly, the Class and the MVC type fields.



After creating the new Custom WFFM Field item, the field is visible at the Field Type dropdown list in the Form Designer:


Implement the codings. inherit from Sitecore.Form.Web.UI.Controls.SingleLineText, and override OnInit(EventArgs e):

namespace MyExtensions.Logic.FieldTypes.WFFM
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text;
    using System.Web.UI.WebControls;
    using DSV.Cms.Logic.Helpers;

    using Sitecore.Data.Items;

    public class CountrySingleLineText : Sitecore.Form.Web.UI.Controls.SingleLineText
    {
        protected override void OnInit(EventArgs e)
        {
            this.textbox.CssClass = "scfSingleLineTextBox country";
            this.help.CssClass = "scfSingleLineTextUsefulInfo";
            this.generalPanel.CssClass = "scfSingleLineGeneralPanel";
            this.title.CssClass = "scfSingleLineTextLabel";
            this.textbox.TextMode = TextBoxMode.SingleLine;
            this.Controls.AddAt(0, this.generalPanel);
            this.Controls.AddAt(0, this.title);
            this.generalPanel.Controls.AddAt(0, this.help);
            this.generalPanel.Controls.AddAt(0, this.textbox);
            this.textbox.Attributes.Add("type", "text");

            this.textbox.Text = LanguageSuggestion();

            StringBuilder sbCountries = new StringBuilder();
            List countryNameList = GetCountryNames();
            sbCountries.Append("[");
            int index = 1;
            foreach (string country in countryNameList)
            {
                sbCountries.Append(string.Format("\"{0}\"", country));
                if (index != countryNameList.Count)
                {
                    sbCountries.Append(",");
                }
                index ++;
            }
            sbCountries.Append("]");
            this.textbox.Attributes.Add("data-countries", sbCountries.ToString());
        }

        private string LanguageSuggestion()
        {
            // Do your language look up
            return countryCode;
        }


        protected List GetCountryNames()
        {
            //Do your listing of the Country Item values and add them to list
            return countryItemList;
        }
    }
}


Wednesday, 1 February 2017

Enhancing Web Forms For Marketers and force Insert WFFM Form Wizard in Experience Editor for a custom form controller.

In simplicity of the case I just want to create a new form controller containing some new styling and logic around the form controller in form of special styling and expand and collapse functionality for the visitor at the website.

First thing to do is to create the new usercontrol for handling the special styling and the expand and collapse functionality. For the simplicity, it is just a copy of the standard WFFM control (SitecoreSimpleFormscx) existing one and added a span tag (<span class="”expanded" hidden="">… </span> at line 4). The standard WFFM control is located at the webroot folder: “sitecore modules/Web/Web Forms for Marketers/Control/SitecoreSimpleFormAscx.ascx”.


<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SitecoreSimpleFormAscx.ascx.cs" Inherits="Sitecore.Form.Web.UI.Controls.SitecoreSimpleFormAscx" %>
<%@ Register Namespace="Sitecore.Form.Web.UI.Controls" Assembly="Sitecore.Forms.Core" TagPrefix="wfm" %>
<!-- ExpandedSitecoreSimpleFormAscx.ascx -->
<span class="expanded hidden">
  <wfm:FormTitle ID="title" runat="server"/>
  <wfm:FormIntroduction ID="intro" runat="server"/>
  <asp:ValidationSummary ID="summary" runat="server" ValidationGroup="submit" CssClass="scfValidationSummary"/>
  <wfm:SubmitSummary ID="submitSummary" runat="server" CssClass="scfSubmitSummary"/>
  <asp:Panel ID="fieldContainer" runat="server"/>
  <wfm:FormFooter ID="footer" runat="server"/>
  <wfm:FormSubmit ID="submit" runat="server" Class="scfSubmitButtonBorder"/>
</span >
<!-- /ExpandedSitecoreSimpleFormAscx.ascx -->
Next thing to do is to create a new rendering ref item for the new form control. Copy the existing WFFM Form rendering ref item and rename it. It is located at ”/sitecore/layout/Renderings/Modules/Web Forms for Marketers”. In this example I named the rendering ref item “Expanded Form”:


In the “Parameters” field change the “FromTemplate” to point at the control you just created.

Add the new control (“Expanded Form”) to the Placeholder Setting item:



Now it is time for some coding and tell Sitecore to force the Insert WFFM Wizard instead of the normal Insert Component Wizard, whenever the content authors select the “Expanded Form” control in Experience Editor mode.

My ForceWFFMWizard code does the exact same thing as the code Sitecore is using when displaying the Insert WFFM Wizard in the Experience Editor. The only different is, that the code below check whether the renderingItem ID is equal to my custom WFFM form rendering ref item (created above) at line 9. If true, then the Insert WFFM Wizard will be displayed instead of the add component Wizard.

Code:

using Sitecore;
using Sitecore.Form.Core.Configuration;
 
namespace MyExtensions.Logic.Processors
{
    class ForceInsertWFFMWizard
    {
        // expandedFormRenderingID is the ItemID of the new created WFFM Form rendering ref item in Sitecore
        private const string expandedFormRenderingID = "{958B06C6-38E1-4D30-B170-4E7474BCBB6B}";
 
        public void Process(Sitecore.Pipelines.GetRenderingDatasource.GetRenderingDatasourceArgs args)
        {
           Sitecore.Diagnostics.Assert.IsNotNull(args, "args");
 
            if (args.ContextItemPath == null)
                return;
            if (!args.RenderingItem.ID.ToString().Equals(expandedFormRenderingID))
                return;
 
            Sitecore.Diagnostics.Assert.IsNotNull(args.ContentDatabase, "args.ContentDatabase");
            Sitecore.Diagnostics.Assert.IsNotNull(args.ContextItemPath, "args.ContextItemPath");
            Sitecore.Data.Database contentDatabase = args.ContentDatabase;
            Sitecore.Data.Items.Item item = contentDatabase.GetItem(args.ContextItemPath, Context.Language);
            Sitecore.Diagnostics.Assert.IsNotNull(item, "currentItem");
            object value = Context.ClientData.GetValue(StaticSettings.prefixId + StaticSettings.PlaceholderKeyId);
            string value2 = (value != null) ? value.ToString() : string.Empty;
            string designMode = StaticSettings.DesignMode;
               
            if (item.Fields["__renderings"] == null || item.Fields["__renderings"].Value == string.Empty)
                return;
 
            Sitecore.Text.UrlString urlString = new Sitecore.Text.UrlString(UIUtil.GetUri("control:Forms.InsertFormWizard"));
            urlString.Add("id", item.ID.ToString());
            urlString.Add("db", item.Database.Name);
            urlString.Add("la", item.Language.Name);
            urlString.Add("vs", item.Version.Number.ToString());
            urlString.Add("pe", "1");
            
            if (!string.IsNullOrEmpty(value2))
            {
                urlString.Add("placeholder", value2);
            }
            
            if (!string.IsNullOrEmpty(designMode))
            {
                urlString.Add("mode", designMode);
            }
            
            args.DialogUrl = urlString.ToString();
            if (string.IsNullOrEmpty(args.CurrentDatasource))
            {
                string text = args.RenderingItem["data source"];
                if (!string.IsNullOrEmpty(text))
                {
                    args.CurrentDatasource = text;
                }
            }
            
            args.AbortPipeline();
        }
    }
}


The trick is to add the processer (in the getRenderingDatasource pipeline) just before the Sitecore.Pipelines.GetRenderingDatasource.CheckDialogState processor:


<Pipelines>
      <getRenderingDatasource>
        <processor type="MyExtensions.Logic.Processors.ForceInsertWFFMWizard, MyExtensions.Logic"                            x:before="processor[@type='Sitecore.Pipelines.GetRenderingDatasource.CheckDialogState, Sitecore.Kernel']" />
      </getRenderingDatasource>
</Pipelines>

The very last step is to open the Experience Editor and navigate to the area of the Placeholder setting containing the “Expanded Form” control and select the “Expanded Form” controller from the control list:


Select the “Expanded Form”:


Verify Sitecore opens the Insert WFFM Form Wizard:





Monday, 28 November 2016

Sitecore Transfer User Passwords

Original artikel: https://kb.sitecore.net/articles/242631

This article describes the inconveniences of password loss in Sitecore when moving users from one Sitecore instance to another and points out that it is particular inconvenient when transferring many users.

It gives you a tool for transfering passwords easily by copying the SQL value in the database field from one core database to another.

However this tool is not very user friendly when it comes to selecting the users. This must be done by selecting one user at the time and clicking ">>" to move the user to the transfer password list (you cannot select many or all in one selection). When you need to transfer passwords of your 2500+ user you just have moved by serialization, this is not a viable solution, if you wish to avoid seriously damages to your hand and fingers :)

If you need to transfer all or all but a few, the solution is simple.

Edit the aspx from the article and in the btnTransfer_Click function let it retrieve the users from your initial list. Then you can remove the few users you wish to not transfer password for to the other list, or simply transfer all of the original users passwords.

So instead of calling:

protected void btnTransfer_Click(object sender, EventArgs e)
    {
        if (lbTransferPasswords.Items != null && lbTransferPasswords.Items.Count > 0)
        {
            int count = UpdatePasswords(tbSQL2.Text, SelectPasswords(tbSQL1.Text, lbTransferPasswords.Items));
            SetErrorMessage(Color.Green, String.Format("{0} user passwords were transferred successfully!", count));
            btnRefresh_Click(sender, e);
        }
        else
        {
            SetErrorMessage(Color.Red, "The list of users whose passwords will be transferred cannot be empty!");
        }
    }

Change it to:

protected void btnTransfer_Click(object sender, EventArgs e)
    {
        if (lbUsersIntersect.Items != null && lbUsersIntersect.Items.Count > 0)
        {
            int count = UpdatePasswords(tbSQL2.Text, SelectPasswords(tbSQL1.Text, lbUsersIntersect.Items));
            SetErrorMessage(Color.Green, String.Format("{0} user passwords were transferred successfully!", count));
            btnRefresh_Click(sender, e);
        }
        else
        {
            SetErrorMessage(Color.Red, "The list of users whose passwords will be transferred cannot be empty!");
        }
    }

Thursday, 10 November 2016

Sitecore WFFM not sending emails

Some weeks ago, I had an issue with the Sitecore Web Forms For Marketers (WFFM) module related to the Save Actions and sending out Email messages. All the data collection was handled in MongoDB correctly, but in some of the forms no emails was send.

When the WFFM did not send out email messages, I found the following exception message in the log file:

"Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.Forms.Core
at Sitecore.Form.UI.Adapters.ListControlAdapter.AdaptToFriendlyListValues(FieldItem field, String value, Boolean returnTexts)
at Sitecore.Form.UI.Adapters.ListControlAdapter.AdaptToFriendlyValue(FieldItem field, String value)
at Sitecore.Form.Core.Utility.FieldReflectionUtil.GetAdaptedValue(FieldItem field, String value)
at Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage.ExpandTokens(ProcessMessageArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Form.Core.Submit.SubmitActionManager.ExecuteSaving(ID formID, ControlResult[] list, ActionDefinition[] actions, Boolean simpleAdapt, ID sessionID)"


Taking a look at the exception it shows some problems with the AdaptToFiendlyListValues method. So I knew it had something to do with List fields types. 

Looking at one of the WFFM List field item at a WFFM form the list data was stored in the “Localized Parameters” field. It seemed all good and fine:

  • %3cquery+t%3d%22default%22+sov%3d%22true%22%3e%3cvalue%3eyes%3c%2fvalue%3e%3c%2fquery%3e%3cquery+t%3d%22default%22+sov%3d%22true%22%3e%3cvalue%3eno%3c%2fvalue%3e%3c%2fquery%3e


Reflecting the “Sitecore.Form.UI.Adapters.ListControlAdapter.AdaptToFriendlyListValues” in Sitecore.Forms.Core.dll:




The Regex.Match in line 21 expect a decoded value – but the value stored in the “Localized Parameters” field was encoded.

Decoding and saving the value stored in the “Localized Parameters” field of the List field item, the value would be:

  • "<items><query sov="true" t="default"><value>yes</value></query><query sov="true" t="default"><value>no</value></query></items>"

The form could now send out emails regarding the field types.

I created a Sitecore Support Ticket and Sitecore responded quickly and provided me with a Support dll. 

To track the future status of this bug report go to:



Related to WFFM 8.0 rev. 151127

Wednesday, 3 February 2016

Logging User Changing Password

How to extend Sitecore to log information, when user change password.

In Sitecore, you can subscribe to a significant number of default events and define if and how Sitecore should log information about the events (none, low, medium and high). From Sitecore v 8.1 the events are listed in the Sitecore config file located at “/App_Config/Sitecore.config” (previous versions of Sitecore the events are listed in the web.config file).

Sitecore logs different information about the user, when the user is created, deleted, when user information is updated, whenever a user logging in and out of Sitecore. There is no event about changing a user password, though.

If Sitecore should log information about change password for a specific user, one way to do this, is to override the System.Web.Security.SqlMembershipProvider.ChangePassword, add Sitecore log information to the ChangePassword method and change the SQL membership provider.

 The include file changing the membership provider:

    <membership defaultProvider="sitecore" hashAlgorithmType="SHA1">
      <providers>
        <add name="sql" type="MyExtensions.Providers.MyExtendedSqlMembershipProvider" connectionStringName="core" applicationName="sitecore" minRequiredPasswordLength="1" minRequiredNonalphanumericCharacters="0" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="256"  xdt:Locator="Match(name)" xdt:Transform="Replace" />
      </providers>
    </membership>


The override of the System.Web.Security.SqlMembershipProvider.ChangePassword:

namespace MyExtensions.Providers
{
    public class MyExtendedSqlMembershipProvider: System.Web.Security.SqlMembershipProvider
    {
        // Added logging for change password.
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            var flag = base.ChangePassword(username, oldPassword, newPassword);
            var msg = "";

            if (flag)
            {
                msg = string.Format("AUDIT Password changed for '{0}' by '{1}'", username, Sitecore.Context.User.Name);
            }
            else
            {
                msg = string.Format("AUDIT Change password failed for '{0}' by '{1}'", username, Sitecore.Context.User.Name);
            }

            Sitecore.Diagnostics.Log.Info(msg, this);

            return flag;
        }
    }
}