Quantcast
Channel: Sitecore – Ctrl+Z
Viewing all 66 articles
Browse latest View live

Quick fix for Experience Editor

$
0
0

In later versions of Sitecore, there is a new link field called General Link with Search. As the name says, it’s basically the same as the old one but with an extra search option. In later versions of Sitecore, there’s also an out-of-the-box fix for clearing links in the Experience Editor, implemented in the same way as sitecoreclimber blogged about a few months ago.

ClearLinkHowever, in a clean Sitecore 8 install, the “Clear Link” web editor button item is missing in the core database for General Link with Search. You can solve this by just copying the existing command from General Link.


TDS Sync settings

$
0
0

After a fresh install of my whole dev environment onto a new nice machine, I realised that Hedgehog Team Development for Sitecore (TDS) isn’t by default configured the way I like it.

In the Options window in Visual Studio, there is a section for TDS Options. In the Sync Window you can specify a set of field names that the Sync with Sitecore should ignore when comparing Sitecore items with your project items. By default, TDS ignores “__Created by“, “__Owner“, “__Revision“, “__Updated” and “__Updated by” which makes perfectly sense. Essentially, this means that if you open an item in Sitecore and just hit Save, some of those fields will be changed but nothing else. Therefore is no meaning in synchronising those items with you project, unless other fields are changed as well.

TDS Sync settings

For some reason the “__Created” field isn’t there by default and I guess one could argue that it should be, but I found it better to ignore that field as well. The main reason for that is as projects evolve, we may upgrade Sitecore to a newer version or a new team member may join that hasn’t followed the same upgrade path as the rest of the team. Instead (s)he just installs the current version used by everyone else in the team. In this case, Sitecore items will have new Created dates and therefore a whole bunch of items are marked as different from the ones you have in the project.

TL;DR In Visual Studio, open Options, navigate to TDS Options and add “__Created” in the Sync window.

An odd Sitecore field validator

$
0
0

Today I faced a Sitecore website rollout problem I’ve never thought of before. When doing a multi language/multi market solution, it’s common to use cloning or other kinds of fallback techniques. This is typically very good, but sometimes it brings some new problems as well.

The scenario I faced was that I wanted to make sure that certain fields did not inherit any value from standard values, default values or via cloning. In my particular case, I wanted to ensure that certain integration settings where defined for each website, even if the sites were cloned. Maybe not a very common requirement, but here’s another example:

Let’s say you have a field that stores a Google Analytics UA code, maybe that code should be defined on each locations where it’s used. Not inherited between sites. (Depending on how you’ve configured GA of course.)

A simple way of solving this is to create a Field Validator that tests this. Here’s some sample code on how to implement such validator:

public class FieldDoesNotInheritValueValidator : StandardValidator
{
	protected override ValidatorResult Evaluate()
	{
		var field = GetField();
		if (field == null)
			return ValidatorResult.Valid;

		// Get field value without resolving fallbacks
		if (field.GetValue(false, false) != null)
			return ValidatorResult.Valid;

		Text = GetText("The field \"{0}\" does not contain a value.", GetFieldDisplayName());
		return GetFailedResult(ValidatorResult.Warning);
	}

	protected override ValidatorResult GetMaxValidatorResult()
	{
		return ValidatorResult.Warning;
	}

	public override string Name
	{
		get { return "Field does not inherit value"; }
	}
}

Just add a new Validation Rule item among the master:/sitecore/system/Settings/Validation Rules/Field Rules and define the class above Your.Namespace.FieldDoesNotInheritValueValidator, Your.Assembly as any other Field validation rule

Multiple item reference indicator in Sitecore Experience Editor

$
0
0

In Sitecore, we build pages adding renderings to a page. Renderings typically have datasources pointing to items carrying the content. Thereby it’s really easy to reuse content. In a scenario where a piece of content is used on multiple pages, it’s maybe not clear to content author that changes to the content will actually affect more pages.

So I wrote a little indicator that will show a warning indicator in the Sitecore Experience Editor (aka Page Editor) when the content of a rendering is being used on other pages as well.

WebEdit warning signThere are probably many ways of doing this, but I just did a simple one by adding a WebEdit button and hide it when there aren’t multiple usages of a referenced item. Using the QueryState method, we can query the LinkDatabase and find items referenced by the LayoutFiled or the FinalLayoutField, and just hide the button if there are other pages than the current one referencing the item.

Below is some sample code that does it. Only the QueryState method is needed for the indicator itself. The code below also fires a dialog that will display a list items referencing the content. But I won’t go into that part now. That’s a future blog post.

using System;
using System.Collections.Specialized;
using System.Linq;
using Sitecore;
using Sitecore.Shell.Applications.WebEdit.Commands;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Text;
using Sitecore.Web.UI.Sheer;

namespace Stendahls.Sc.EditorExtensions.Commands
{
    [Serializable]
    public class MultipleUsagesNotification : WebEditCommand
    {
        public override CommandState QueryState(CommandContext context)
        {
            if (context.Items == null || context.Items.Length != 1)
                return CommandState.Hidden;

            if (context.Parameters == null)
                return CommandState.Hidden;

            var item = context.Items[0];
            var referrers = Sitecore.Globals.LinkDatabase.GetReferrers(item);
            var renderingReferences = referrers.Where(r => r.SourceFieldID == FieldIDs.LayoutField || 
                r.SourceFieldID == FieldIDs.FinalLayoutField).ToList();

            if (renderingReferences.Count() > 1)
            {
                var currentItem = Sitecore.Context.Item;
                var otherPages = renderingReferences.Where(r => r.SourceItemID != currentItem.ID).ToList();

                return otherPages.Count > 0 ? CommandState.Enabled : CommandState.Hidden;
            }
            return CommandState.Hidden;
        }

        public override void Execute(CommandContext context)
        {
            var item = context.Items[0];
            var parameters = new NameValueCollection();
            parameters["id"] = item.ID.ToString();
            parameters["database"] = item.Database.Name;

            Sitecore.Context.ClientPage.Start(this, "Run", parameters);
        }

        protected void Run(ClientPipelineArgs args)
        {
            if (args.IsPostBack)
                return;
 
            var url = new UrlString(UIUtil.GetUri("control:MultipleUsagesNotificationDialog"));
            if (args.Parameters["database"] != null)
            {
                url["database"] = args.Parameters["database"];
            }
            if (args.Parameters["id"] != null)
            {
                url["id"] = args.Parameters["id"];
            }
            SheerResponse.ShowModalDialog(url.ToString(), true);
            args.WaitForPostBack();
        }
    }
}

Here’s how it’s added into the core database:
webedit-button-core-item

Hope you find it useful.

Nordic Sitecore Conference summary

$
0
0

Nordic Sitecore ConferenceHome again after a long Nordic Sitecore Conference day at Sankt Gertrud in Malmö, Sweden. A fully packed day with great topics, speakers and a few minutes of networking in between the sessions.

Lars Nielsen started the day with a keynote about the Sitecore road map so far and the schedule of the upcoming 8.2 release, commerce, EXM “the mail thing” (aka ECM) and the schedule of upcoming updates of all the other versions as they are being maintained as well. Since it was presented, I assume the upcoming 8.2 next year is not a secret any longer. A lot of bugs has been fixed in the latest updates and more fixes are on the way. I’m really happy that Sitecore is running this initiative not to kill as many issues as possible. I’ll keep feeding the support team 😉

Content has been king for quite some time. With it goes the context queen. To give customers the right experience, the right kind of content has to be served in the right context (device, situation etc) to the right person. Essentially the Sitecore 1:1 vision. To help us achieve this, Todd Mitchell gave us a nice overview of xDB and where it’s heading.

Emil Okkels Klein shared his experiences on how to succeed with Sitecore as a platform. Good reminders that’s worth repeating over and over again. Some key takeaways are that the platform goal has to be well sold in and communicated within the client organization. Editors are a first person citizen and should be treated as one. If you just think you know how their daily work look like, then spend a day observing it. It’ll be worth it. Finally, ensure everything is tested before deployment.

After lunch, Stephen Pope took us through an amazing journey on using some new ASPNET5 components within an existing Sitecore solutions. I never thought that was even possible at this stage. The future of DI in Sitecore looks really promising. And some nice anim gifs as always in his presentations.

Unfortunately, Popes session collided with Robbert Hocks session on advanced Sitecore Rocks – Tips & Tricks. But that session will hopefully show up on YouTube pretty soon. Can’t wait :-)

Jens Mikkelsen showed us some live coding on how to build real life SPEAK apps. Impressive to get something working from scratch in such a short time and simultaneously sharing thoughts, tips & tricks. Key takeaway is the A in SPEAK (Sitecore Process Enablement and Acceleration Kit). It’s an Acceleration kit – not a framework. Let it do its job and fill in the gaps and the learning curve will be easier.

Meanwhile, Mike Edwards held a session with a very interesting topic: Making it open source. I’d love to attend that one as well, but unfortunately I haven’t learned how to clone myself. Everything isn’t Items yet.. But I heard a lot of good comments about it afterwards.

Mike Reynolds probably convinced a few people of how powerful Sitecore PowerShell Extensions is. I’ve been using it for some time and is already a believer. If you haven’t used it yet, it’s really about time! Imagine remoting into your production environment and bulk updating things without touching the server code, or provide your authors with reports, gutters, context menu commands etc with almost no effort. Well, don’t imagine. Just do it with SPE.

Martina Welander wrapped up the day with nice insights on how perform text search with Sitecore. Sitecore ContentSearch is really indexing. Search becomes more complex than you may think at first. A very pedagogic way of describing what tokenizers and analyzers do, the differences between search LINQ .Like(), .Contains() and .Equals() methods etc and a little bit about the Predicate builder.

Finally, thanks to our host Alan Coats and all of you guys at Pentia, Cabana and Sitecore that pulled off this great conference!

Managing clone notifications

$
0
0

At yesterdays Nordic Sitecore Conference, I was asked about how we deal with clone notifications in Sitecore. So here I’ll try to do a brief blogpost of my thoughts on this and how we’ve solved it. It may be gaps in here and room for improvements, but perhaps it’s a start to get your solution work well in this area.

Clones in Sitecore has been around for quite some time now and they are really powerful and important, but also causes a lot of pain. The default implementation is quite generic but still quite well thought through. To us, this means it’s a toolbox that is mostly useless until it’s adjusted to the specific project needs. I won’t go into all those details in this post.

Clones uses a Notification concept to notify editors when things happens to related items. For example, when a child item is created of a cloned item, a ChildCreatedNotification is sent to all item clones. When the source of a non-inherited field of a cloned item is changed, a FieldChangedNotification is sent and so on. There are tons of post about this already, so just google it if you need more info about it.

To make the notification behave the way we want them to, I usually find implementing a custom NotificaitonProvider to be the best way. You can simply inherit the SqlServerNotificationProvider and override the AddNotification method. This method will be called for every notification created in the system, so we can tapp into how those are handled before the enter database. There are tons of examples already about this as well.

From the notification object we can decide if we should accept, reject or just pass through the notification. Typically like this:

public override void AddNotification(Notification notification)
{
  var item = ItemManager.GetItem(notification.Uri.ItemID, notification.Uri.Language, notification.Uri.Version,
    Database.GetDatabase(notification.Uri.DatabaseName));

  if (ShouldAutomaticallyAcceptChanges(notification, item))
  {
    notification.Accept(item);
  }
  else if (ShouldAutomaticallyRejectChanges(notification, item))
  {
    notification.Reject(item);
  }
  else
  {
    base.AddNotification(notification);
  }
}

The two methods, ShouldAutomaticallyAcceptChanges and ShouldAutomaticallyRejectChanges, you can implement logic that suits your site. For example, certain changes should be automatically be accepted, such as centrally managed content. Local content, such as news or integrated content, you may want to automatically reject notifications and so on.

We can do this with any custom code, or we can do this in a very generic way or a combination of the two. I added some Tristate fields on my templates and fields, such as ItemCreatedNotification and FieldChangedNotification. The first I added to templates so I can set this value on the template standard values. The other is an extension to the Fields template so that I can control each individual field.

Clone Notifications section added to website templates standard values:
Clone Notifications settings on Template standard values

Field clone notifications setting added to “Template field” template:
Field level clone notification option

With this, we get much more control over our notifications and the amount notifications actually sent to users becomes a much more manageable volume.

However, there is one more problem with the nature of Notifications. They are of a fire-and-forget type. When an editor receives a notification, he or she would just answer accept or reject and there is really no way to naturally change that decision. Sometimes there is a need to “re-create” those, or perhaps clean up some that has been created accidentally.

So, say hi to DevOps best friend SPE, Sitecore PowerShell Extensions!

With SPE we can easily correct notifications at any time, even on the fly in a production environment without fiddle with code deployments etc. You can even make custom commands in the UI that would synchronize parts of a clone branch with certain rules etc. The possibilities are endless.

# Below is basically just fake data. Don't even try this. It's incorrect. It won't work. 
# See it as a sample to start working from.
$parent = Get-Item master:/content
$item = Get-Item master:/content/Home
$clone = New-ItemClone -Item $item -Destination $parent -Name HomeClone

$provider = $clone.Database.NotificationProvider
$notification = New-Object Sitecore.Data.Clones.ChildCreatedNotification
$notification.ID = [Sitecore.Data.ID]::NewID
$notification.Processed = $false
$notification.Uri = New-Object Sitecore.Data.ItemUri($item.ID, $item.Database)
$notification.ChildId = New-Object Sitecore.Data.ID("{DCF23BA5-7A42-431F-BD29-076E3A02B91F}")
$provider.AddNotification($notification)

I have made some extensions to SPE that makes it easier to handle notifications, but at the time this is being written, I haven’t made pull request for it yet. But you can probably get the above to work, but keep an eye on the Notifications table in the database. Creating new notifications and actually get the right values into the database isn’t trivial. (Tips: a ItemUri will typically save the item language in the Notifications table. A ChildCreatedNotification with a non-empty language won’t show up in the editor.)

With the (maybe) upcoming addition, you’d be able to do things like the sample below. I just have to find some time to finish it and hope it gets accepted.

Get-Item master:/content/HomeClone | Get-ItemNotification | Format-Table Type, ID, Uri

Sitecore 8.1 config changes

$
0
0

With the new version 8.1 of Sitecore we get a some really nice new features that’s been talked about a lot over the last few weeks. In this post I’d like to highlight one of the minor changes that I think is well worth spreading as well, since it makes life a bit easier for us developers.

The web.config file in a default Sitecore solution is really big and over the past years it has actually grown close to its maximum of 250kB file size. Most configuration in Sitecore can also be patched, but things outside the <sitecore> element cannot be patched as easily. With almost all solutions we need to do at least small changes to the web.config outside the <sitecore> scope, so that means we’ll have to include the web.config in our source code repositories.

You probably already know that having a clear strategy of dividing configs is key to make a solution easier to maintain. Your changes to the config should be clearly separated from the default config that Sitecore provides, so that you can let Sitecore maintain the default config as patches and updates are applied to the solution. 3rd party modules (should) also have their separate config and you should keep yours separate as well.

So the huge web.config file isn’t very elegant and can of course cause some trouble when upgrading Sitecore etc, since we typically have to include it in our code repositories. We want to keep it as small as possible. The first thing I’ve always done with every new instance of Sitecore, is to extract the majority of the web.config into a stand alone Sitecore.config file that I place in the App_Config folder and reference it in the sitecore section. This reduces the ~245kB file to ~25kB on a Sitecore 8.0 instance. I’ve been doing this for years.

So it was really about time Sitecore did something about this, and I was really happy to find that in 8.1, Sitecore have done this in the exact same way. Yes! In addition, Sitecore has also moved the log4net section from web.config into the Sitecore.config, so now we can patch the log4net config as well. Nice!

At last, I’d like to send a little message to Sitecore support: Please don’t ask us to do modifications in the config files. Tell us to patch it with a separate config file instead. I believe you’re working on this already, since it seems like you’ve improved on this over the last year, but I sometime still get the suggestion to modify a config file. I would never go on and actually do that myself, but others may do. Embrace good config structure and patch it instead.

Let’s keep config files well structured and maintainable.

Flatten the Sitecore web database

$
0
0

This post describes a concept of resolving content during publish in Sitecore 8.1, but most of it will apply to older versions as well.

With Sitecore 8.1 we get language fallback out of the box. Previously we’ve had the Partial Language Fallback module written by Alex Shyba. The new built-in one is a rewritten one.

It is a very powerful function, but it also uncovers some problems that occurs when relying on any kind of fallback mechanism. The most obvious one is performance. It does take some extra CPU cycles to resolve a fallback value, but the impact should not be that big with correct cache sizes etc.

A less obvious, but to me greater, problem is the fact that it may be quite hard for editors to really know what’s being published. An editor may be presented with the expected content in the Experience Editor (or in the Content Editor for that matter), but a field may fallback to an item that is not publishable or may not be in its final workflow step etc. Since the resolving is performed run-time, the presented content may be from an older version or even null/empty if the referred item doesn’t exist.

To solve this, I wanted to flatten the content of the web database in a similar way as clones work. When a cloned item is published, the clone reference is removed and the source content is copied into the each clone. Thereby the final value is always written into each field in the web database and no fallback processing is needed. This also eliminates the problem with unpublished referred item content.

There are a few ways to hook into the publishing process. For this scenario I found it quite simple to actually replace the publishing provider with a custom one that overrides the default PipelinePublishProvider. In Sitecore there is a PublishHelper that is quite simple to extend. By overriding the publish provider, we can provide our own PublishHelper class, like this:

public class CustomPipelinePublishProvider : Sitecore.Publishing.PipelinePublishProvider
{
	public override PublishHelper CreatePublishHelper(PublishOptions options)
	{
		Assert.ArgumentNotNull(options, "options");
		return new CustomPublishHelper(options);
	}
}

Then we need to take a look at the default implementation of PublishHelper. Most of the magic is performed in a private method called TransformToTargetVersion. Using a favorite reflection tool, we can build our own version of this:

public class CustomPublishHelper : Sitecore.Publishing.PublishHelper
{
	public CustomPublishHelper(PublishOptions options) : base(options)
	{
	}

	protected virtual Item TransformToTargetVersion(Item sourceVersion)
	{
		// Keep majority of exsting code from original method
		// Depending on you requirement, add custom flattening code, 
		// at a suitable place such as this:
		FieldCollection fields = sourceVersion.Fields;
		fields.ReadAll();
		foreach (Field field in fields)
		{
			if (field.Definition != null && !field.Definition.IsShared &&
				field.Definition.IsSharedLanguageFallbackEnabled && !field.HasValue)
			{
				// Force a copy of the value into the target field
				targetVersion.Fields[field.ID].SetValue(field.Value, true);
			}
		}
	}

	public override void CopyToTarget(Item sourceVersion)
	{
		// Unchanged code from original method
	}

	private void ReplaceFieldValues(Item targetVersion)
	{
		// Unchanged code from original method
	}

	private void CopyBlobFields(Item sourceVersion, Item targetVersion)
	{
		// Unchanged code from original method
	}
}

Finally we need to configure our new publish provider. The publishing process is also executed in the publisher site context, so in order for this to work, we have to enable language fallback on that site as well. Such configuration could look something like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <publishManager>
      <patch:attribute name="defaultProvider">custom</patch:attribute>
      <providers>
        <add name="custom" type="Custom.Namespace.CustomPipelinePublishProvider, Custom.Assembly" />
      </providers>
    </publishManager>
    <sites>
      <site name="publisher">
        <patch:attribute name="enableItemLanguageFallback">false</patch:attribute>
        <patch:attribute name="enableFieldLanguageFallback">true</patch:attribute>
      </site>
    </sites>
  </sitecore>
</configuration>

As discussed previously, the result of this is that the evaluated value is written into the web database, i.e. the content that an editor sees in Sitecore is the actual content being published. This is typically the expected behavior, since it is that piece of content the author is typically approving through a workflow and publish.

However, one could argue that if the fallback value is not in a publishable state, a inheriting item should not be allowed to publish this content either. Luckily the language fallback function itself is implemented as part of the getFieldValue pipeline, so the GetLanguageFallbackValue can probably be replaced with one that always returns the field value from latest approved and publishable item. I haven’t tried this myself yet though. That’s a subject for a follow post.


Thoughts on indexing Sitecore content with Solr

$
0
0

Today, my colleagues stumbled upon a strange behavior in one of our projects. I won’t go into all the details about it, but it let me think about how Sitecore indexes fields and how it can be improved. We use Solr, so at this stage, I don’t know if this applies to Lucene as well, but I guess it does.

We typically auto generate our domain model from a TDS project using a T4 template. Having that, we can easily map all Sitecore template fields to typed properties in our domain model and we can also map those to the fields in a Solr index. This means we can use the same model when using the ContentSearch API as well as with the traditional API. We can also lazy load fields from the original item if they are not marked as stored in the index.

Part of today’s strange behavior was that html and rich text fields are not stored properly, at least not in my opinion. Looking at the Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config file, we see that text fields, such as “html”, “rich text”, “single-line text” etc are treated as “text” field types. Those are then typically configured with stemming and other text analyzers in Solr.

Further down that config file, we have field readers, such as NumericFieldReader, DateFieldReader etc, that converts the raw value of a Sitecore field into a index suitable value. Here we find that “html” and “rich text” is using a RichTextFieldReader. That little class gets the plain text from a rich text field and thereafter it’s treated as any other text string.

I think that’s not a very good idea for two reasons. First, the html stripper is really simple. It basically just removes <tags>, so for example a script block fall through. Secondly, since it’s treated as a text field, it’s stored in the index by default. But since it’s a processed value by the RichTextFieldReader, it’s just a complete waste of index space. It can’t be used for anything since it’s no longer its original value.

To solve this, we can change this to a separate dynamic field type in the Solr config, that works similar to the text field type, but has an html analyzer, such as the solr.HTMLStripCharFilterFactory. That one is more advanced than the RichTextFieldReader and by moving the html analyzing part to the Solr server, we can make use of a correct index stored value (or easily opt-out all those fields), since we can send the original value from Sitecore using the DefaultFieldReader instead. We also ease the load of the web server a little bit.

Speading up the Sitecore Experience Editor

$
0
0

If you’ve spent some time with Sitecore 8.x, you’ve probably experienced the tremendously slow SPEAK driven Experience Editor (formerly known as Page Editor). This is due to multiple problems. The design of SPEAK is really chatty and cache is vital, some XHR requests are really slow and some are caused by heavy precompilations.

Kam Figy wrote a really good post about Sitecore 8 Experience Editor Performance Optimization. Read this first if you haven’t already gone through this one!

Brijesh Patel also addressed the “Suggested Tests Count” problem, where the Experience Editor ribbon blocks the rendering for a very long time just to get a list of suggested tests.

With those problems solved, I’ve found that there is a similar call for getting the number of locked items the current user have. This little digit can easily block the UI for several seconds. The figure is quite nice for authors, so let’s optimize this one.

My locked items counter

In the Sitecore config, we can replace the ExperienceEditor.MyItems.Count hadler in the <sitecore.experienceeditor.speak.requests> section, with our own class. Looking at the default implementation, we see that Sitecore looks through all the items and finds items where the __lock field contains the current user name. Though it uses Fast Query, it’s still quite slow. So I decided to go with a search implementation instead.

I’m using Solr, so this example is based on that, but it’s virtually the same if you’re using Lucene

First, we need to add the lock owner to the index. By default, the __lock field is ignored and there is a separate computed lock field that indicates locked items. But for this function we need to know the owner of the lock. Such field can be implemented like this:

public class IsLockedByComputedField : AbstractComputedIndexField
{
    public override object ComputeFieldValue(IIndexable indexable)
    {
        Item obj = indexable as SitecoreIndexableItem;
        if (obj == null)
            return null;
        if (!obj.Locking.IsLocked())
            return null;
        return obj.Locking.GetOwner();
    }
}

Then we need a replacement for the MyItemsCountRequest class that uses the index for counting instead. This code should probably be adapted so that it fits your environment regarding getting the right search context. Here it’s pretty hard coded:

public class MyItemsCountRequest : PipelineProcessorRequest<ItemContext>
{
    public override PipelineProcessorResponseValue ProcessRequest()
    {
        var searchContext = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext();
        var myLockedItemsCount = searchContext.GetQueryable<LockedItem>()
            .Filter(f => f.LockedBy == Sitecore.Context.User.Name &&
                     f.LanguageName == Sitecore.Context.Language.Name &&
                     f.LatestVersion)
            .Take(0)
            .GetResults()
            .TotalSearchResults;
        return new PipelineProcessorResponseValue { Value = myLockedItemsCount };
    }

    protected class LockedItem
    {
        [IndexField("lockedby")]
        internal string LockedBy { get; set; }

        [IndexField("_language")]
        internal string LanguageName { get; set; }

        [IndexField("_latestversion")]
        internal bool LatestVersion { get; set; }
    }
}

Now we can just put this together with a config patch file, such as this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <sitecore.experienceeditor.speak.requests>
      <request name="ExperienceEditor.MyItems.Count">
        <patch:attribute name="type">YourNamespace.MyItemsCountRequest, YourAssembly</patch:attribute>
      </request>
    </sitecore.experienceeditor.speak.requests>

    <contentSearch>
      <indexConfigurations>
        <!-- change this if you're using lucene! -->
        <defaultSolrIndexConfiguration>
          <fields hint="raw:AddComputedIndexField">
            <field fieldName="lockedby" returnType="string">YourNamespace.IsLockedByComputedField, YourAssembly</field>
          </fields>
        </defaultSolrIndexConfiguration>
      </indexConfigurations>
    </contentSearch>
  </sitecore>
</configuration>

With this solution in place, the MyItem.Count handler returns in less than 0.1 seconds. That’s in the magnitude of 100 times faster.

Now lets just hope that Sitecore starts bundling some of the XHR requests, such as “CanEdit”, “CanAddVersion”, “CanResetFields” etc, into one fewer requests. Then the Experience Editor might gain a decent performance again.

Editor friendly language fallback

$
0
0

Language fallback is really nice when working multi lingual websites. It save a lot of time and makes the solution very flexible. However, among content editors it can cause some headaches since the content shown to editors may not be the actual content being published.

I touched on this in my previous post about flatten the Sitecore web database, where this becomes a real problem unless we solve it. If we just flatten the web db as I described in that post, unapproved content can be published.

language fallback modelThe problem can be visualized with this diagram, representing one Sitecore item. Let’s say the Swedish (sv) language is configured to fallback to the English (en) language. When there is no content (null) in a fallback enabled field in the Swedish version, it will by default fallback to the latest English version. That content will be displayed to content editors while working in the master database, both in Content Editor and Experience Editor. This is confusing to editors, since that’s not the content being published.

The fallback operation is performed at runtime, meaning that in this example, only the first version will be published and therefore the fallback will be only to approved English content (correct) but not to the content shown to editors when editing and approving the Swedish language version. So let’s fix this.

The code example applies to Sitecore 8.1 with built in language fallback. The concept can applied to any version.

When Sitecore loads a field value for an item, the language fallback module executes the configured languageFallbackFieldValues provider. We can simply extend the default one with something like the one below. Essentially, I’ve kept the class as the default one, but extended it with some logic that will prevent it from performing a fallback to a unapproved version.

public class CustomLanguageFallbackFieldValuesProvider : LanguageFallbackFieldValuesProvider
{
  public override LanguageFallbackFieldValue GetLanguageFallbackValue(Field field, bool allowStandardValue)
  {
    // This is Sitecore default code
    var fallbackValuesFromCache = GetFallbackValuesFromCache(field, allowStandardValue);
    if (fallbackValuesFromCache != null)
      return fallbackValuesFromCache;

    var item = field.Item;
    Field fallbackField = null;
    string fieldValue = null;
    var list = new List<Language>(4);
    using (new EnforceVersionPresenceDisabler())
    {
      using (new LanguageFallbackItemSwitcher(false))
      {
        do
        {
          list.Add(item.Language);
          var fallbackLanguage = LanguageFallbackManager.GetFallbackLanguage(allowStandardValue ? 
            item.Language : item.OriginalLanguage, item.Database, item.ID);
          
          // Replaced code from here
          if (fallbackLanguage == null ||
            string.IsNullOrEmpty(fallbackLanguage.Name) ||
            list.Contains(fallbackLanguage))
          {
            break;
          }

          item = item.Database.GetItem(item.ID, fallbackLanguage, Sitecore.Data.Version.Latest);
          if (item == null)
            break;

          // Test if this version is valid. If not, load older versions
          if (!IsSharedValid(item))
            break;

          if (!IsItemVersionValid(item, field.ID))
          {
            item = item.Versions
              .GetOlderVersions()
              .Reverse()
              .FirstOrDefault(i => IsItemVersionValid(i, field.ID));
            if (item == null)
              break;
          }
          // End of replaced code

          fallbackField = item.Fields[field.ID];
          fieldValue = fallbackField.GetValue(allowStandardValue, false, false);
        }
        while (fieldValue == null);
      }
    }

    LanguageFallbackFieldValue languageFallbackFieldValue;
    if (fieldValue != null)
    {
      ItemUri uri = item.Uri;
      bool containsStandardValue = fallbackField.ContainsStandardValue;
      languageFallbackFieldValue = new LanguageFallbackFieldValue(fieldValue, containsStandardValue, uri.Language.Name);
    }
    else
      languageFallbackFieldValue = new LanguageFallbackFieldValue(null, false, null);

    AddLanguageFallbackValueToCache(field, allowStandardValue, languageFallbackFieldValue);
    return languageFallbackFieldValue;
  }

  // Extend this as needed
  public virtual bool IsSharedValid(Item item)
  {
    return item.Publishing.IsPublishable(DateTime.UtcNow, false);
  }

  // Extend this as needed
  public virtual bool IsItemVersionValid(Item item, ID fieldID)
  {
    bool hasPublishingErrors = PublishingInformationBuilder
      .GetPublishingInformation(item, PublishingInformationLevel.Item | PublishingInformationLevel.Version)
      .OfType<Sitecore.Publishing.Explanations.Error>()
      .Any();
    return !hasPublishingErrors;
  }
}

Then patch this into the config like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <languageFallbackFieldValues>
      <patch:attribute name="defaultProvider">custom</patch:attribute>
      <providers>
        <add name="custom" type="YourNamespace.CustomLanguageFallbackFieldValuesProvider, YourAssembly" />
      </providers>
    </languageFallbackFieldValues>
  </sitecore>
</configuration>

Now only approved and publishable content will be displayed to authors in the Sitecore editors. From web db perspective, this won’t make much difference, since only the latest publishable version will be published.

If we apply the flatten web db concept mentioned earlier, we get an additional advantage of this. The web database will be filled with approved content for each language. As the fallback content is approved and published (English version 2 in this example), other contents (Swedish versino 1 in this example) will stay as-is until it’s published.

Sitecore File Watcher Internal Buffer Overflow Exception in 8.1 update-1

$
0
0

After upgrading some of our solutions to Sitecore 8.1 update-1 we started seeing errors in our log files, such as this:

... ERROR Error in FileWatcher. Internal buffer overflow.
Exception: System.IO.InternalBufferOverflowException
Message: Too many changes at once in directory:path-to-data-dir\Data.

I haven’t figured out exactly why this happens yet, but we do quite a lot of automated file deployments etc, and it seems to occur more often around deployments as well as on local builds on our dev machines. I searched all our machines and the exception had occurred in all our instances that were running update-1, and I didn’t find the error in any instances running other versions of Sitecore.

With very good help from Sitecore support, it turned out it could be solved by moving the license file. As suggested, we just created a license folder in the Data dir and moved the license.xml file in there, and then we applied the following License.config patch into App_Config/Include:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="LicenseFile">
        <patch:attribute name="value">$(dataFolder)/license/license.xml</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

Even if you haven’t seen the exception yet, I suggest you apply this patch if running Sitecore 8.1. It doesn’t do any harm regardless if you’re affected by it or not.

Language Fallback on Display Name

$
0
0

In Sitecore 8.1 we finally got language fallback built into the core product. This gives us the option to specify, per field, if we want to inherit a field value from another language when the field itself is null.

I guess we all have some sort of love and hate relation to the built in Display Name field of Sitecore. It solves a lot of potential issues, but it causes tons of other problems as well. With language fallback, there is another one. If you enable language fallback on the Display Name item, nothing happens. This is because the default LanguageFallbackFieldValuesProvider ignores all standard fields. Well, all fields starting with a double underscore to be exact.

So in order to have language fallback on Display Name or any other standard field (though the need for it on other fields are probably very rare), we’ll have to replace the default implementation, with an overridden version where the bool IsValidForFallback(Field field) method doesn’t return false just because its internal name is "__Display Name".

If you need details on how to replace the default implementation, look at my post about editor friendly language fallback.

Sitecore MVP 2016 Winner

$
0
0

Sitecore MVP 2016Thank you Sitecore for awarding me “Technology Most Valuable Professional (MVP)” again! Fourth year in a row! This year, Sitecore has designated 177 Technology MVPs, 35 Digital Strategist MVPs, and nine Commerce MVPs, which brings the total group to more than 200 spread across 25 countries worldwide.

Here are all the Sitecore MVPs 2016. More information on the MVP program can be found on the Sitecore MVP site.

Automatic unlock items

$
0
0

There has been many posts about unlocking Sitecore items, and here’s yet another one that perhaps fits your needs.

In one of our projects, we decided that users that are not logged into Sitecore, doesn’t need to retain locks on any items, so we wanted a solution where Sitecore automatically releases all item locks where the user doesn’t have an active session.

To make the solution efficient, I decided on re-using the LockedBy computed index field I described in my earlier post about speeding up the Sitecore Experience Editor. Using that index field, it is simple and really fast to find all items that are locked by users that doesn’t have a session.

So I essentially created a scheduled agent that is executed every hour, or as often as needed.

First we need the LockedBy Index field and a class that represents a locked item in the index, like this:

public class IsLockedByComputedField : AbstractComputedIndexField
{
	public override object ComputeFieldValue(IIndexable indexable)
	{
		Item obj = indexable as SitecoreIndexableItem;
		if (obj == null)
			return null;
		if (!obj.Locking.IsLocked())
			return null;
		return obj.Locking.GetOwner();
	}
}

public class LockedItem
{
	[IndexField("_group")]
	public Guid ID { get; set; }

	[IndexField("_language")]
	public string LanguageName { get; set; }

	[IndexField("_version")]
	public int Version { get; set; }

	[IndexField("lockedby")]
	public string LockedBy { get; set; }
}

Then we need an agent that is executed periodically. When executed, I load all users that are having locked items using the index:

var searchContext = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext();
var usersHavingLock = searchContext.GetQueryable<LockedItem>()
	.FacetOn(f => f.LockedBy)
	.GetFacets()
	.Categories.FirstOrDefault();

Now we have a facet list with all users. Now we can get a list of all user sessions:

var loggedInUsers = new HashSet<string>();
using (new SecurityDisabler())
{
	foreach (var session in DomainAccessGuard.Sessions)
	{
		if (!loggedInUsers.Contains(session.UserName))
			loggedInUsers.Add(session.UserName);
	}
}

With these two lists, we can simply find all users that have items that should be unlocked:

var usersToUnlock = new List<string>();
foreach (var userFacet in usersHavingLock.Values.Where(f => f.AggregateCount > 0))
{
	if (!loggedInUsers.Contains(userFacet.Name)) 
		usersToUnlock.Add(userFacet.Name);
}

Now we can simply look through all users and find all items to unlock them:

var db = Factory.GetDatabase("master");
foreach (var userToUnlock in usersToUnlock)
{
	// Find all items that is locked by user
	Sitecore.Diagnostics.Log.Info("Unlocking items owned by "+ userToUnlock, this);
	var username = userToUnlock;
	var itemsToUnlock = searchContext.GetQueryable<LockedItem>()
		.Where(i => i.LockedBy == username)
		.GetResults()
		.Hits.Select(i => i.Document);

	// Unlock each item
	foreach (var lockedItem in itemsToUnlock)
	{
		var id = new ID(lockedItem.ID);
		var language = LanguageManager.GetLanguage(lockedItem.LanguageName);
		var version = Sitecore.Data.Version.Parse(lockedItem.Version);
		var item = db.GetItem(id, language, version);
		if (item != null)
		{
			Sitecore.Diagnostics.Log.Info("Unlocking item" + item.Uri.ToString(ItemUriFormat.Uri), this);
			using (new EditContext(item, SecurityCheck.Disable))
			{
				item.Locking.Unlock();
			}
		}
	}
}

Switching authoring language in Sitecore

$
0
0

There are different approaches that can be taken when managing multi-lingual web sites in Sitecore. Sometimes your authors might have to manage content in multiple languages and the might need to switch between site trees that are built on different languages.

In that scenario, editor often faces the message that there is no version on the current language, and they have to find the right one in a long list of languages. This isn’t very convenient. Using the new Experience Editor it’s even worse, because authors are not given this information at all, so they might start editing a page in the wrong language by mistake.

default-content-editor-message

I wanted to make this easier for the authors, so I decided to change the built-in Content Editor warning, and add an equivalent function to the Experience Editor.

The Content Editor uses the getContentEditorWarnings pipeline, and it contains a HasNoVersions processor. This particular processor is a little special, because it suppresses the other processors in the pipeline. So the easiest way to achieve the goal is to override it, like this:

public class ContentEditorMissingLanguageWarning
{
	public void Process(GetContentEditorWarningsArgs args)
	{
		var item = args.Item;
		if (item == null)
			return;

		var versionNumbers = item.Versions.GetVersionNumbers(false);
		if(versionNumbers != null && versionNumbers.Length > 0)
			return;

		var languages = item.Versions.GetVersions(true)
			.Select(i => i.Language)
			.Distinct(new LanguageComparer())
			.OrderBy(l => l.Name)
			.ToList();
			
		var contentEditorWarning = args.Add();
		contentEditorWarning.Title = string.Format("The current item does not have a version in \"{0}\".", item.Language.CultureInfo.DisplayName);
		contentEditorWarning.Text = "You may jump to any of the languages below";
		foreach (var lang in languages)
		{
			var message = string.Format("Go to {0} {1}", lang.Name, lang.CultureInfo.DisplayName);
			var command = string.Format("item:load(id={0},language={1},version=0)", item.ID, lang.Name);
			contentEditorWarning.AddOption(message, command);
		}

		if (item.Access.CanWriteLanguage() && item.Access.CanWrite())
		{
			contentEditorWarning.Text += ", or add a new version";
			contentEditorWarning.AddOption(string.Format("Add a new version on {0}", item.Language.CultureInfo.DisplayName), "item:addversion");
			contentEditorWarning.IsExclusive = true;
		}
		else
			contentEditorWarning.IsExclusive = false;

		contentEditorWarning.HideFields = true;
		contentEditorWarning.Key = "ContentEditorMissingLanguageWarning";
	}
}

public class LanguageComparer : IEqualityComparer<Language>
{
	public bool Equals(Language x, Language y)
	{
		if (x == null && y == null)
			return true;
		if (x == null || y == null)
			return false;
		return string.Equals(x.Name, y.Name);
	}

	public int GetHashCode(Language obj)
	{
		if (obj == null)
			return 0;
		return obj.Name.GetHashCode();
	}
}

With this, we will get the following result in the Content Editor instead, that is much more convenient:

go-to-language-content-editor

Adding the same function to the Experience Editor is about the same, but with a few adaptations to make it fit into the getPageEditorNotifications pipeline:

public class ExperienceEditorMissingLanguageWarning : GetPageEditorNotificationsProcessor
{
	public override void Process(GetPageEditorNotificationsArgs args)
	{
		Assert.ArgumentNotNull(args, "arguments");
		var item = args.ContextItem;
		if (item == null)
			return;

		var versionNumbers = item.Versions.GetVersionNumbers(false);
		if (versionNumbers != null && versionNumbers.Length > 0)
			return;

		var languages = item.Versions.GetVersions(true)
			.Select(i => i.Language)
			.Distinct(new LanguageComparer())
			.OrderBy(l => l.Name)
			.ToList();

		var message = string.Format("This page does not have a version in {0}", item.Language.CultureInfo.DisplayName);

		var options = new List<PageEditorNotificationOption>();
		foreach (var lang in languages)
		{
			var title = string.Format("Go to {0} {1}", lang.Name, lang.CultureInfo.DisplayName);
			var cb = new CommandBuilder("webedit:setlanguage");
			cb.Add("language", lang.Name);
			var option = new PageEditorNotificationOption(title, cb.ToString());
			options.Add(option);
		}

		var notification = new PageEditorNotification(message, PageEditorNotificationType.Error);
		notification.Options.AddRange(options);
		args.Notifications.Add(notification);
	}
}

And we’ll get an Experience Editor that looks like this:

go-to-language-experience-editor

And here’s how to patch it to the config:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<pipelines>
			<getContentEditorWarnings>
				<processor type="Stendahls.Sc.EditorExtensions.Pipelines.ContentEditorMissingLanguageWarning, Stendahls.Sc.EditorExtensions" patch:instead="processor[@type='Sitecore.Pipelines.GetContentEditorWarnings.HasNoVersions, Sitecore.Kernel']"/>
			</getContentEditorWarnings>
			<getPageEditorNotifications>
				<processor type="Stendahls.Sc.EditorExtensions.Pipelines.ExperienceEditorMissingLanguageWarning, Stendahls.Sc.EditorExtensions" />
			</getPageEditorNotifications>
		</pipelines>
	</sitecore>
</configuration>

Enjoy!

Yet another Sitecore scheduled publishing engine

$
0
0

When you schedule an item in Sitecore, it doesn’t mean that the item gets published or unpublished at that date. It just means that it is possible to publish the item within the given period. One have to perform an actual publish for it to actually happen.

There are many great modules already that handles this, such as Scheduled Publish Module for Sitecore by Hedgehog.

In my scenario, we just wanted to trigger a publish for items as they are scheduled. Scanning through the whole database is very expensive though, so I decided to make one that utilizes our search index instead. We use Solr in our solution, but it’ll probably work as good with Lucene as well.

First, I created a computed field that stores the dates when a publish needs to occur. This can be either a start date or an end date. The field is defined as a list of dates in the index.

public class ScheduledPublishComputedField : AbstractComputedIndexField
{
	public override object ComputeFieldValue(IIndexable indexable)
	{
		Sitecore.Data.Items.Item item = indexable as SitecoreIndexableItem;
		if (item == null)
			return null;

		var dateList = new List<DateTime>();
		if (item.Publishing.PublishDate != DateTimeOffset.MinValue.UtcDateTime)
			dateList.Add(item.Publishing.PublishDate);
		if (item.Publishing.UnpublishDate != DateTimeOffset.MaxValue.UtcDateTime)
			dateList.Add(item.Publishing.UnpublishDate);

		if (item.Publishing.ValidFrom != DateTimeOffset.MinValue.UtcDateTime)
			dateList.Add(item.Publishing.ValidFrom);
		if (item.Publishing.ValidTo != DateTimeOffset.MaxValue.UtcDateTime)
			dateList.Add(item.Publishing.ValidTo);

		return dateList.Count == 0 ? null : dateList;
	}
}

And the field is added into the list of computed fields in the configuration

<fields hint="raw:AddComputedIndexField">
  <field fieldName="scheduledpublish" returnType="datetimeCollection">Stendahls.Sc.ScheduledPublishing.ScheduledPublishComputedField, Stendahls.Sc.ScheduledPublishing</field>
</fields>

Then we need an object that can represent items that should be published that we’ll use when querying the index:

protected class PublishItem
{
	[IndexField("_group")]
	public Guid ID { get; internal set; }

	[IndexField("_fullpath")]
	public string FullPath { get; internal set; }

	[IndexField("_language")]
	public string LanguageName { get; internal set; }

	[IndexField("_latestversion")]
	public bool LatestVersion { get; internal set; }

	[IndexField("scheduledpublish_tdtm")]
	[IgnoreIndexField]
	internal DateTime ScheduledPublishDateTime { get; set; }
}

Then we need an agent that can check the index if there are any items that needs publishing. Essentially we just need a timestamp when the agent was previously executed, so instead of creating new database tables etc, I decided to just use a property in the core database. So the agent wrapper becomes quite simple:

public void Run()
{
	var database = Factory.GetDatabase(SourceDatabase);
	var now = DateTime.UtcNow;

	var lastRun = LoadLastPublishTimestamp(database);
	if (lastRun == DateTime.MinValue)
	{
		SaveLastPublishTimestamp(database, now);
		return;
	}

	try
	{
		PerformPublish(lastRun, now);
	}
	finally
	{
		SaveLastPublishTimestamp(database, now);
	}
}

private string PropertiesKey
{
	get
	{
		return "ScheduledPublishing_" + Settings.InstanceName;
	}
}

public DateTime LoadLastPublishTimestamp(Database db)
{
	string str = db.Properties[PropertiesKey];
	DateTime d;
	DateTime.TryParseExact(str, "s", CultureInfo.InvariantCulture,
		DateTimeStyles.None, out d);
	return d;
}

public void SaveLastPublishTimestamp(Database db, DateTime time)
{
	db.Properties[PropertiesKey] = time.ToString("s", CultureInfo.InvariantCulture);
}

Now we can easily find what items needs publishing by a regular search query:

var searchContxt = ContentSearchManager.GetIndex(SourceIndex).CreateSearchContext();

List<PublishItem> publishItemQueue = new List<PublishItem>();
int skip = 0;
bool fetchMore;
do
{
	var queryResult = searchContxt.GetQueryable<PublishItem>()
		.Filter(f => f.LatestVersion && 
			f.ScheduledPublishDateTime.Between(publishSpanFrom, publishSpanUntil, Inclusion.Upper))
		.OrderBy(f => f.FullPath)
		.Skip(skip)
		.Take(500)
		.GetResults();
	skip += 500;

	publishItemQueue.AddRange(queryResult.Hits.Select(h => h.Document));
	fetchMore = queryResult.TotalSearchResults > skip;
} while (fetchMore);

I decided to opt for publishing related items for each item that is scheduled. This may require adaptations according to you needs. The complete source of the agent ended up like this in my case:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Sitecore.Collections;
using Sitecore.Configuration;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;
using Sitecore.Diagnostics.PerformanceCounters;
using Sitecore.Globalization;
using Sitecore.Publishing;

namespace Stendahls.Sc.ScheduledPublishing
{
	public class PublishAgent
	{
		public string SourceDatabase { get; private set; }
		public string SourceIndex { get; private set; }
		public List<string> TargetDatabases { get; private set; }
		public PublishMode Mode { get; private set; }

		public PublishAgent(string sourceDatabase, string sourceIndex, string targetDatabases, string mode)
		{
			Assert.ArgumentNotNullOrEmpty(sourceDatabase, "sourceDatabase");
			Assert.ArgumentNotNullOrEmpty(sourceDatabase, "sourceIndex");
			Assert.ArgumentNotNullOrEmpty(targetDatabases, "targetDatabase");
			Assert.ArgumentNotNullOrEmpty(mode, "mode");
			SourceDatabase = sourceDatabase;
			SourceIndex = sourceIndex;
			TargetDatabases = ParseDatabases(targetDatabases);
			Mode = ParseMode(mode);
		}

		public void Run()
		{
			var database = Factory.GetDatabase(SourceDatabase);
			var now = DateTime.UtcNow;

			var lastRun = LoadLastPublishTimestamp(database);
			if (lastRun == DateTime.MinValue)
			{
				SaveLastPublishTimestamp(database, now);
				return;
			}

			try
			{
				PerformPublish(lastRun, now);
			}
			finally
			{
				SaveLastPublishTimestamp(database, now);
			}
		}

		public void PerformPublish(DateTime publishSpanFrom, DateTime publishSpanUntil)
		{
			var searchContxt = ContentSearchManager.GetIndex(SourceIndex).CreateSearchContext();

			List<PublishItem> publishItemQueue = new List<PublishItem>();
			int skip = 0;
			bool fetchMore;
			do
			{
				var queryResult = searchContxt.GetQueryable<PublishItem>()
					.Filter(f => f.LatestVersion && 
						f.ScheduledPublishDateTime.Between(publishSpanFrom, publishSpanUntil, Inclusion.Upper))
					.OrderBy(f => f.FullPath)
					.Skip(skip)
					.Take(500)
					.GetResults();
				skip += 500;

				publishItemQueue.AddRange(queryResult.Hits.Select(h => h.Document));
				fetchMore = queryResult.TotalSearchResults > skip;
			} while (fetchMore);

			if (publishItemQueue.Count == 0)
				return;

			var db = Factory.GetDatabase(SourceDatabase);
			var publishingTargets = GetPublishingTargets(db, TargetDatabases);

			// Loop over the queue, but not using a regular foreach, since
			// we'll remove items from the queue as they are processed as related items.
			while (publishItemQueue.Count > 0)
			{
				var publishItem = publishItemQueue.First();
				publishItemQueue.RemoveAt(0);

				if (publishItem.LanguageName == null)
					continue;

				var language = LanguageManager.GetLanguage(publishItem.LanguageName);
				if (language == null)
					continue;

				var item = db.GetItem(new ID(publishItem.ID), language);
				if (item == null)
					continue;

				PublishItemAsyncWithRelatedItems(item, publishingTargets);
				RemoveRelatedItems(publishItemQueue, item);
			}
		}

		protected virtual void PublishItemAsyncWithRelatedItems(Item item, List<string> publishingTargets)
		{
			if (item == null)
				return;

			var targetDb = Factory.GetDatabase(TargetDatabases.First());
			var options = new PublishOptions(item.Database, targetDb, Mode, item.Language,
				DateTime.UtcNow, publishingTargets)
			{
				RootItem = item,
				Deep = false,
				PublishRelatedItems = true
			};
			var publisher = new Publisher(options);
			publisher.PublishAsync();

			// Increment performance counter
			JobsCount.TasksPublishings.Increment();
		}

		private void RemoveRelatedItems(List<PublishItem> publishItemQueue, Item item)
		{
			var links = Sitecore.Globals.LinkDatabase.GetReferences(item)
				.Where(l => l.TargetDatabaseName == l.SourceDatabaseName);
			foreach (var itemLink in links)
			{
				var itemGuid = itemLink.TargetItemID.Guid;
				var itemLanguage = itemLink.TargetItemLanguage != Language.Invariant ? itemLink.TargetItemLanguage.Name : null;
				var relatedLinks = publishItemQueue
					.Where(l => itemGuid == l.ID && (itemLanguage == null || itemLanguage == l.LanguageName))
					.ToList();

				foreach (var toRemove in relatedLinks)
				{
					publishItemQueue.Remove(toRemove);
				}
			}
		}

		private List<string> GetPublishingTargets(Database sourceDatabase, ICollection<string> targetDatabases)
		{
			var targets = new List<string>();
			var parent = sourceDatabase.GetItem("/sitecore/system/publishing targets");
			// Loop over all targets and add those that matches the database list
			foreach (Item target in parent.GetChildren(ChildListOptions.SkipSorting))
			{
				if (targetDatabases.Contains(target["Target database"]))
					targets.Add(target.ID.ToString());
			}
			return targets;
		}

		private string PropertiesKey
		{
			get
			{
				return "ScheduledPublishing_" + Settings.InstanceName;
			}
		}

		public DateTime LoadLastPublishTimestamp(Database db)
		{
			string str = db.Properties[PropertiesKey];
			DateTime d;
			DateTime.TryParseExact(str, "s", CultureInfo.InvariantCulture,
				DateTimeStyles.None, out d);
			return d;
		}

		public void SaveLastPublishTimestamp(Database db, DateTime time)
		{
			db.Properties[PropertiesKey] = time.ToString("s", CultureInfo.InvariantCulture);
		}

		private static List<string> ParseDatabases(string databases)
		{
			return databases.Split(',')
				.Select(s => s.Trim())
				.Where(s => !string.IsNullOrWhiteSpace(s))
				.ToList();
		}

		private static PublishMode ParseMode(string mode)
		{
			if (mode.Equals("Full", StringComparison.InvariantCultureIgnoreCase))
				return PublishMode.Full;
			if (mode.Equals("Incremental", StringComparison.InvariantCultureIgnoreCase))
				return PublishMode.Incremental;
			return mode.Equals("Smart", StringComparison.InvariantCultureIgnoreCase) ? PublishMode.Smart : PublishMode.Unknown;
		}
	}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<scheduling>
			<agent type="Stendahls.Sc.ScheduledPublishing.PublishAgent, Stendahls.Sc.ScheduledPublishing" method="Run" interval="00:30:00" >
				<param desc="source database">master</param>
				<param desc="source index">sitecore_master_index</param>
				<param desc="publish targets">web, web2</param>
				<param desc="mode (full, smart or incremental)">smart</param>
			</agent>
		</scheduling>
		<contentSearch>
			<indexConfigurations>
				<defaultSolrIndexConfiguration>
					<fields hint="raw:AddComputedIndexField">
						<field fieldName="scheduledpublish" returnType="datetimeCollection">Stendahls.Sc.ScheduledPublishing.ScheduledPublishComputedField, Stendahls.Sc.ScheduledPublishing</field>
					</fields>
				</defaultSolrIndexConfiguration>
			</indexConfigurations>
		</contentSearch>
	</sitecore>
</configuration>

Include renderings with Sitecore MVC

$
0
0

This post is about extending Sitecore to simplify reuse of content. It’s nothing new and has been done many times before. This is just my take on a common scenario. The solution is only tested on Sitecore 8.0 & 8.1, but will probably work on older versions as well. There are many usages for this extensions, but I’ll first try to explain the requirement I had and why this extension was needed.

As a start, we have a multi-brand, multi-lingual and multi-market set of websites in one Sitecore instance, where each market have its own cloned branch. How many doesn’t really matter, but the important thing here is that we have multiple sites or branches.

As with almost every site, we have a set of components that are the same on all or many pages, such as the header, navigation, footers etc. We don’t want to define and manage those on every page, so in Sitecore one typically define those in the template standard values or bind them statically in the layout. In both cases, it becomes pretty static.

In our scenario, we want the editors to be able change these parts of the website as well. We want the header to be adapted per site, the menu needs personalisation ability and the sites uses a “mega menu” where editors can put campaigns and offers inside the menu. Editor should be able to make these changes in one location for each site, using the Experience Editor, and changes should apply to multiple pages (parts or the whole site, but only one of the set of sites).

This means we cannot bind those components statically. Neither can we do it using template standard values, because that would apply to all markets, sites etc. And of course we cannot let the editors do these kind of changes to every page on their sites. So let’s fix this and extend Sitecore a bit.

This is pretty similar to what Alex Shyba wrote about Cascading Renderings.

Note that this solution applies to Sitecore MVC only. You can probably do the same in WebForms, but it would involve other pipelines.

The idea is to have separate items representing for example a header/menu for each site. Those items will not be navigable when published, but they do have a layout that’s used internally so that it can be easily edited in the Experience Editor. The renderings we place on these items will be reused on all the pages for the current website.

On the website pages, we add a specific Include Rendering renderer. This renderer will pick up this header/menu item. At runtime, the include rendering will be replaced with all the renderings on the mega menu item.

By default, rendering datasources are stored as Guid references to its target item. We cannot use Sitecore queries here and there is a good reason for that. Sitecore queries are slower, potentially a lot slower, than resolving just a Guid. Otherwise it would be very tempting to reference the an included item using a Sitecore query such as query:./ancestor-or-self::*[@@templatekey='website container']/Settings/MegaMenu

One option would be to add a pipeline to actually support this, and it could make perfectly sense to do so in some scenarios. One could be where you know the queries won’t be too complex or when performance won’t be an issue.

A second alternative could be to support this in the editor only and add some logic to the publisher, so that the queries are resolved at publish time and have the proper ID’s published to the web databases.

A third alternative is to hook up some custom code to resolve this in a faster way than just standard queries and perform the lookup runtime. This may seem a bit odd, but would make sense in some scenarios as well. If one can identify the renderings on the template standard values, a Sitecore query as described above would make perfect sense since developers would write it. But it’s rather complex for editors to write, so in a scenario where you want editors to add a complex set of reused components on multiple pages, but maybe not all pages, it would be nice if the datasource could be fixed without editor interaction.

But first, let’s look at how we can include renderings from another page.

Time spending extending SitecoreThere are a few places we can hook into to solve this, and I found it best to add a step in the mvc.getXmlBasedLayoutDefinition pipeline. This pipeline is executed during the build page definition process, and it solves things like applying layout deltas to standard values, final renderings etc. and as a return we get the full rendering xml including devices, rules sets etc.

So I appended a new pipeline step into the mvc.getXmlBasedLayoutDefinition pipeline. It searches for any of our specific (configurable) include rendering item. When one is found, it loads the item pointed out by its datasource and executes the same pipeline again. Thereby we also make recursion possible. Since the result of the pipeline is an xml of the whole layout, we have to pick only the renderings (the <r> elements) that applies to the current device. Secondly, remember that renderings without a datasource, sees the current item as source. Since we move renderings from one item into another, we have to set the datasource if it’s missing.

To prevent accident endless recursion loops, I build up a set of executed items and pass those as CustomData in the PipelineArgs, so that the process can be stopped if the current item is found in the set of already processed items. I also decided to add an additional rendering parameter indicating when a rendering is being included. In that way I can adjust the rendering if needed.

Worth noticing is that previous versions of Sitecore had a lot of ready-made methods for doing this, but I’ve discovered that since the introduction of Sitecore MVC, the built in classes for this now parses the XML manually as XDocuments. I guess there is a good reason for that, and I’ll just do it in the same way in this example.

Then, I want this to play nice with the Experience Editor as well. The problem is that if we include a few renderings from another item, the “Add here” buttons would be all messed up and a few other things as well. We want the included section to remain intact. Therefore I don’t perform the rendering replace when the PageMode is in IsPageEditorEditing mode. Instead, the include rendering renders itself as a placeholder.

So the include processor takes a set of view renderings (or any rendering actually) as configuration arguments. Whose renderings will be treated as include renderings in normal rendering mode, but in editing mode the view rendering will render itself. Thereby we can create a generic include view rendering that just renders a common placeholder in editing mode, and we can also create specific renderings that renders more specific code in the editor that makes more sense to authors.

Here’s also where the third option comes into play. Where I configure the include renderings in the config, I can also specify a datasource resolver class that is executed in normal rendering mode. This would be the preferred option if the datasource can’t be specified on standard values.

So here’s the code for it:

public class AddIncludeItemRenderings : GetXmlBasedLayoutDefinitionProcessor
{
    public const string IncludedItemParameterName = "included_rendering";
    public Dictionary<Guid, IncludeRendering> IncludeItemRenderingGuidSet { get; private set; }

    public AddIncludeItemRenderings()
    {
        IncludeItemRenderingGuidSet = new Dictionary<Guid, IncludeRendering>();
    }

    public override void Process(GetXmlBasedLayoutDefinitionArgs args)
    {
        Assert.ArgumentNotNull(args, "args");
        if (args.Result == null || Context.Site == null)
            return;

        try
        {
            var includeElements = args.Result
                .Elements("d").Elements("r")
                .Where(e => IncludeItemRenderingGuidSet.ContainsKey((Guid)e.Attribute("id")));

            foreach (var includeElement in includeElements)
            {
                // ReSharper disable once PossibleNullReferenceException
                var deviceId = (Guid) includeElement.Parent.Attribute("id");
                var renderingId = (Guid) includeElement.Attribute("id");
                var dataSource = includeElement.GetAttributeValueOrNull("ds");

                if (string.IsNullOrWhiteSpace(dataSource))
                {
                    var renderingItem = RenderingItem.GetItem(new ID(renderingId), args.PageContext.Database, true);
                    dataSource = renderingItem.DataSource;
                }

                if (string.IsNullOrWhiteSpace(dataSource))
                {
                    var type = IncludeItemRenderingGuidSet[renderingId].Type;
                    if (type != null)
                    {
                        var processor = (IIncludeProcessor) Activator.CreateInstance(type);
                        dataSource = processor.GetDatasource(includeElement, args.ContextItem ?? args.PageContext.Item);
                    }
                }
                if (string.IsNullOrWhiteSpace(dataSource))
                {
                    includeElement.Remove();
                    continue;
                }

                if (Context.PageMode.IsPageEditorEditing)
                {
                    // Let the rendering render itself
                    continue;
                }

                Item item = null;
                if (ID.IsID(dataSource))
                {
                    item = args.PageContext.Database.GetItem(new ID(dataSource));
                }
                else if (dataSource.StartsWith("query:"))
                {
                    item = args.PageContext.Item.Axes.SelectSingleItem(dataSource.Substring("query:".Length));
                    dataSource = item.ID.ToString();
                }

                if (item == null)
                {
                    includeElement.Remove();
                    continue;
                }

                var includeArgs = new GetXmlBasedLayoutDefinitionArgs();
                includeArgs.ContextItem = item;
                var items = (args.CustomData["includedItems"] as ISet<ID>) ?? new HashSet<ID>();
                if (items.Contains(item.ID))
                {
                    // Recursion occured. Break.
                    Log.Error("Found recursive inclusion!", this);
                    includeElement.Remove();
                    continue;
                }
                items.Add(item.ID);
                includeArgs.CustomData["includedItems"] = items;

                var includeLayoutDefinition = PipelineService.Get()
                    .RunPipeline("mvc.getXmlBasedLayoutDefinition", includeArgs, a => a.Result);

                var replacementElements = includeLayoutDefinition
                    .Elements("d").SingleOrDefault(e => (Guid) e.Attribute("id") == deviceId);
                if (replacementElements == null || replacementElements.IsEmpty)
                {
                    includeElement.Remove();
                    continue;
                }

                foreach (var replacementElement in replacementElements.Elements("r"))
                {
                    if (string.IsNullOrWhiteSpace((string)replacementElement.Attribute("ds")))
                    {
                        replacementElement.SetAttributeValue("ds", dataSource);
                    }

                    var par = (string)replacementElement.Attribute("par");
                    par += (!string.IsNullOrWhiteSpace(par) ? "&" : "") + IncludedItemParameterName + "=true";
                    replacementElement.SetAttributeValue("par", par);
                }
                includeElement.ReplaceWith(replacementElements.Elements("r"));
            }
        }
        catch (Exception ex)
        {
            Log.Error("Error replacing include renderings", ex, this);
        }
    }

    public void AddIncludeItemRendering(XmlNode arg)
    {
        var renderingElement = arg as XmlElement;
        if (renderingElement == null)
            return;

        Guid guid;
        if (!Guid.TryParse(renderingElement.GetAttribute("id"), out guid))
            return;

        if (IncludeItemRenderingGuidSet.ContainsKey(guid))
            return;

        Type type = null;
        var typeName = renderingElement.GetAttribute("processor");
        if (!string.IsNullOrWhiteSpace(typeName))
        {
            type = Type.GetType(typeName);
            if (type == null || !typeof(IIncludeProcessor).IsAssignableFrom(type))
                type = null;
        }
        var rendering = new IncludeRendering(guid, type);

        IncludeItemRenderingGuidSet.Add(rendering.Id, rendering);
    }

    public class IncludeRendering
    {
        public Guid Id { get; private set; }
        public Type Type { get; private set; }

        public IncludeRendering(Guid id, Type type)
        {
            Id = id;
            Type = type;
        }
    }
}

public interface IIncludeProcessor
{
    /// <summary>
    /// Resolves a datasource for a include rendering
    /// </summary>
    /// <param name="rendering"></param>
    /// <param name="contextItem"></param>
    /// <returns>A string, typically an ID, representing the target ID</returns>
    string GetDatasource(XElement rendering, Item contextItem);
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.getXmlBasedLayoutDefinition>
        <processor patch:after="*[@type='Sitecore.Mvc.ExperienceEditor.Pipelines.Response.GetXmlBasedLayoutDefinition.SetLayoutContext, Sitecore.Mvc.ExperienceEditor']"
          type="Your.Namespace.IncludeRendering.AddIncludeItemRenderings, Your.Assembly">
          <includeRenderings hint="raw:AddIncludeItemRendering">
            <includeRendering id="{5948095C-EDB3-4378-8906-92AE0ACFED7F}" processor="Your.Namespace.IncludeRendering.FooterIncludeProcessor, Your.Assembly" />
            <includeRendering id="{C71F6D80-F217-44CF-8807-CD868224E83D}" />
          </includeRenderings>
        </processor>
      </mvc.getXmlBasedLayoutDefinition>
    </pipelines>
  </sitecore>
</configuration>

Small fixes to Sitecore 8.1 Experience Editor

$
0
0

There are always room for improvements of the Experience Editor. Here are a few simple ones to fix.

Since Sitecore 8.1, field validation results are shown inline in text fields. This is quite useful for content editors, both for showing errors, but also to give soft errors, such as warnings or suggestions.

Notification MessagesOne can argue that all non-valid results from validators are errors, but I think it makes more sens to say “Show suggestion” instead of “Show error” next to a validation suggestion.

This can be fixed with a simple line change in
sitecore\shell\client\Sitecore\ExperienceEditor\ValidateFieldsUtil.js.
At about line 28, you’ll find the following row:
$(notification).append(DOMHelper.getNotificationOption(showErrorText, null, notificationId));
Just insert this line before it:
showErrorText = "Show " + validateFieldsUtil.getNotificationType(error);

If you’re managing sites with multiple languages, you might want to ask for a patch with reference number 55656. When editing a page, with components, in the Experience Editor, on a non-“en” language (assuming “en” is your Sitecore default UI language), validators are executed with the wrong context languages for components. Essentially, the validators are not being executed on the current language. Note that this error only applies to components, i.e items that are referenced from the current page being edited.

If you’re managing website where there are many items on the same level, you may also find it hard to use the navigation bar in the Experience Editor. Simply because there are no vertical scroll bars. Patch with reference number 85083 will fix this.

Optimized Progressive Images in Sitecore

$
0
0

Serving optimized images on websites is essential to get high performances website and get a good user experience. There are numerous implementations of this and here is yet another one. Inspired by Anders Laub previous posts on cropping and compressing images, I decided to make one that makes optimized progressive images.

Normal images are loaded from top to bottom, aka baseline images. The advantage of progressive images is that the entire image is shown almost right away, but the image is blurry at first when only a portion of the image is loaded and the image then becomes clearer as more image data is loaded in the browser. This makes the perceived load speed faster to the viewer.

Here is how a standard baseline image will load in a browser:

And here is how the same image will load in a browser when saved as progressive jpeg.

Leveraging from the Magick.Net library, a .Net port of the famous ImageMagick library, we can easily create optimized progressive images and gain quite good server rendering performance as well. By hooking into the <getMediaStream> pipeline, we can just add a processor for cropping and one for saving the image. By doing so, we’ll also leverage from Sitecore built-in disk cache for resized images. This means the CPU heavy creation of the images will only be performed once.

Later versions of Sitecore (7.5+) requires a hash code in the image URL to prevent Denial of Service attacks. One could send a lot of requests to the servers and alter the requested image width or height with just one pixel and that would cause Sitecore to generate a new image for every new unique request. The hash code prevents this by ignoring the parameters and just send the original file when the hash parameter is missing or incorrect. It’s therefore important that we add our custom cropping and quality parameters to the media library <protectedMediaQueryParameters> list. And as always, HashingUtils.ProtectAssetUrl needs to be called when creating the image urls.

It’s important to verify that images are scaled properly, since rendering a page with incorrect or missing hashes may not be clearly visible to the human eye.

Sitecore Nuget
Sitecore has finally provided a official Nuget feed. If you’re not using it already, add the feed to your Package Sources list in Visual Studio. The Sitecore Official feed is available here https://sitecore.myget.org/F/sc-packages/api/v3/index.json for Visual Studio 2015+. If you’re using an older version (VS 2012+) you’ll need to use this NuGet V2 feed: https://sitecore.myget.org/F/sc-packages/

The feed contains Sitecore packages from 7.2 and upwards and packages are provided in alternative variants, such as component packages, single assembly packages with references and single assembly packages with no references. I find the last one very attractive when distributing small modules, such as this one.

The code
Feel free to grab the code from github: https://github.com/mikaelnet/sitecore-imaging. It’s currently linked to Sitecore 8.2, but you can change the version reference to fit your solution. You may also want to change the Magick.Net reference to fit your environment, but make sure you reference a late version that isn’t linked to a Image Tragick vulnerable version of ImageMagick.

The cropper processor uses the cw and ch parameters to set a crop width and height. The progressive jpeg processor uses the jq parameter to set jpeg quality between 0 and 100.

Using the library is pretty straight forward. You can either create bare metal URLs by just appending the query string parameters you want and wrap it in HashingUtils.ProtectAssetUrl. Alternatively, you can use the fluent ImageRendering helper class to chain image commands, such as imageRendering.WithAutoCrop(400,300).WithQuality(75).GetUri() and so on.

Enjoy!

Viewing all 66 articles
Browse latest View live