Sitecore Quick Tip: Programmatically Set Item to Not Publishable & Check if Is Publishable

Pretty straight forward but I needed this today. To set an entire item (not just certain versions of it) to not publishable:


using (new SecurityDisabler())
{
    item.Editing.BeginEdit();
    item.Publishing.NeverPublish = true;
    item.Editing.EndEdit();
}

Now of course this by itself will not remove the item from the content delivery servers, it needs to be published, so you can follow this up with:


Sitecore.Publishing.PublishManager.PublishItem(
    item,
    new Database[] { Database.GetDatabase("web") },
    new Language[] { Language.Parse("en") },
    false,
    true,
    false);

Which essentially says publish the item to the web database in english, do not perform a deep publish, perform a smart publish and do not publish related items.

Now more interestingly if you want to take any given item and tell whether its publishing is restricted in any way you can use the following:


var pubInfo = PublishingInformationBuilder.GetPublishingInformation(item, PublishingInformationLevel.Item);

var restrictions = pubInfo.OfType<ItemPublishingRestrictedError>().FirstOrDefault<ItemPublishingRestrictedError>();

This requires the using statements:


using Sitecore.Publishing.Explanations;
using Sitecore.Publishing.PublishingInformation;

Advertisements

Sitecore Sunday Pt 1: Programmatic Layout Assignment

It has been some time since I last updated this blog and it has been quite a year and a half! It all started when my family and I decided that after 5 years of living in Toronto and working for nonlinear creations it was time to move back to the UK. I was also in the middle of delivering a large project during the move and it wasn’t long after I got back to the UK that I began helping nonlinear start the nonlinear London office! For those of you who are familiar with nonlinear creations you may also be aware that nonlinear was acquired by Valtech in June.

So this is all basically a reason (read excuse) for my dip in contributions to the Sitecore community with the exception of presenting Sitecore XP at Scale at the London user group in May. It is also the motivation to get involved again and it starts with this blog series entitled Sitecore Sundays. The idea is simple, write a Sitecore blog post every Sunday for a 100 Sundays (100 always seems like a nice round number and a challenge).

So without further ado let’s get into the first post in the series, programmatic layout assignment.

The standard way that Sitecore “presents” a page is by resolving the context item (the page) and then inspecting the renderings (shared) and final renderings (not shared) field of the context item and its templates standard values. This is all known as resolving layout deltas and is a fairly complex subject that I won’t go into in detail now but follow this link for a flow chart of the process.

But what if we want to resolve the presentation that should be used for the context item programmatically based on some business logic? What if the context item has no presentation details associated with its templates standard values (think data repositories)? What if we want to expose a subset of products to a version of the presentation that contains a specific personalisation scenario?

Well, it turns out there’s a pipeline for that (disclaimer: this applies to MVC only, but we’re all using MVC now, right?). It’s in Sitecore.MVC.config and by default it looks like this:

<mvc.getXmlBasedLayoutDefinition>
  <processor type="Sitecore.Mvc.Pipelines.Response.GetXmlBasedLayoutDefinition.GetFromLayoutField, Sitecore.Mvc"/>
</mvc.getXmlBasedLayoutDefinition>

The GetFromLayoutField processor inherits from GetXmlBasedLayoutDefinitionProcessor which takes in a GetXmlBasedLayoutDefinitionArgs parameter in its constructor that has one really important property, ContextItem. Essentially if ContextItem is set on the args it will use that item to get the presentation details instead of the default, which is the standard context item:

protected virtual XElement GetFromField(GetXmlBasedLayoutDefinitionArgs args)
{
    Item contextItem = args.ContextItem ?? PageContext.Current.Item;
    
    if (contextItem == null)
    {
        return null;
    }

    return this.GetFromField(contextItem);
}

So this gives us an opportunity to place a processor in the pipeline that runs before the default processor and sets the ContextItem on the args based on some business logic. Et voila, dynamic presentation resolution that means the presentation item and the context item do not have to be the same thing. There are lots of possibilities with this.

Until next Sunday!

Disable WebAPI Verbs (Http Methods) on Sitecore Content Delivery Servers

It’s increasingly common to implement web services in a Sitecore hosted solution using Microsoft’s WebAPI framework. I’m personally a fan of this approach and if you’re paying attention to your usage of verbs then typically services responsible for modifying content in Sitecore will use action methods decorated with POST, PUT or DELETE http methods.

In a typical scenario you would only want these services to be accesible in content management server roles with the distribution of content modifications to content delivery server roles handled by Sitecore’s publishing API (or task runner).

So how do I ensure that only GET requests will be accepted on a content delivery server? In this example I’ll setup my WebAPI endpoint configuration in the initialize pipeline:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="MyClass, MyAssembly" patch:before="processor[@type='Sitecore.Pipelines.Loader.ShowVersion, Sitecore.Kernel']" />
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

Inside of my class I will implement a method that reads a boolean value (which defaults to false to be safe) from a patch file that will instruct my WebAPI configuration method in terms of the constraints it should place on my WebAPI routes.

public void Process(PipelineArgs args)
{
    GlobalConfiguration.Configure(ConfigureWebApi);
}

protected void ConfigureWebApi(HttpConfiguration configuration)
{
    var routes = configuration.Routes;

    routes.MapHttpRoute("WebApiDefault", "projectname/api/{controller}/{action}/{code}", new
    {
        code = RouteParameter.Optional
    },
    new
    {
        // read route constraints from include file

        httpMethod = 
            SettingsUtil.GetBool("Api.ModificationsAllowed", false) 
                ? new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Put, HttpMethod.Post, HttpMethod.Delete) 
                : new HttpMethodConstraint(HttpMethod.Get)
    });
}

Now we simply to need to add config transforms to the include file in order to control the value of “Api.ModificationsAllowed” for each server role.

<configuration xmlns:x="http://www.sitecore.net/xmlconfig/" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <sitecore>
    <settings>
      <setting xdt:Locator="Match(name)" xdt:Transform="SetAttributes(value)" name="Api.ModificationsAllowed" value="true" />
    </settings>
  </sitecore>
</configuration>

That’s it, nice and simple!

Creating Localized PDFs in the Sitecore Media Library

Sitecore makes it very simple to create multiple language versions of page and component items but it’s not so obvious how to do the same with PDFs. By default Sitecore will create PDF items using the Unversioned PDF template meaning that the uploaded media file is shared across all languages. Here are three ways to get around this.

Upload Files (Advanced)

By far the simplest method is to train/instruct your content authors to use the Upload Files (Advanced) option when uploading files. This dialog provides a checkbox titled “Make uploaded media items versionable”, checking this box will create the media items using the Versioned PDF template allowing authors to then upload media files per language version of the media item.

Upload Files (Advanced)

Extend the uiUpload Pipeline

Option one is the simplest option but what if you want to automatically enforce that all PDFs uploaded can be translated without having to rely on content authors remembering to use the Upload Files (Advanced) option and then remembering to check the versionable checkbox? the uiUpload pipeline runs every time a media file is uploaded to the media library. To do this we can add a custom processor to the upUpload pipeline that runs after Sitecore’s Save processor.

<sitecore>
  <processors>
    <uiUpload>
      <processor patch:after="processor[@type='Sitecore.Pipelines.Upload.Save, Sitecore.Kernel']" mode="on" type="YourClass, Your.Assembly"/>
    </uiUpload>
  </processors>
</sitecore>

In your custom class you can then detect whether the media item just uploaded is based on the Unversioned PDF template and switch it to the Versioned PDF template.

    public class ChangeMediaTemplate
    {
        public void Process(UploadArgs args)
        {
            if (args.UploadedItems == null || !args.UploadedItems.Any())
                return;

            var db = Database.GetDatabase("master");

            foreach (var item in args.UploadedItems)
            {
                if (item.TemplateID != ID.Parse("{Unversioned PDF Guid}"))
                    continue;

                item.ChangeTemplate(new TemplateItem(db.GetItem(ID.Parse("{Versioned PDF Guid}"))));
            }
        }
    }

Custom Media Templates

Option 3 is an option you might want to consider if you also want to customize the fields on your PDF template. For example you might want to allow a content author to assign a document type to a PDF. Create a new template and add Versioned PDF to the list of the templates base templates. Add your custom fields to the template.

You can now add your custom template as an insert option. I would recommend doing this using the rules engine. Under the item /sitecore/system/Settings/Rules/Insert Options/Rules add a new item based on the Insert Options Rule template and edit the rule like so (replacing “specific template” with your custom template):

Insert Option Rule

Now content authors can create items under media folders using your custom template. The next step for the author is to select which language version they want to edit and then attach the media file to that version.

So there you have it, 3 options for localized PDFs in the Sitecore content management system.

Sitecore Heartbeat Page & Web Forms for Marketers on Remote Servers

When you are running Sitecore with multiple load balanced content delivery servers, typically you would use Sitecore’s heartbeat page to monitor the health of each content delivery instance:

http://host/sitecore/service/heartbeat.aspx

This service does more than just return a http 200 status letting you know that the site is running, it also iterates over each connection string defined in ConnectionStrings.config and executes a simple SQL query against sys.tables to check for un-responsive databases.

This is all fine and dandy until you throw Web Forms for Marketers into the mix. On a remote installation of Web Forms for Marketers a connection string is added to ConnectionStrings.config that communicates with a content management instance and looks something like this:

Unfortunately the heart beat page will try to process this connection string but it doesn’t know what to do with it so the page subsequently returns a 500 server error every time.

Fortunately Sitecore provides a way around this. If you look at the standard web.config you will not find a setting called:

Sitecore.Services.Heartbeat.ExcludeConnection

But, the heart beat page is coded to check for it (expecting a pipe separated list) and by default excludes the connection string called “LocalSqlServer”.

As we know the best practice when adding custom settings to a Sitecore solution is to use include files. So, to fix this problem all we need to do is add (or update an existing) include file that contains the following:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <settings>
            
            <!-- stops the heartbeat page from returning an error 500 on delivery servers -->
            <setting name="Sitecore.Services.Heartbeat.ExcludeConnection" value="LocalSqlServer|remoteWfmService"/>

        </settings>
    </sitecore>
</configuration>

And that’s it! We have a working heart beat page on a content delivery instance that also has Web Forms for Marketers installed.

Sitecore Item Web API Client: Create

This is a quick update to announce that the client library now supports item creation operations. There are a couple of considerations that need to be taken into account with regards to Security

  1. You must update the itemwebapi.access attribute for the target site in Sitecore.ItemWebApi.config to ReadWrite
  2. You must use an AuthenticatedSitecoreDataContext
  3. The user must have create permissions on the parent item
  4. To update the newly created items fields, the user must have write permissions for the item

Warning!

When creating new items you can define the parent item using Sitecore query, to be sure where your new item will be created limit the scope of the query to match a single item e.g. avoid things such as descendant selectors.

Example

var credentials = new SitecoreCredentials();

credentials.UserName = "sitecore\\foo";
credentials.Password = "bar";

var context = new AuthenticatedSitecoreDataContext("host", credentials);

var query = new SitecoreCreateQuery();

// the name of the new item
query.Name = "Foo";

// the id of the parent item
query.ItemId = "{11111111-1111-1111-1111-111111111111}";

// alternatively specify the parent with a query
// if you specify the ItemId it will take precedence
query.ParentQuery = "/sitecore/content/Home";

// Template can be a template id, branch id or path relative to the
// sitecore/Templates folder
query.Template = "{11111111-1111-1111-1111-111111111111}";
query.Database = "master";

query.FieldsToUpdate = new Dictionary<string, string>();

// fields can be specified by name or id
query.FieldsToUpdate.Add("Field Name", "Value");
query.FieldsToUpdate.Add("{11111111-1111-1111-1111-111111111111}", "Value");

// only return the fields we updated in the response to validate the operation
query.FieldsToReturn = new List<string>();
query.FieldsToReturn.Add("Field Name");
query.FieldsToReturn.Add("{11111111-1111-1111-1111-111111111111}");

ISitecoreWebResponse response = context.GetResponse(query);

if (response.StatusCode == HttpStatusCode.OK)
{
    Console.WriteLine(response.Result.Count);
    
    foreach (WebApiItem item in response.Result.Items)
    {
        Console.WriteLine(item.Path);
    }
}

Download the code here

Sitecore Item Web API Client: Update

This is a quick update to announce that the client library now supports field update operations. There are a couple of considerations that need to be taken into account with regards to Security

  1. You must update the itemwebapi.access attribute for the target site in Sitecore.ItemWebApi.config to ReadWrite
  2. You must use an AuthenticatedSitecoreDataContext
  3. The user must have write permissions on the item

Warning!

Field update queries are affected by the scope parameter. If the query is scoped to the parent or children of the item that matches the query or item id the fields on the parent and child items will also be updated.

Example

Update queries can be used with either item or expression queries. In the case of expression queries, all items matching the query will be updated.

var credentials = new SitecoreCredentials();

credentials.UserName = "sitecore\\foo";
credentials.Password = "bar";

var context = new AuthenticatedSitecoreDataContext("host", credentials);

var query = new SitecoreItemQuery();

// the id of the item to update
query.ItemId = "{11111111-1111-1111-1111-111111111111}";

query.Database = "master";

query.FieldsToUpdate = new Dictionary<string, string>();

// fields can be specified by name or id
query.FieldsToUpdate.Add("Field Name", "Value");
query.FieldsToUpdate.Add("{11111111-1111-1111-1111-111111111111}", "Value");

// only return the fields we updated in the response to validate the operation
query.FieldsToReturn = new List();
query.FieldsToReturn.Add("Field Name");
query.FieldsToReturn.Add("{11111111-1111-1111-1111-111111111111}");

ISitecoreWebResponse response = context.GetResponse(query);

if (response.StatusCode == HttpStatusCode.OK)
{
    Console.WriteLine(response.Result.Count);

    foreach (WebApiItem item in response.Result.Items)
    {
        Console.WriteLine(item.Path);
    }
}

Download the code here