codeflood logo

Item Auto Golive and Expire

Sitecore supports setting publishing restrictions on content items using dates for when the content should go live and when to take the content off the live site. I find many content authors expect these changes to their content to happen automatically, but in Sitecore's model a publish operation still needs to occur to have the changes affect the live database. This is because the Master database contains all copies of all the content and when the publishing operation occurs, the publisher is what determines which items will be pushed over to each publishing target. This publisher component reads the publishing restriction fields of the candidate items to determine if they should be pushed over to the publishing target or not.

So once you understand how the publishing model works you can understand why the publish operation still needs to occur once the item is "publishable" for the changes to be realised in the live database. But I do like the idea of having content automatically go live and expire without any outside influence. Let's go ahead and tweak Sitecore to work this way.

It's the publisher that moves the items from one database to another, so you're first reaction may be to write a class which inherits the publisher and override the methods which determine what makes an item "publishable". The issue is the publisher is not a single class; it is many classes working together to perform this operation. And in addition to this, there are cases when the item we're trying to have published is actually published. Every publish operation copies the publishable items from the source database to the target database (using the chosen publish mode) and removes expired content from the target database. If you drop down a few layers in Sitecore and inspect the interface of the DataProvider class you'll find methods to create items, move items, retrieve items and yes, delete items. So we're not just trying to have our item included in the publish operation, as it may already be included.

Let's simplify. Rather than trying to change the publishing model which is quite complex, let's approach the problem from a different angle. What if we were to add a different set of fields to the item which we used to set a go live and expire date for the content on. With these dates set in a different location then the publishing components would publish our item and we could use another mechanism to control when an item is ready to be live.

So we've now changed our problem from a publishing problem to an item retrieval problem. Luckily Sitecore contains a single class which is responsible for retrieving items; the Item Manager. We could quite easily extend this class and provide an implementation which takes into account our new custom publishing fields so the item will not be returned if the current date and time doesn't fall within the range between these 2 fields.

Inspecting the web.config file you'll find the item manager uses a provider model to provide this functionality. And the default provider class is Sitecore.Data.Managers.ItemProvider. So we can create our own Item Provider class which inherits this class and override the virtual GetItem(ID, Language, Version, Database) method to include a check for our publishing fields. We don't have to worry about overriding the other GetItem overloaded methods cause they all call into this method eventually.

But we also need to take into account the database in which this code is executing. Note in web.config that we don't define an Item Provider per database, we have a single globally shared Item Provider. You might also notice the signature for the GetItem method we're overriding includes the database as a parameter as well. We wouldn't want to filter the items in master where we're editing content, otherwise as soon as you set a date which invalidates the current date the item would disappear out of the content editor and you'd never be able to find it to update it again. So we need to make a DB check before we check for the dates.

You may be tempted to just check if the DB name equals "web", but please don't. Sitecore allows you to configure multiple content and delivery databases. In the case of multiple delivery databases you would need to check for more DB names. The best way to do this is to check for publishing targets.

A publishing target is merely a database which can have items published into it. These are the databases where we want to filter the items. Due to the fact that the publishing targets are defined in each source database, the ideal solution would be to enumerate the databases and add all the publishing targets found into a list. Unfortunately that would require a call to GetItem (eventually) on the database which will pass that call onto the Item Provider, which we're currently inside. If you try this you'll soon become acquainted with a StackOverflowException.

So en lieu of a more seamless solution, we'll just define in our Item Providers web.config declaration which databases should be filtered.

<add name="default"
  type="Sitecore.Starterkit.Sandbox.ItemProvider, Sitecore.Starterkit">
  <databases hint="list">
    <database>web</database>
  </databases>
</add>

To populate the databases into our class all we have to do is provide a property of type ArrayList and let the Sitecore object factory take care of the rest.

All we have to do now is check to see if we need to filter the items or not. Of course we'll need to check the database name is in the list we defined in the config file, but we'll also have to check to see if we're currently publishing or not. The reason for this is that the Item Provider is used to retrieve all items including when retrieving items during publishing in both the source and target databases. If you filter the items in the target database and the filter is invalidating the item, you won't be able to update the item to validate the filter again as when the publisher tries to retrieve the item to change it's field value, the filter will not allow the item to be returned. This is why we need to check for publishing. It's actually quite easy to check if we're publishing, just check the name of the context site.

Our resulting class may look like this.

using System;
using System.Collections;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Globalization;

namespace Sitecore.Starterkit.Sandbox
{
  public class ItemProvider : Sitecore.Data.Managers.ItemProvider
  {
    private readonly ArrayList m_databases = new ArrayList();

    public ArrayList Databases
    {
      get { return m_databases; }
    }

    protected override Sitecore.Data.Items.Item GetItem(ID itemId,
      Language language, Sitecore.Data.Version version, Database database)
    {
      Item item = base.GetItem(itemId, language, version, database);

      if (database != null &&
        m_databases.IndexOf(database.Name) >= 0 &&
        item != null &&
        Context.Site.Name != "publisher")
      {
        // Perform check for our date fields
        var golive = DateTime.MinValue;
        var expire = DateTime.MaxValue;

        if (item.Name != "__Standard Values")
        {
          if (item.Fields["golive"] != null &&
            item["golive"] != string.Empty)
          {
            DateField goliveField = item.Fields["golive"];
            golive = goliveField.DateTime;
          }

          if (item.Fields["expire"] != null &&
            item["expire"] != string.Empty)
          {
            DateField expireField = item.Fields["expire"];
            expire = expireField.DateTime;
          }

          DateTime now = DateTime.Now;
          if (now < golive || now > expire)
            item = null;
        }
      }

      return item;
    }
  }
}

Note the check for when we're asked to get a standard values item. If we don't perform this check we'll end up in another recursive dead-end loop. Obviously when we query for an item's field Sitecore in turn looks for the standard values item to check that, so we need to guard against that case.

And lastly we need to define the "auto golive expire" data template which will define the date fields we're after, and then adjust existing data templates to inherit that template so they can make use of this new capability.

auto publish

And there you have it. Items which inherit from the "auto golive expire" data template can now be dynamically scheduled on the live site and they can appear and disappear without the need for a publish (after you've published your schedules of course).

Comments

Raul

That's all very clever... I guess you can't use rendering caching then.

Alistair Deneys

Thanks Raul, And well done on picking that up. Thankfully, yes, you CAN still use caching for your presentation components. But now in addition to the cache clear on publish, we also need the cache to clear periodically on a timer. So set your control to be cacheable, then set the CacheTimeout property (inherited from WebControl) to a timespan like 00:01:00 to have the control cached for 1 minute. This can be done for both statically placed controls (just add these as attributes in your HTML) and dynamically placed controls (set the CacheTimeout as a parameter in your presentation deifnition). Then you just have to get the balance between dynamically appearing content and performance gains through caching right. Even if you cached for 1 minute performance would improve significantly.

[...] Item Auto Golive and Expire « Coffee => Coder => Code [...]

Paul

Very nice! The one thing that wasn’t entirely clear to me was if this could work across multiple versions of an item. It seems like either you get the item or you don’t. I don’t see any granular logic for checking for and returning previous versions. In order for that to work I would assume that multiple versions would need to have been published to Web in the first place which doesn't happen right?

Alistair Deneys

Hi Paul, Yes, you're right. This approach won't work with versions of an item because as you point out, the web database only contains the highest numbered publishable version of an item. To have multiple items supported you'd have to adjust the publisher to have it publish the versions even if they weren't required. Looks like I'll have to alter the publisher after all...stay tuned :)

Anders Gjelstrup

What about preview forward in time in this matter? That would not work either, would it?

evb

Using the indexer was giving us a problem (linked to the use of sitecore 6.5?): stackoverflow exception We changed:
if (item.Fields["GoLiveDate"] != null &amp;&amp; item["GoLiveDate"] != string.Empty)
{
DateField goliveField = item.Fields["GoLiveDate"];
golive = goliveField.DateTime;
}

if (item.Fields["ExpireDate"] != null &amp;&amp; item["ExpireDate"] != string.Empty)
{
DateField expireField = item.Fields["ExpireDate"];
expire = expireField.DateTime;
}
To: if (item.Fields["GoLiveDate"] != null &amp;&amp; item.Fields["GoLiveDate"].HasValue)
{
golive = ((DateField) item.Fields["GoLiveDate"]).DateTime;
}

if (item.Fields["ExpireDate"] != null &amp;&amp; item.Fields["ExpireDate"].HasValue)
{
expire = ((DateField) item.Fields["ExpireDate"]).DateTime;
}

And the problem was gone.

Leave a comment

All fields are required.