Thursday, December 09, 2010

Assembly Version Conflicts for Newtonsoft.Json, Telerik, ComponentArt, etc.


Hi there,

Most of you know that Sitecore CMS relies on a few 3rd party components that help providing compelling web experience. A few come to mind: Telerik’s RTE, ComponentArt’s grids, JSON for .NET utility from Newtonsoft, Lucene.NET, etc.

Since earlier Sitecore versions (pre 6.3) rely on older versions of these 3rd party assemblies and most implementers are logically striving for using latest and greatest, conflicts of assembly versions arise. And strangely enough, I am hearing about these issues quite frequently now.

So how can these conflicts can be resolved?

Well, since an option of registering those in GAC is almost always a big NO-NO, the following technique proved to be workable for a few customers. It is called assembly version redirection.

What you need to do is add the following into the web.config’s <assemblyBinding>. The following example covers the “Newtonsoft.Json” assembly, however this is applicable to any other 3rd party assembly:

<dependentAssembly>
   <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
   <codeBase version="3.5.0.0" href="C:\wwwroot\Sitecore\WebSite\new_bin\Newtonsoft.Json.dll"/>
</dependentAssembly>

Afterwards, feel free to reference the newer version in your Visual Studio project, just remember to set the “Copy Local” property to False.

Note that since the release of Sitecore 6.3, most of those 3rd party assemblies were updated, so this article may be completely useless to you. Just make sure to check the exact Sitecore version you are using on the login page!

As always, thanks goes to our tech support for finding such a plausible alternative.

Wednesday, December 08, 2010

Treelist Not Registering Links in Sitecore


Greetings,

If you work with the Treelist field extensively like I do, you may notice that it is not registering links under certain conditions. You may experience this when search is not coming back with results, when working with Link Database, etc. Also the issue is happening when you have a complex source parameter passed to your treelist field on the template:

DataSource=/sitecore/content/Home&IncludeTemplatesForDisplay=PrimaryPage

Luckly, there are two solutions from our brilliant tech support.

1st option:
Add “databasename=master” attribute to every treelist field source:
DataSource=/sitecore/content/Home&IncludeTemplatesForDisplay=PrimaryPage&databasename=master

2nd option:
Override the Sitecore.Data.Fields.MultilistField class:

    1) Compile the following class and put the built assembly to the “/bin” folder:

public class MultilistField : MultilistField
{
    public MultilistField(Field innerField) : base(innerField) { }

    private Database GetDatabase()
    {
        string source = base.InnerField.Source;
        if (!string.IsNullOrEmpty(source))
        {
            if (!LookupSources.IsComplex(source))
            {
                return base.InnerField.Database;
            }
            Database database = LookupSources.GetDatabase(source);
            if (database != null)
            {
                return database;
            }
        }
        return base.InnerField.Database;
    }

    public override void ValidateLinks(LinksValidationResult result)
    {
        Database database = this.GetDatabase();
        if (database != null)
        {
            foreach (string str in base.Items)
            {
                if (ID.IsID(str))
                {
                    ID id = ID.Parse(str);
                    if (!ItemUtil.IsNull(id) && !id.IsNull)
                    {
                        Item targetItem = database.GetItem(id);
                        if (targetItem != null)
                        {
                            result.AddValidLink(targetItem, base.Value);
                        }
                        else
                        {
                            result.AddBrokenLink(base.Value);
                        }
                    }
                }
            }
        }
    }
}

2) Modify the \App_Config\FieldTypes.config file, specifically the Treelist field type definition:

<fieldType name="Treelist" type="Custom.MultilistField,Custom" />

Enjoy!

Wednesday, November 17, 2010

Previewing Sitecore Reports with Windows Report Designer


You probably know that you can edit Sitecore OMS reports outside of the browser with Windows based tools from Stimulsoft available for download here.

Our Report Designer Cookbook on SDN documents the following benefits of the Windows based Designer:

  • Create new reports from scratch
  • Use wizards to quickly create new reports
  • Reports load quickly (the client does not run in a browser window)
  • Use a number of keyboard shortcuts (including Ctrl Z)
  • Preview functionality
  • Create complex SQL queries using the Query Builder
  • Import and export formatting styles

It turns out that the “Preview” functionality does not, you may be seeing the following errors when trying to preview:

image

What you need is to copy the following DLLs from your Sitecore bin folder into the bin directory where Report Designer is installed on your workstation (C:\Program Files (x86)\Stimulsoft Reports.Net 2010.2 Trial\Bin):

  • Sitecore.Oracle.dll
  • Sitecore.Analytics.dll

After you do that, you will be able to preview the reports just fine:

image

Happy reporting!

Friday, November 12, 2010

Sitecore Searcher and Advanced Database Crawler


Hi there,
Today I am proud to announce a preview release of a component that extends the standard Sitecore Searching mechanisms, specifically, the relatively “new” Sitecore.Search namespace introduced in 6.0 and provides easy search querying APIs. If you are not sure what I am talking about, check out this recently published document on SDN and also Ivan’s blog posts about it.

Friday, November 05, 2010

Resetting Sitecore Staging Module


Greetings,
A quick troubleshooting post on Friday to wrap up the work week Winking smile

This time about our favorite Staging module. This applies to all pre Sitecore 6.3 installations as the latest version does not require this module.

So I have recently witnessed a problem that our customer was facing. The module being stuck during file upload process, giving out a pretty generic error from the Staging log files:

11/2/2010 1:20:29 PM (ER): Could not Execute file upload: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.

It turns out that sometimes the module may not output the most meaningful information to the logs, so the error may not be really helpful here.
For example, this could be caused by missing ACL permissions on the files that the module is synchronizing, etc. By the way, a useful thing to double check – make sure the application pool account has read/write/modify on the folder which are configured to be synchronized.

Here are some quick things you can do fairly to “reset” the module configuration:

1. Delete any files under upload, download and cache sub-folders within the working directory (/sitecore modules/staging/workdir). These are the temp files automatically generated, so it is safe to do this.
2. Clear the value of the “Last updated” field on the appropriate StagingServer item in your master db:

image3. Run any type of publishing to trigger the Staging operation again.
4. Monitor Staging logs which are located within the working directory.

Also, check out this useful troubleshooting resource on Staging module troubleshooting.

Thursday, November 04, 2010

Approaching Language Fallback with Sitecore


Greetings,
Today I would like to present to you a prototype I have been working over past couple of weeks. This solution is built around a story about language fallback requirements of a fictitious multinational company. This is just an example of how you can approach similar requirement.
There is a number of alternative solutions that the Sitecore gurus from all over the world came up with, so you could naturally ask me: what is so special about this approach?

Wednesday, October 27, 2010

Securing Sitecore Admin


Greetings,

One of the frequent questions I am hearing besides development related stuff is configuration related. In Sitecore world, there is always plenty of options available for you in terms of configuring your production environment. Not to get carried away, but this is really a critical aspect, especial for large enterprises. When your product cannot be flexible enough to be decoupled in components, this may represent quite a challenge. Systems forcing large footprint are more difficult to maintain, backup, secure, etc.
With Sitecore, you can pretty much create a lightweight Content Delivery instance by cutting down the configuration and files to mere 50 Mb quite with a little bit of effort. This will create a more manageable and secure environment, but what if you don’t want to go through this exercise?

A quick and proven way to handle this it rely on native IIS securing features. With IIS7 you can do that even easier. What you can do is simply deny access to /sitecore folder based on IP restrictions.

1. Make sure you have “IP Security” feature installed for IIS:

image

2. Locate your site in IIS, select /sitecore folder:

image

3. On the Features view, select “IP Address and Domain Restrictions”:

image

4. Configure any allow/deny rules you want:

image

Isn’t it easy?

Monday, October 25, 2010

Undefined text in Page Editor when clearing field


Greetings!

You may be experiencing an issue with Page Editor in Sitecore 6.1/6.2 releases when clearing a field results in “Undefined” text being written back to your item.

To work this around, follow these 3 easy steps.

1. Locate the following file: <web root>\sitecore\shell\Applications\WebEdit\WebEditElement.js
Create a backup copy of it.

2. Find the following function: Sitecore.WebEdit.Element.prototype.load = function() and replace it with this:

Sitecore.WebEdit.Element.prototype.load = function() {
  if (this.element.getAttribute("scWatermark") == "true") {
    this.element.removeAttribute("scWatermark");
    this.watermarkHTML = this.element.innerHTML;
  }else
  {
  this.watermarkHTML = "[No text in field]";
  }
this.buttons.select(".scWebEditFrameButtonIcon").each(function(icon) {
    icon.observe("dragstart", function(e) { e.stop(); });
  });
  this.element.observe("click", this.onClick.bind(this));
  this.element.observe("blur", this.onBlur.bind(this));
}

3. Then locate this function: Sitecore.WebEdit.Element.prototype.isWatermark = function() {
and replace it with this:

Sitecore.WebEdit.Element.prototype.isWatermark = function() {
  fl=false;
  if((this.watermarkHTML == this.element.innerHTML)||(this.element.innerHTML=="[No text in field]"))
               {
               fl=true;
               }
  return fl;
}

Quick disclaimer: this proved working on my local 6.2.0 (rev. 100831) installation, however, this is not a fully tested solution, so there may be conflicts depending on exact version of your Sitecore instance and level of customization.

Thanks goes to Sitecore Customer Service for quick solution!

Monday, October 18, 2010

Sitecore 6.4 in Technical Preview


mill valley_resized 
Monday…it is a wonderful day in our Mill Valley office…I am in the mood of writing something non-technical for once, so no pipelines, custom commands or event handlers today.

That’s because a truly great piece of news hit the web today. Sitecore 6.4 is out in Technical Preview. If you have not seen Alistair’s early Sneak Peak on 6.4 post and John West’s blog series on new features, here is a quick teaser for you on what’s in the box:

  • Updated Page Editor merged with Page Designer. Try it out on your iPad!
  • Cross browser support
  • New Rich Text Editor
  • Proxies deprecated in favor of Item Cloning
  • .NET 4.0
  • Initial support for MVC
  • More extensibility through additional pipelines
  • Much more

Check out Release disclaimer.

Besides all the technological goodies included in this release, I believe that the next coolest thing about it is how Sitecore approached it. Every single feature or enhancement is purely based on your feedback. Developers wanted .NET 4.0 and MVC, even more freedom in customizing the software. Business Users wanted better behaving Rich Text Editor and enhanced Page Editor/Design experience. Everyone wanted to work in favorite browser.
Sitecore listened and executed.

So what does it mean for you, fellow Sitecorian? Now it is your chance – download, install and provide that valuable feedback as early as possible.
6.4 will be one of the coolest versions we’ve ever had, there is no doubt about that!

Friday, October 15, 2010

How to Sort your Multilist Field


Greetings!

Just recently one of the Sitecore implementation partners asked me whether there is a way to have the list of available items on a Multilist field sorted by updated date.
To clarify, by default, the left side is sorted by item name, it is by design.

image

So I did some digging, and have found that it is a very frequent feature request. Another thing I’ve found is that since Sitecore 6 it is actually pretty easy to change this behavior.

First thing I need for that is subclass MultilistEx class which implements is UI part of the Multilist field. This class has a method called GetSelectedItems (introduced since Sitecore 6 release) where the list of unselected items is constructed. Since this method is marked as protected virtual, I can easily override it:

namespace Sitecore.SharedSource.CustomFields
{
   public class SortedMultilist : Sitecore.Shell.Applications.ContentEditor.MultilistEx
   {
      protected override void GetSelectedItems(Item[] sources, out ArrayList selected, out IDictionary unselected)
      {
         Assert.ArgumentNotNull(sources, "sources");
         ListString str = new ListString(this.Value);
         unselected = new SortedList(StringComparer.Ordinal);
         selected = new ArrayList(str.Count);
         int index = 0;
         while (index < str.Count)
         {
            selected.Add(str[index]);
            index++;
         }
         foreach (Item item in sources)
         {
            string str2 = item.ID.ToString();
            index = str.IndexOf(str2);
            if (index >= 0)
            {
               selected[index] = item;
            }
            else
            {
               unselected.Add(GetItemKey(item), item);
            }
         }
      }
      protected virtual string GetItemKey(Item item)
      {
         return MainUtil.GetSortKey(item[FieldIDs.Created]);
      }
   }
}

The only thing I change, actually, is the line where items are added to the “unselected” list. Since the key is used for sorting, I move this outside to a separate method “GetItemKey” where instead of constructing sort key by item.Name as in default implementation, I simply read the “created” date.

After this class is compiled, all I need to define the new “Sorted Multilist” field under core:/sitecore/system/Field types/List Types:

image

The easiest way to do it is to duplicate the existing Multilist item ;-)

I should mention that adding complex sorting rules will definitely slow down you multilist, so you have to be very careful.

Also, there is a very handy FilteredMultilist that is already on shared source. It gives you ability to refine the left side of Multilist, so you may never need it sorted at all.

no_filter[1]

Enjoy!

Friday, October 01, 2010

Sitecore Bloggers take over the world


I just want to give a quick shout-out to all Sitecore bloggers out there who bring new fresh ideas to the surface, provoke discussions and constantly provide food for thought. The best thing about it is that you get different perspective from different levels: end user’s, developer’s (either customer or partner) and Sitecore employee’s.
I am absolutely confident that information space around Sitecore would never be as active and relevant without you guys.

Also I want to take this opportunity and urge my readers to check out our new Sitecore Community Blogs section on sitecore.net. If you are leaning towards more technical stuff, the place to go would be our Technical Blogs area.

Of course, if you have not subscribed to our RSS channel that aggregates all Sitecore related blogs, you are really missing out!

Monday, September 27, 2010

Image Field in Page Editor [Common Errors]


This issue is related to the latest *6.2 rev.100831 (Update-4)* only.

You may be getting the following error when launching Page Editor on a page with an image field rendered via <sc:image /> Web or XSL Control:

at Sitecore.Diagnostics.Assert.ArgumentNotNull(Object argument, String argumentName)
  at Sitecore.Resources.Media.MediaProvider.GetMediaUrl(MediaItem item, MediaUrlOptions options)
  at Sitecore.Xml.Xsl.ImageRenderer.GetSource()
  at Sitecore.Xml.Xsl.ImageRenderer.Render()
  at Sitecore.Pipelines.RenderField.GetImageFieldValue.Process(RenderFieldArgs args)
  at (Object , Object[] )
  at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
  at Sitecore.Web.UI.WebControls.FieldRenderer.RenderField()
  at Sitecore.Xml.Xsl.XslHelper.image(String fieldName, XPathNodeIterator iterator, String parameters)

While our team is working on an official document about, here is a quick 3 step solution:

1. Create a custom ImageRenderer class:

using System;
using Sitecore.Data.Fields;
namespace SitecoreSupport.Shell
{
   public class ImageRenderer : Sitecore.Xml.Xsl.ImageRenderer
   {
      protected override string GetSource()
      {
         var innerField = Item.Fields[FieldName];
         var imageField = new ImageField(innerField, FieldValue);
         ParseField(imageField);
         if (imageField.MediaItem != null)
         {
            return base.GetSource();
         }
         return String.Empty;
      }
   }
}

2. Create a custom implementation of GetImageFieldValue processor in the renderField pipeline:

namespace SitecoreSupport.Shell
{
   public class GetImageFieldValue : Sitecore.Pipelines.RenderField.GetImageFieldValue
   {
      protected override Sitecore.Xml.Xsl.ImageRenderer CreateRenderer()
      {
         return new ImageRenderer();
      }
   }
}

3. Create a .config file with the following content and place it under /App_Config/Include:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
   <sitecore>
      <pipelines>
         <renderField>
            <processor type="Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel">
               <patch:attribute name="type">SitecoreSupport.Shell.GetImageFieldValue, SitecoreSupport.Shell</patch:attribute>
            </processor>
         </renderField>
      </pipelines>
   </sitecore>
</configuration>

Monday, September 20, 2010

Optimize Sitecore Performance Checklist


Greetings! I’ve stumbled upon this hot discussion on StackOverflow about Sitecore CMS performance and could help but sharing a few thoughts on the subject.
The message I am trying to convey is that the product is built for performance and scalability. After seeing a fair number of successful implementations that have the unlimited scaling capacity, it is sad for me to see some developers struggle.
We all love checklists, so here is a slightly modified list of things to look out for and try out when you start having performance degradation. Each case is special, so apologies if most of this is not applicable to your situation.

Friday, September 10, 2010

Campaign Summary report is throwing exception [Common Errors]


A quick post today in the Common Errors category. You may be getting the following exception when trying to run Summary report on your Campaign:

image


Object reference not set to an instance of an object.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Stack Trace:

[NullReferenceException: Object reference not set to an instance of an object.]
   Stimulsoft.Report.StiReport.GetReferencedAssemblies() +1179
...

To resolve this, you need a reference to Sitecore.Oracle.dll within the source of Campaign Summary report.

1. Open the following file: \sitecore\shell\Applications\Analytics\Reports\Campaigns\Summary.mrt

2. Add the following to the <ReferencedAssemblies /> section:
image

That’s all!

Friday, September 03, 2010

Optimizing Sitecore Link Database Rebuild


Greetings! One of the topics I covered during my Dreamcore presentation on Data Access Techniques with Sitecore CMS was Link Database. While it is a great feature that enables fast and efficient retrieval of item relations, there is one caveat, it requires some maintenance and configuration to be working properly in multi server environment. Many customers face this challenge, and while there is a number of ways you can tackle this, the approach I want to share today seems like the most appropriate to me.

The idea behind the scenes is to separate the Link Database used on the Content Delivery side from the one used on the Content Management side by introducing a second link database. This database will be updated during item publishing. This way if CMS decides to update its own Link DB, the one used on the front-end will not be affected.

So, here is what you need to do:

1. Add the FrontEndLinkDatabase definition below the default <LinkDatabase> in web.config file of the CMS server:

<!-- LINK DATABASE -->
<LinkDatabase type="Sitecore.Data.$(database).$(database)LinkDatabase,
                    Sitecore.Kernel">
   <param connectionStringName="core" />
</LinkDatabase>
<!-- FRONT END LINK DATABASE -->
<FrontEndLinkDatabase type="Sitecore.Data.$(database).$(database)LinkDatabase,
                            Sitecore.$(database)">
   <param desc="connection" ref="connections/web" />
</FrontEndLinkDatabase>

2. Compile the following code that contains two Sitecore event handlers (publish:itemprocessed and publish:itemprocessing)

using System;
using Sitecore.Configuration;
using Sitecore.Links;
using Sitecore.Publishing;
using Sitecore.Publishing.Pipelines.PublishItem;
using Sitecore.Diagnostics;
namespace Sitecore.SharedSource.LinkDatabaseRebuilder
{
   public class EventHandler
   {
      protected static LinkDatabase FrontEndLinkDatabase
      {
         get
         {
            return (Factory.CreateObject("FrontEndLinkDatabase", true) 
                     as LinkDatabase);
         }
      }
      public string Database { get; set; }
      public void OnItemProcessed(object sender, EventArgs args)
      {
         var context = ((ItemProcessedEventArgs)args).Context;
         Assert.IsNotNull(context, 
         "Cannot get PublishItem context");
         Assert.IsNotNull(FrontEndLinkDatabase,
         "Cannot resolve FrontEndLinkDatabase from config");
         if (context.PublishOptions.TargetDatabase.Name.Equals(Database))
         {
            var item = context.PublishHelper.GetTargetItem(context.ItemId);
            // if an item was not unpublished, 
            // the call below will reintroduce the reference
            // removed within OnItemProcessing method
            if (item != null)
            {
               FrontEndLinkDatabase.UpdateReferences(item);
            }
         }
      }
      public void OnItemProcessing(object sender, EventArgs args)
      {
         var context = ((ItemProcessingEventArgs)args).Context;
         Assert.IsNotNull(context,
         "Cannot get PublishItem context");
         Assert.IsNotNull(FrontEndLinkDatabase, 
         "Cannot resolve FrontEndLinkDatabase from config");
         if (context.PublishOptions.TargetDatabase.Name.Equals(Database))
         {
            if (context.Action == PublishAction.DeleteTargetItem)
            {
               var item = context.PublishHelper.GetTargetItem(context.ItemId);
               Assert.IsNotNull(item, "Source item cannot be found");
               FrontEndLinkDatabase.RemoveReferences(item);
            }
         }
      }
   }
}

3. Last thing to do is to define the two event handler mapping in web.config.

<events>
   <event name="publish:itemProcessing">
    <handler type="Sitecore.SharedSource.LinkDatabaseRebuilder.EventHandler, 
Sitecore.SharedSource.LinkDatabaseRebuilder"
method="OnItemProcessing">
      <database>web</database>
     </handler>
   </event>
   <event name="publish:itemProcessed">
     <handler type="Sitecore.SharedSource.LinkDatabaseRebuilder.EventHandler,
Sitecore.SharedSource.LinkDatabaseRebuilder"
method="OnItemProcessed">
       <database>web</database>
      </handler>
   </event>
 </events>

That’s all you need to do. For the sake of simplicity, here is the link to the whole VS project where you can find the auto-include file.

While this solution has been tested on 6.2, it is expected to work on all Sitecore 6 installations.

Let me know if it does not ;-)

Thursday, September 02, 2010

Publish to pre-production web database. Part 2.


A while back I shared a technique of publishing to a pre-production database via a custom ExtendedPublishProvider. As much as I like the clarity of this approach, as Kamsar mentioned in the comments, “…it has a downside: if you run a smart publish site to your staging database it will "unstage" any items that workflow staged into them with the current published version, ignoring the workflow state.”

Valid point.

After some internal brainstorming on the subject with our US based tech team, here is another approach to the problem that eliminates the drawback mentioned above.

What you can actually do is plug in a custom WorkflowProvider which will refer to a custom implementation of Sitecore.Workflows.Simple.Workflow class. This class has a method IsApproved(Item item) that is called from all publishing operations. We are going to override that and check if the item we are checking approval for is currently in a “semi-final” workflow state and if the database we publish to matches the designated “pre-production” database. Pretty simple, huh?

Here is the code.

1. First you need a custom version of the WorkflowProvider implementation:

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Workflows;
namespace Sitecore.Starterkit.Workflow
{
   public class WorkflowProvider : Sitecore.Workflows.Simple.WorkflowProvider
   {
      public WorkflowProvider(string databaseName, HistoryStore historyStore)
         : base(databaseName, historyStore)
      {
      }
      public override IWorkflow GetWorkflow(Item item)
      {
         Assert.ArgumentNotNull(item, "item");
         string workflowID = GetWorkflowID(item);
         if (workflowID.Length > 0)
         {
            // customization
            return new AdvancedWorkflow(workflowID, this);
            // customization
         }
         return null;
      }
      private static string GetWorkflowID(Item item)
      {
         Assert.ArgumentNotNull(item, "item");
         WorkflowInfo workflowInfo = item.Database.DataManager.GetWorkflowInfo(item);
         if (workflowInfo != null)
         {
            return workflowInfo.WorkflowID;
         }
         return string.Empty;
      }
      public override IWorkflow GetWorkflow(string workflowID)
      {
         Assert.ArgumentNotNullOrEmpty(workflowID, "workflowID");
         Error.Assert(ID.IsID(workflowID), "The parameter 'workflowID' must be parseable to an ID");
         if (Database.Items[ID.Parse(workflowID)] != null)
         {
            // customization
            return new AdvancedWorkflow(workflowID, this);
            // customization
         }
         return null;
      }
      public override IWorkflow[] GetWorkflows()
      {
         Item item = this.Database.Items[ItemIDs.WorkflowRoot];
         if (item == null)
         {
            return new IWorkflow[0];
         }
         Item[] itemArray = item.Children.ToArray();
         IWorkflow[] workflowArray = new IWorkflow[itemArray.Length];
         for (int i = 0; i < itemArray.Length; i++)
         {
            // customization
            var wfId = itemArray[i].ID.ToString();
            workflowArray[i] = new AdvancedWorkflow(wfId, this);
            // customization
         }
         return workflowArray;
      }
   }
}

2. Then you will need to attach it to the “master” database:

<!-- master -->
<database id="master" singleInstance="true" type="Sitecore.Data.Database, Sitecore.Kernel">
   <param desc="name">$(id)</param>
   ...
   <workflowProvider hint="defer" type="Sitecore.Starterkit.Workflow.WorkflowProvider, Sitecore.Starterkit">
      <param desc="database">$(id)</param>
      <param desc="history store" ref="workflowHistoryStores/main" param1="$(id)" />
    </workflowProvider>

 

3. Since the custom WorkflowProvider is referring to AdvancedWorkflow implementation, you need the code for that:

using System;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.SecurityModel;
using Sitecore.Workflows;
using Version = Sitecore.Data.Version;
namespace Sitecore.Starterkit.Workflow
{
   public class AdvancedWorkflow : Sitecore.Workflows.Simple.Workflow
   {
      private readonly WorkflowProvider _owner;
      public AdvancedWorkflow(string workflowID, WorkflowProvider owner)
                             : base(workflowID, owner)
      {
         _owner = owner;
      }
      private Database Database
      {
         get
         {
            return _owner.Database;
         }
      }
      public override bool IsApproved(Item item)
      {
         var result = base.IsApproved(item);
         if (!result && 
            Context.Site.Name.Equals("publisher",
                                     StringComparison.InvariantCultureIgnoreCase))
         {
            var stateItem = GetStateItem(item);
            if (stateItem != null &&
                MatchTargetDatabase(stateItem) &&
                IgnoreWorkflow(stateItem))
            {
               result = true;
            }
         }
         return result;
      }
      protected virtual bool MatchTargetDatabase(Item stateItem)
      {
         if (Context.Job != null && !String.IsNullOrEmpty(Context.Job.Name))
         {
            var target = TargetDatabase(stateItem);
            return Context.Job.Name.Equals(
                     String.Format("Publish to '{0}'", target), StringComparison.InvariantCultureIgnoreCase);
         }
         return false;
      }
      protected virtual string TargetDatabase(Item stateItem)
      {
         var publishTargetId = stateItem["Semi-Final Target Database"];
         var publishTargetItem = PublishActionHelper.GetItemById(publishTargetId);
         if(publishTargetItem != null)
         {
            return PublishActionHelper.GetFieldValue(publishTargetItem, "Target database");
         }
         return String.Empty;
      }
      protected virtual bool IgnoreWorkflow(Item stateItem)
      {
         return stateItem["Semi-Final"] == "1";
      }
      private Item GetStateItem(Item item)
      {
         string stateID = GetStateID(item);
         if (stateID.Length > 0)
         {
            return GetStateItem(stateID);
         }
         return null;
      }
      private Item GetStateItem(ID stateId)
      {
         return ItemManager.GetItem(stateId, Language.Current, Version.Latest, Database, SecurityCheck.Disable);
      }
      private Item GetStateItem(string stateId)
      {
         ID id = MainUtil.GetID(stateId, null);
         return id == (ID)null ? null : this.GetStateItem(id);
      }
      private string GetStateID(Item item)
      {
         Assert.ArgumentNotNull(item, "item");
         WorkflowInfo workflowInfo = item.Database.DataManager.GetWorkflowInfo(item);
         if (workflowInfo != null)
         {
            return workflowInfo.StateID;
         }
         return string.Empty;
      }
   }
}

All the magic is happening within “IsApproved” method that we override. We check if both “MatchTargetDatabase()” and “IgnoreWorkflow()” methods return true. The majority of other methods in this class are here because the derived class could not inherit those.

4. I reference PublishActionHelper class here too, it provides some utility methods for me:

 public class PublishActionHelper
   {
      public static Database Db
      {
         get { return Context.ContentDatabase ?? Context.Database ?? Factory.GetDatabase("master"); }
      }
      public static string GetFieldValue(Item item, string fieldName)
      {
         return item[fieldName] ?? String.Empty;
      }
      public static Item GetItemById(string id)
      {
         if (ID.IsID(id))
         {
            return Db.GetItem(ID.Parse(id));
         }
         return null;
      }
      public static string GetTargetDatabaseName(string targetId)
      {
         var publishingTarget = Db.SelectSingleItem(targetId);
         return publishingTarget["Target database"] ?? String.Empty;
      }
      public static IEnumerable<Item> GetItemsFromMultilist(Item carrier, string fieldName)
      {
         var multilistField = carrier.Fields[fieldName];
         if(FieldTypeManager.GetField(multilistField) is MultilistField)
         {
            return ((MultilistField)multilistField).GetItems();
         }
         return new Item[0];
      }
   }

5. Finally, you will need to have the extended version of the WorkflowState template where you can set the needed flags and settings:

image

I created my own “Semi Final State” template which inherits from standard /System/Workflow/State template.

image

This solution has been provided to a customer, and appears to be working fine in production for a couple of months now.

Let me know what you think!