Sitecore Rocks – SitecoreCop

SitecoreCop is a cool little feature in Sitecore Rocks, which can check a lot of the Sitecore configurations. It can be executed by connecting to a Sitecore instance using Sitecore Explorer, then right-clicking on a Sitecore Item -> SitecoreCop -> Run SitecoreCop.

By default there are already a whole lot of rules available, from checking whether fields contain Lorem Ipsum text to whether the license is about to expire and from whether media items have actual media attached to them to whether the admin password is still the default – and a whole lot more still.

Even so, if one would want to add their own rules to it, that’s also possible but requires a bit more than just the code (which I didn’t find out until @TheRocksGuy gave me some pointers): The assemblyname has to start with Sitecore.Rocks.Server, so for instance Sitecore.Rocks.Server.CustomRules. Also, the assembly needs to reference Sitecore.Rocks.Server.Core and Sitecore.Rocks.Server.Validation.

The class then needs to be marked with the [Validation] attribute, in which you can specify a rule name and the rule category.

namespace Sitecore.Rocks.Server.CustomRules
{
  [Validation("This is a custom rule", "Items")]
  public class SomeCustomRule : ItemValidation
  {
    ...
  }
}

There are multiple existing Validation types to inherit from, such as ItemValidation or RenderingValidation.

You will then need to implement the CanCheck and Check methods. CanCheck will execute to see if the rule can be executed in the context you’re running it in and the Check method has the actual logic.

...
public bool CanCheck(string contextName, Sitecore.Data.Items.Item item)
{
  Assert.ArgumentNotNull(contextName, "contextName");
  return contextName == "Site";
}

public void Check(ValidationWriter output)
{
  // You'd probably want some logic here to make sure it only flags the problem in the right cases.
  output.Write(SeverityLevel.Warning, "Title", "This is the problem", "This is the solution");
}
...

When you then execute SitecoreCop (after making sure your assembly is copied across to the bin folder of your webroot) you might have your problem flagged up:

SitecoreCop custom rule

Adding FxCop rules to Sonarqube

In my posts about custom FxCop rules I mention creating rules for FxCop. In this post I’ll explain how to add the created rules to Sonarqube.

First of all, the assembly needs to be put on the server somewhere. The location doesn’t really matter too much, as long as it’s accessible for Sonarqube. In my case I just dropped it into the default FxCop Rules folder, which is located by default in C:\Program Files (x86)\Microsoft FxCop 10.0\Rules.

After that, Sonarqube needs to be told about the rules as well. The XML is actually fairly similar to the XML file we create for the FxCop rules in the first place.

<?xml version="1.0" encoding="utf-8" ?>
<rules>
    <rule key="Classname">
        <name><![CDATA[Name for the rule]]></name>
        <configKey><![CDATA[Classname@Location\Of\Assembly\Assemblyname.dll]]></configKey>
        <description><![CDATA[Rule description]]></description>
    </rule>
</rules>

So, to take the example of the GetItemUsingID rule, the XML could look something like this:

<?xml version="1.0" encoding="utf-8" ?>
<rules>
    <rule key="GetItemUsingID">
        <name><![CDATA[Enforce calling the GetItem method using an ID]]></name>
        <configKey><![CDATA[GetItemUsingID@$(FxCopDir)\Rules\Sitecore.FxCop.dll]]></configKey>
        <description><!CDATA[Use GUIDS where possible instead of path / name. This will improve performance as well as prevent code breaking if content is renamed or moved in the content tree.]]></description>
    </rule>
</rules>

When the XML has been created it has to be put in the FxCop custom rules field in Sonarqube. You can get to this by going to Sonarqube -> Settings -> Configuration -> .NET FxCop:
Sonarqube configuration settings

Before the rules can be used Sonarqube does have to be restarted. Then we can go into Sonarqube -> Settings -> Quality Profiles. Select the active code profile, and go to the inactive FxCop rules. Select the newly added rules to be active, and we’re all done.

I guess this would also be a good time to mention the source code of the 11 rules I initially created is available on Bitbucket and Sitecore’s marketplace, so feel free to create a fork and add your own rules!

If you just want to use the rules, that’s possible as well, just go to the download page on Bitbucket and download the zipfile. The zipfile contains a dll of the initial release at the moment, although I hope to steadily add new rules to the project.

Sitecore and FxCop – ItemAxes and Database Rules

This is the fourthpost in the Sitecore and FxCop series

I’ve been working on a bunch of easier to implement rules, which I figured I’d run through here. As always, I’m trying to improve the code, either increasing the performance of the code or making it a little more safe to use.

There’s a couple of categories in which I’ve added rules, and I’ll be creating a post per category, as it’ll be a huge post otherwise.

The categories I added rules in are:

ItemAxes & Database

This is one of the most important areas for performance issues. When we are using the ItemAxes to get all ancestors or descendants that can take a very big performance hit. It is often much better and faster to use Sitecore.ContentSearch (granted, this can only be done using Sitecore 7 and up). The same goes for the SelectItems() with Xpath as well.

So the four rules I put in on the ItemAxes are:

  1. Don’t use SelectSingleItem (accessible as Sitecore.Data.Items.ItemAxes.SelectSingleItem as well as Sitecore.Data.Database.SelectSingleItem)
  2. Don’t use SelectItems (accessible as Sitecore.Data.Items.ItemAxes.SelectItems as well as Sitecore.Data.Database.SelectItems)
  3. Don’t use GetAncestors
  4. Don’t use GetDescendants

On this blogpost I saw some stats for Lucene and fast query, which is not really a fair comparison I think because fast query will always go straight to the database, I figured I’d get a bit of data together.

Consider the following code:

var index = Sitecore.ContentSearch.ContentSearchManager.GetIndex("sitecore_master_index")
{
    using (var context = index.CreateSearchContext())
    {
        // ContentSearch
        Stopwatch sw = new Stopwatch();
        sw.Start();
        var indexed = context.GetQueryable<Sitecore.ContentSearch.SearchTypes.SearchResultItem>().Where(item => item.Name.Contains("s"));
        sw.Stop();
        Sitecore.Diagnostics.Log.Info(string.Format("ContentSearch. Elapsed time: {0}. Items found: {1}", sw.Elapsed, indexed.ToList().Count));
    }

    // Fast query
    Stopwatch sw2 = new Stopwatch();
    sw2.Start();
    var fQuery = Sitecore.Configuration.Factory.GetDatabase("master").SelectItems("fast://*[@@name='%s%']");
    sw2.Stop();
    Sitecore.Diagnostics.Log.Info(string.Format("Fast query. Elapsed time: {0}. Items found: {1}", sw2.Elapsed, fQuery.Length), this);

    // Normal query
    Stopwatch sw3 = new Stopwatch();
    sw3.Start();
    var query = Sitecore.Configuration.Factory.GetDatabase("master").SelectItems("//*[contains(@@name, 's')]");
    sw3.Stop();
    Sitecore.Diagnostics.Log.Info(string.Format("Normal query. Elapsed time: {0}. Items found: {1}", sw3.Elapsed, query.Length), this);
}

This gave me the following output in my Sitecore log file:

INFO ContentSearch. Elapsed time: 00:00:00:0178173. Items found: 8723
INFO Fast query. Elapsed time: 00:00:05.2718443. Items found: 5194
INFO Normal query. Elapsed time: 00:00:09.7881149. Items found: 4320

Those are some pretty big differences. These results are for just under 13000 items. Of course, it’s still not completely honest because the normal query does need to call the Contains function, which I don’t know the performance implications for…

Another big plus of using ContentSearch is that Linq is a lot easier to read in more complex queries than Xpath / Fast query.

From a custom rule perspective, those four rules are extremely simple to create. Almost all rules I added to my project so far use a lot of the same code – If the corresponding classes get called, flag them as warnings on the page.

Of course, as with all rules, there’s bound to be some exceptions and actual legitimate good uses of these classes. The rules are only there to point out that it might be a good idea to re-think that.

Sitecore and FxCop – Field Rules

This is the fourthpost in the Sitecore and FxCop series

I’ve been working on a bunch of easier to implement rules, which I figured I’d run through here. As always, I’m trying to improve the code, either increasing the performance of the code or making it a little more safe to use.

There’s a couple of categories in which I’ve added rules, and I’ll be creating a post per category, as it’ll be a huge post otherwise.

The categories I added rules in are:

Fields

I actually added a couple of field rules. In a previous post I was talking about accessing raw field value properly and getting items from the database with IDs rather than a path.

Now, of course it would also make sense to get the field values based not on the fieldname, but the GUID of the particular fields. This of course has the advantage of getting the correct field always, even when the fieldname changes.

I’ll admit this is a rule I had some difficulty with, although it ended up being very easy.

public override void VisitMethodCall(MethodCall methodCall)
{
    base.VisitMethodCall(methodCall);

    var memberBinding = methodCall.Callee as MemberBinding;
    if (memberBinding != null)
    {
        var methodCalled = memberBinding.BoundMember as Method;
        if (methodCalled != null)
        {
            if (methodCalled.FullName == "Sitecore.Collections.FieldCollection.get_Item(System.String)")
            {
                Expression parameter = methodCall.Operands[0];
                CheckValue(methodCall, parameter);
            }
        }
    }
}

In which CheckValue just tries to match the value of the parameter with a GUID, using a Regular Expression. The thing I had difficulties with was that I was accessing a FieldCollection, and could not inspect the actual line which was making the call.

Another rule which I think will be particularly useful is one that flags where people are trying to get the value from a CheckboxField – to check whether it’s been checked or not. The utils that come with Sitecore seem to be a little under-used, and one of the things that’s possible with the utils is exactly this: Get the value of a checkbox.

Consider the following code snippet:

namespace SomeNamespace
{
    public class SomeClass
    {
        public void SomeMethod()
        {
            var item = Sitecore.Context.Database.GetItem("{66EC0398-1E67-4D43-B2EB-ED29E3E8E291}");
            if (item != null)
            {
                CheckboxField field = (CheckboxField)item.Fields["checkbox"];
                if (field != null)
                {
                    var isChecked = field.Checked;
                }
            }
        }

        public void SomeOtherMethod()
        {
            var item = Sitecore.Context.Database.GetItem("{66EC0398-1E67-4D43-B2EB-ED29E3E8E291}");
            if (item != null)
            {
                var isChecked = MainUtil.GetBool(item["checkbox", false);
             }
        }
    }
}

Of course, instead of calling the field with its fieldname we should do that with the ID – but for this example it’s a bit easier to read like this.
We don’t have to first get the checkboxfield, then check whether it even exists followed by finally getting the value, we can just call Sitecore’s MainUtil.GetBool, pass in the value we want to convert to bool and the default value. A lot safer and it’s a couple lines shorter which should appeal to us lazy programmers ;-)

I figured I’d skip the same FxCop rule code this time, as it’s the same as the examples in the other posts. The method we’re checking for this time is Sitecore.Data.Fields.CheckboxField.get_Checked . If that method is indeed called, flag that as a possible problem.

Sitecore and FxCop – Security Rules

This is the third post in the Sitecore and FxCop series

I’ve been working on a bunch of easier to implement rules, which I figured I’d run through here. As always, I’m trying to improve the code, either increasing the performance of the code or making it a little more safe to use.

There’s a couple of categories in which I’ve added rules, and I’ll be creating a post per category, as it’ll be a huge post otherwise.

The categories I added rules in are:

  • Security
  • Fields
  • ItemAxes
  • Database

Security

The first new FxCop rule I implemented is to make sure to use the Sitecore.Security.Accounts.UserSwitcher, rather than the Sitecore.SecurityModel.SecurityDisabler.
The SecurityDisabler elevates the users permission (temporarily) to administrator rights, which has the potential to be very dangerous to use and errors to potentially be very costly. An interesting side effect is that anything done with the SecurityDisabler will show up as being done by the sitecore\Anonymous role, messing up the audit trail.

However, if we use a UserSwitcher, we will actually tell the system to do something based on a user, with the access rights of that user. Consider the following code snippet:

namespace SomeNamespace
{
    public class SomeClass
    {
        var home = Sitecore.Context.Database.GetItem("{66EC0398-1E67-4D43-B2EB-ED29E3E8E291}");
        using (new Sitecore.SecurityModel.SecurityDisabler())
        {
            home.Delete();
        }

        Sitecore.Security.Accounts.User someUser = Sitecore.Security.Accounts.User.FromName(@"sitecore\SomeUser", false);
        using (new Sitecore.Security.Accounts.UserSwitcher(someUser))
        {
            home.Delete();
        }
    }
}

Assuming we have set up the access for the SomeUser account correctly, the home item will not be deleted with the UserSwitcher, but will be deleted with the SecurityDisabler, because we have effectively disabled all security there. In our case, because the SomeUser account has been set up correctly, an AccessDeniedException will be thrown.
Although this is a trivial example, it does point out the dangers of the SecurityDisabler.

The rule is actually also very easy, and is very similar to the rules described in earlier posts. The biggest difference is that because we’re now looking for a constructor rather than a method that’s been called, we need to override the VisitConstruct method.

public override void VisitConstruct(Construct construct)
{
    base.VisitConstruct(construct);

    Method method = (Method)((MemberBinding)construct.Constructor).BoundMember;
    if (method.FullName.Equals("Sitecore.SecurityModel.SecurityDisabler.#ctor", StringComparison.OrdinalIgnorecase)
    {
        this.Problems.Add(new Problem(this.GetResolution(method), construct));
    }
}

We don’t really care about anything else – if the SecurityDisabler is getting called, we want to add a warning that the UserSwitcher should be used.

Sitecore and FxCop – Accessing Raw Field Values

This is the second post in the Sitecore and FxCop series. Read the first post here. 

The second rule I came up with has to do with how to access raw field values.

Let’s say we want to grab the raw value of the Title field on the current item. There’s two different options we can use:

var fieldValue = Sitecore.Context.Item.Fields["Title"].Value;

Which really isn’t correct – what if there is no Title field? Or what if we have a typo in the fieldname? We’d end up with a NullReferenceException. To get around this we could use:

var field = Sitecore.Context.Item.Fields["Title"];
if (field != null)
{
    var fieldValue = field.Value;
}

The other option is:

var fieldValue = Sitecore.Context.Item["Title"];

In this case, if there is no Title field, or if we have a typo in the fieldname, we’d get an empty string.

To be absolutely clear, this is only to get the raw field value.  To display it on the front end side of things usually one would use a FieldRenderer instead anyway.

So, the second option is shorter and safer. Seems like a good thing to check for.

In the Rules.xml we need to add the following to our existing <Rules> definitions:

<Rule TypeName="AccessRawFieldValueProperly" Category="Sitecore.BestPractice" CheckId="SC002">
  <Name>Get the raw field value properly</Name>
  <Description>
    Accessing the raw field value through the Field property on the item can throw NullReferenceExceptions when
    the fieldname changes, the field doesn't exist or there's a misspelling in the fieldname.
  </Description>
  <Url />
  <Resolution>
    Raw field value is accessed by Item.Fields["Fieldname"].Value. Raw field values should be accessed by Item["fieldname"]
  </Resolution>
  <Email />
  <MessageLevel Certainty="70">Error</MessageLevel>
  <FixCategories>Breaking</FixCategories>
  <Owner />
</Rule>

As always, the TypeName needs to match the name of the class.

internal sealed class AccessRawFieldValueProperly : BaseRule
{
  public AccessRawFieldValueProperly() : base("AccesssRawFieldValueProperly")
  {
  }

  public override ProblemCollection Check(Member member)
  {
    var method = member as Method;
    if (method != null)
    {
      VisitStatements(method.Body.Statements);
    }

    return this.Problems;
  }

  public override void VisitMethodCall(MethodCall methodCall)
  {
    var memberBinding = methodCall.Callee as MemberBinding;
    if (memberBinding != null)
    {
      var methodCalled = memberBinding.BoundMember as Method;
      if (methodCalled != null)
      {
        if (methodCalled.FullName == "Sitecore.Data.Fields.Field.get_Value")
        {
          Problems.Add(new Problem(GetResolution(), methodCall));
        }
      }
    }

    base.VisitMethodCall(methodCall);
  }
}

Then, when I have a solution with the following code:

public void SomeMethod()
{
  var rawFieldValue1 = Sitecore.Context.Item.Fields["fieldname"].Value;
  var rawFieldValue2 = Sitecore.Context.Item["fieldname"];
  var field = Sitecore.Context.Fields["fieldname"];
}

I would expect to get one error in FxCop. I still want to be able to grab the actual field of course.

The correct error showing up

When I have a couple more rules in here I’m thinking about releasing this to the Sitecore Marketplace, so your input is very welcome. Is there any rule you would like to see added?

Sitecore and FxCop

I’ve been looking to create some FxCop rules for Sitecore development to get easier overviews to check (and possible enforce) using best practices on Sitecore development. This is the first post in a series of  Sitecore specific rules, which eventually (when there’s a couple of useful rules) will be released through the Sitecore Marketplace as well.

Creating custom rules for FxCop isn’t that hard, especially with some great resources such as this post. Following that post, there’s a couple of simple steps which boil down to the following:

Create a new class library project. The project will of course require references to the FxCop assemblies, FxCopSdk.dll and Microsoft.Cci.dll.

After that, create a rule definition XML, a BaseRule and a custom Rule which inherits BaseRule. The custom Rule can now contain some code that will show if the code is abiding by the rules or breaking them.

Before I give an example of my custom rules, please note that it’s very important to set the Build Action for the XML file as Embedded Resource, otherwise the error ‘Analysis was not performed; at least one valid rules assembly and one valid target file must be specified’ will be thrown.

No valid rules assembly and no valid target file

Oops…

I would strongly advise having a read through the blog post to get a better understanding what’s going on.

Sitecore rules

I’ve created a baseclass called BaseRule, which is exactly the same as in the blog mentioned earlier so I won’t describe that one. As for the rule I’ll describe here, I want to make sure that the GetItem method is called using a GUID, not a path.

To do this, I’ve created a Rules.xml which looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<Rules>
  <Rule TypeName="GetItemUsingID" Category="Sitecore.BestPractice" CheckId="SC0001">
    <Name>Enforce calling the GetItem method using ID</Name>
    <Description>
      Use GUIDs where possible instead of path / name. This will improve performance, as well as prevent any breakage if you move content to a new location in the content tree.
    </Description>
    <Url />
    <Resolution>GetItem is called passing in {0}. GetItem should be called passing in an ID.</Resolution>
    <Email />
    <MessageLevel Certainty="80">Error</MessageLevel>
    <FixCategories>NonBreaking</FixCategories>
    <Owner />
  </Rule>
</Rules>

A couple clarifications: I’ve put the certainty on 80 for now, since all I’m doing is checking if there’s a string parameter that does not have a GUID. I completely ignore things like queries, empty IDs and the like.

Also, the Resolution message could be a little clearer, pointing out where the GetItem method gets called for instance, but for the purpose of this example it’s clear enough.

namespace Sitecore.FxCop
{
  using System;
  using System.Collections.Generic;
  using System.Text.RegularExpressions;
  using Microsoft.FxCop.Sdk;

  internal sealed class GetItemUsingID : BaseRule
  {
    public GetItemUsingID() : base("GetItemUsingID")
    {
      public override ProblemCollection Check(Member member)
      {
        Method method = member as Method;
        if (method != null)
        {
          this.Visit(method.Body);
        }

        return this.Problems;
      }

      public override void VisitMethodCall(MethodCall call)
      {
        base.VisitMethodCall(call);

        Method targetMethod = (Method)((MemberBinding)call.Callee).BoundMember;
        if (targetMethod.DeclaringType.FullName.Equals("Sitecore.Data.Database", StringComparison.Ordinal) && targetMethod.Name.Name.Equals("GetItem", StringComparison.Ordinal))
        {
          Expression endResponse = call.Operands["0"];
          if (endResponse.NodeType = NodeType.Literal)
          {
            var value = ((Literal)endResponse).Value.ToString();
            var match = Regex.IsMatch(value, @"\b[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\b", RegexOptions.IgnoreCase);
            if (!match)
            {
              this.Problems.Add(new Problem(this.GetResolution(value, call))
            }
          }
        }
      }
    }
  }
}

A couple of interesting things here:

  • On line 8: The name of the class needs to be exactly the same as what’s defined in the XML definition.
  • On line 30: we only check the first parameter (so the parameter at index 0). This is because the GetItem method, when a path or GUID is used, will always have that path or GUID as the 0th parameter.
  • On line 31: we are only interested in that parameter when it’s a string. Remember, the first parameter for GetItem is either string, DataUri or ID. We could check them all of course, but right now I just want to make sure the first parameter is a string, and contains a GUID
  • On line 37: I pass in the value of the string that gets passed to GetItem, which will replace the {0} token defined in the XML with that value

So now consider the following code:

  var database = Sitecore.Data.Database.GetDatabase("master");
  var item1 = database.GetItem("/sitecore/content/home");
  var item2 = database.GetItem("{4E7AB8D1-6A39-4C8C-BF5B-816F8105BFFD}");

When I then add my custom rule to be inspected by FxCop, I get the following:

FxCop showing custom rule

A grand start, although I would still only get one message about GetItem even if I would have multiple instances of calling GetItem with a path in the same method.

Which FxCop rules would you like to have regarding Sitecore best practices?

Golden nuggets – Reset Blank

During my Sitecore 7 training the trainer, Raul Jimenez, pointed out the ‘Reset Blank’ checkbox on the Template Fields template. 

Image

Before today I’ve never used it, so I had no clue what it was that the checkbox does. I figured this might be the case for a lot of others as well:

If the ‘reset blank’ checkbox is ticked, when that field is saved without a value the field will actually be reset to the default value. This is very handy in situations where it doesn’t make sense to have the field blank.

Warning your Content Editors

I was recently in a situation where Content Editors could add a widget which needs a datasource, otherwise it would display the wrong information. Even though we’ve had meetings, trainings, wikis and more this is still something that could be forgotten, or the datasource could still be in workflow and therefore not be live yet resulting in incorrect information. Normally, I would suggest not displaying it at all if it’s not there (rather than displaying ‘incorrect’ data), but that would lead to questions about why it’s not there, while on our staging environment it all looks good (in case of Workflow at least). I’m loving the Rules Engine, and figured this would be a good usage of said engine.

Sitecore has a very easy way to display alerts in the Content Editor when an item is selected – Content Editor Warnings. When adding a rule to /sitecore/system/Settings/Rules/Content Editor Warnings/Rules Sitecore will run these rules when you select an item.

Now, on to the new rule. In our case, two new Conditions need to be created. One to check whether the datasource of the widget is empty and one to check whether the item the datasource is linking to is in Workflow.

Checking whether the datasource is set

I’ve created a new Folder called Conditions in /sitecore/system/Settings/Rules/Content Editor Warnings/Conditions, and below that created a new Condition.

The Text of the condition: where the datasource for [RenderingId,Tree,root={EB2E4FFD-2761-4653-B052-26A64D385227},specific] is not set

Where the GUID points to the Layout item. Please note that you can also specify a path if that’s more to your liking – I prefer using GUIDs as those don’t change, while paths may. I’ve also filled the Type field to point to my class and assembly.

Now the code. I’ve created a new class called EmptyDataSourceWarning, and overridden the Execute method:

public class EmptyDataSourceWarning<T> : StringOperatorCondition<T> where T : ContentEditorWarningsRuleContext
{
  public string RenderingId { get; set; }

  protected override bool Execute(T ruleContext)
  {
    Assert.ArgumentNotNull(ruleContext, "ruleContext");
    foreach (RenderingReference reference in ruleContext.Item.Visualization.GetRenderings(Sitecore.Context.Device, false))
    {
      if (base.Compare(reference.RenderingID.ToString(), RenderingId)
      {
        if (string.IsNullOrEmpty(reference.Settings.DataSource))
        {
          return true;
        }
      }
    }

    return false;
  }
}

I think this speaks for itself for the most part – the RenderingId gets passed in from Sitecore (this is the [RenderingId,Tree,root={EB2E4FFD-2761-4653-B052-26A64D385227},specific] bit in the condition).

Because the rendering can be added to the page multiple times I’m using a foreach loop to go through all renderings. If the rendering’s ID and RenderingId match AND the datasource of the rendering is not set, the action needs to be triggered.

For the action I just have the ‘show editor warning’ which comes out of the box with Sitecore.

rule1

Don’t mind the ‘and where true’, which I just used for debugging purposes.

The result:

homedatasourcenotset

In my case there was just one widget that actually needs to have its datasource set, but if all of them need to be set it (or the rule needs to be inverted) that’s easily changed.

Checking the workflow status of the datasource item

Another Condition needs to be created, this time to check the datasource item’s workflow status.

The Text of the Condition: where the datasource item for [RenderingId,Tree,root={EB2E4FFD-2761-4653-B052-26A64D385227},specific rendering] is in workflow

The code:

public class DataSourceItemInWorkflow<T> : StringOperatorCondition<T> where T : ContentEditorWarningsRuleContext
{
  public string RenderingId { get; set; }

  protected override bool Execute(T ruleContext)
  {
    Assert.ArgumentNotNull(ruleContext, "ruleContext");

    foreach (RenderingReference reference in ruleContext.Item.Visualization.GetRenderings(Sitecore.Context.Device, false))
    {
      if (base.Compare(reference.RenderingID.ToString(), RenderingId))
      {
        var item = Factory.GetDatabase("master").GetItem(reference.Settings.DataSource);
        if (item != null)
        {
          IWorkflow wf = Factory.GetDatabase("master").WorkflowProvider.GetWorkflow(item);
          return !wf.IsApproved(item)
        }
      }
    }

    return false;
  }
}

Mind you, this only checks if the latest version is approved, it will not check if there’s an older version that’ll go live, nor does it check things like publishable etc.

I’ve actually created a new rule in the same rule item I already had:

rules

And the result when I do set a datasource, but point to an item which is in workflow, as expected:

homedatasourceworkflow

Of course, this is a reasonably simple example, but it does demonstrate that without too much effort we can improve the warnings to our Content Editors.

Contextual Ribbons In Page Editor – Part 2

While I was creating another Page Editor ribbon  was hoping to find another way of making the ribbons contextual. I did find an easier way to achieve the same goal, which I will explain here.

In the Core database, in /sitecore/content/Applications/WebEdit/Ribbons/WebEdit, I created a new item based on the /sitecore/templates/System/Ribbon/Strip template.

I set the Type field to my custom class (TestApplication.Commands.Strips.TestStrip).

Here’s the code for the class.

namespace TestApplication.Commands.Strips
{
    using System.Web.UI;
    using Sitecore.Data.Items;
    using Sitecore.Diagnostics;
    using Sitecore.Shell.Framework.Commands;
    using Sitecore.Shell.Web.UI.WebControls;
    using Sitecore.Web.UI.WebControls.Ribbons;

    public class TestStrip : RibbonStrip
    {
        public override void Render(HtmlTextWriter output, Ribbon ribbon, Item strip, CommandContext context)
        {
            Assert.ArgumentNotNull(output, "output");
            Assert.ArgumentNotNull(ribbon, "ribbon");
            Assert.ArgumentNotNull(strip, "strip");
            Assert.ArgumentNotNUll(context, "context");

            if (Helper.DescendantOrSelf(context.Items[0], TestConstants.TestID))
            {
                ribbon.RenderChunks(output, strip, context, false);
            }
        }
    }
}

The call to Helper.DescendantOrSelf is a simple test class I created to check whether context.Items[0] is a descendant (or self) of an item with the GUID specified. If that’s the case, the strip will be rendered, otherwise it will not appear.

Easy, no? Sure, it’s theoretically not the same as Contextual Ribbons – you don’t have to set anything in Sitecore, and in this case has hardcoded when to display – but it does do the trick.

While looking for this however, I also noticed (in the Strip item) the ‘Contextual Header’ field. I haven’t been able to find an example of it filled and haven’t yet experimented with it to see how it works. If anyone has any clue or pointers on that, please let me know in the comments.