The Little Book of Sitecore® Tips volume 2 now available

littlebookofsitecore_newsFollowing volume 1 I’m pleased to announce the launch of volume 2 of The Little Book of Sitecore® Tips.

Just like the first book, these tips were created from my daily workings with the Sitecore experience platform and we now have another instalment in the ongoing series.

The main purpose of The Little Book of Sitecore Tips series is a light-hearted look at some useful Sitecore tips. These tips are targeted at all levels of user but whether you’re recapping or learning, I hope you find the material either useful, or of some entertainment value.

Your can order the next volume from the likes of Amazon or Barnes & Noble – The booklittlebookofsitecore_unicorn is also available as an eBook on the iBooks Store.

Thanks is due to the technical reviewers Michael West and Matthew Dresser – and the usual thanks to Tamas Varga who was as helpful as ever. Thanks is also due to Mr Helix Thomas Eldblom, and Dominic Hurst who supplied some nice reviews:

littlebookofsitecore_man

“Einstein once said: ‘The definition of genius is taking the complex and making it simple.’ – and this book does just that. It makes the complex simple and it is simply genius!”

“A must read from an author that doesn’t stop in his quest for learning, sharing and giving back.”

amazon-appsstore-us-black-v2Get_it_on_iBooks_Badge_US_1114google-play-badge

Advertisements

Using EnclosingTag, DisableWebEdit and ShowTitleWhenBlank when rendering fields in Glass

If we’re doing views right we’ll be doing views dumb (or replacing the layout service with JSS). In an ideal world our views would be free from helpers and business logic, they should simply render data from a model. Anybody who has been working with Glass for a while will know that the best way to achieve this is to create an editable field with GlassHtml, we simply pass a HtmlString to our view:

var myField = new HtmlString(_glassHtml.Editable(someitem, f => f.SomeField));

However, when rendering fields using the Sitecore API there are properties available from RenderFieldArgs (such as ‘EnclosingTag’) that Glass doesn’t use when rendering a field.

Fortunately, Glass calls the renderField pipeline when creating an editable field so I created a pull request to ensure we can use the following properties:

EnclosingTag – this is available when using @Html.Sitecore().Field as it is present in the render field pipeline as a field arg (renderFieldArgs.EnclosingTag). This can now be used in Glass to wrap our field in the chosen markup if the field value is not empty. Usage: new { EnclosingTag = “h2”}

DisableWebEdit – previously Glass forced renderFieldArgs.DisableWebEdit to false, this is now made optional with a ‘false’ default. There are instances where we may still want to pass in our field values as HtmlString but not allow it to be an editable field (when using EnclosingTag for example). Usage: new { DisableWebEdit = true }

ShowTitleWhenBlank – while it is possible to force this option across the Sitecore instance, we may still want to achieve this on a per field basis. This option will show a field title in the experience editor if the field is empty.  Usage: new { ShowTitleWhenBlank= “true” }

You can see an example implementation of the Glass features below (or using Gist):

This is available from the following pull request so be sure to update your version of Glass and begin (or continue) the good practice of simple view logic 🙂

Resolving workflow permission problems when using Habitat

If like the rest of us you’re a fan of Habitat you may find yourself experimenting with the solution and I wanted to draw attention to potential problems with workflow permissions when modifying the predicates for role serialization. Having disabled the serialization of feature roles:

e.g.
<include domain=”modules” pattern=”^Feature Accounts .*$” />

I found that users cannot edit any data when workflow is enabled. A quick chat with Mr Eldblom revealed the following:

“This is an example of how to cleanly separate security into the modules. Each feature module has a feature role defined which sets rights on the feature fields. Thereby if a feature is added then users do not automatically get access to the fields in the feature.”

So if you remove serialization of the feature role, you lose the ability to edit content. This is because the permissions are assigned on the template fields:

habitat_permissions

In order for your editors to modify content you will need to edit these permissions and grant access to the template fields, in my case I removed the restrictions above. Once you update these permissions, the fields that were previously locked will now be editable again.

Hopefully this saves a few headaches.

Automating item unlocks when deleting Sitecore users

Working with a large team of content editors can present us with difficulties, and one of these problems presents itself when we delete a user. If a user has locked items, deleting the user still leaves their items locked.

Sitecore contains a rule /sitecore/system/Settings/Validation Rules/Item Rules/Security/Locked By Non Existing User and we can use this item rule to give us a visual indicator of which items are locked by none existing users. This can be applied globally by adding it to our Global Rules item found at /sitecore/system/Settings/Validation Rules/Global Rules item.

So we can see which items are locked, and as an individual a content editor can unlock their own files by selecting the ribbon option ‘My Items’ and then using ‘Unlock All Items’ which calls the a method in the Sitecore.Shell.Applications.WebEdit.Commands.UnlockAll class. However, this method executes in the context of the current user rather than our non existing user who has locked the items. So while this method isn’t suitable for what we’re looking for, we can use something similar.

Upon deleting users Sitecore calls the user:deleted event so we need to create a handler for this event. You can see this handler being patched in Helix Base, and the source used by the event but below is the source for the handler:

    public class RemovedUserEventHandler
    {
        public void OnUserDeletedUnlockFiles(object sender, EventArgs args)
        {
            if (EventDisabler.IsActive)
                return;

            var objList = new List<Item>();
            var userName = Event.ExtractParameter<string>(args, 0);

            Assert.IsNotNullOrEmpty(userName, "User name was null or empty");

            var lockedItems = Client.ContentDatabase.SelectItems($"search://*[@__lock='%{userName}%']");

            if (lockedItems == null || !lockedItems.Any())
                return;

            foreach (var lockedItem in lockedItems)
                objList.AddRange(lockedItem.Versions.GetVersions(true).Where(version =>
                    string.Compare(version.Locking.GetOwner(), userName, StringComparison.OrdinalIgnoreCase) == 0));

            ProgressBox.Execute(nameof(RemovedUserEventHandler), "Unlocking items", "Network/16x16/lock.png",
                UnlockAllItems, "lockeditems:refresh", Context.User, objList);

            SheerResponse.Alert($"Successfully unlocked {objList.Count} item(s) checked out by {userName}",
                Array.Empty<string>());
        }

        private static void UnlockAllItems(params object[] parameters)
        {
            Assert.ArgumentNotNull(parameters, nameof(parameters));

            if (!(parameters[0] is List<Item> parameter))
                return;

            var job = Context.Job;

            if (job != null)
                job.Status.Total = parameter.Count;

            foreach (var lockedItem in parameter)
            {
                job?.Status.Messages.Add(Translate.Text("Unlocking {0}", (object)lockedItem.Paths.ContentPath));
                lockedItem.Locking.Unlock();
                if (job != null)
                    ++job.Status.Processed;
            }
        }
    }

Which we then patch in to the event:

  <sitecore>
    <events>
      <event name="user:deleted">
        <handler type="Helixbase.Feature.ItemUnlock.Events.RemovedUserEventHandler, Helixbase.Feature.ItemUnlock" method="OnUserDeletedUnlockFiles" />
      </event>
    </events>
  </sitecore>

Upon deleting users, you should now see their files automatically unlocked.

You can also find this source on GitHub Gist.

Edit… since writing this blog post Michael West has created a useful script for SPE to transfer ownership from one user to another. This can be used in situations where you wish to maintain a record of a previous users locked items.

Using voice commands to add items to Sitecore. IFTTT: Complete walk through

Having purchased a few Google Home devices my mind turned to getting them to work with Sitecore. Wouldn’t it be cool to tell your Google Home or mobile device to interact with the Sitecore environment?! So I setup a POC to do just that, by using a custom route in an API controller we can create Sitecore items by asking Google Home to “Create Sitecore item [ItemName]“:

First visit the IFTTT Platform where we can add our Applet. By selecting ‘New Applet’ we’re first asked to create a Trigger, for this we select ‘Google Assistant’

Google assistant

This will present us with some options:

google trigger

We’ll be using a phrase with a text ingredient, this allows us to say something like “Hey Google, add [ingredient] to my request”. For the POC this ingredient will be an item name which will be used to add an item to Sitecore under /sitecore/content/Home/Google Home

Now we’ll tell Google that our phrase will be “Create Sitecore item [ItemName]:

google assitant sitecore

And we need a reply from the device we’re speaking to:

google assistant sitecore response

Great, so we’ve got an “if this”, now it’s time for a “then that”. Our devices know what we’re going to say and know how to respond, so we need to create our API controller in Sitecore that’s going to create the item for us. The rough example I used is below:

    public class GoogleTestController: SitecoreController
    {
        public ActionResult IFTTT(string itemName)
        {
            using (new Sitecore.SecurityModel.SecurityDisabler())
            {
                var masterDb = Sitecore.Data.Database.GetDatabase("master");

                var template = masterDb.GetItem("/sitecore/templates/System/Templates/Standard template");

                var parentItem = masterDb.GetItem("/sitecore/content/Home/Google Home");

                parentItem.Add(itemName, new Sitecore.Data.Items.TemplateItem(template));

                return Content($"{itemName} added");
            }
        }
    }

And we’ll need our custom route

        public void Process(PipelineArgs args)
        {
            RouteTable.Routes.MapRoute("Feature.GoogleTest", "GoogleTest/{itemName}",
                new { controller = "GoogleTest", action = "IFTTT" });
        }

That’s our Sitecore code setup, now we need to add a webhook so that Google Assistant can talk to our Sitecore API. Under ‘then’ we select ‘Add action’ and select ‘Webhooks’:

ifttt then

This presents us with a dropdown and we can select ‘Make a web request’. These fields should be self explanatory, so the one I’ll focus on is the URL. In the below I have used a sample URL but obviously this needs to be a publicly accessible server with the Sitecore item Web API enabled:

Sitecore google api

Now we’re calling our Sitecore API controller, and sending in the name of the item that the user has asked to create. We could stop there and deploy but let’s select ‘Add action’ again and choose ‘notifications’. We want a phone notification when an item has been added to Sitecore:

ifttt notification sitecore

Give the Applet a title and description and save it. The final step is to enable this Applet on our IFTTT devices, simply select ‘Turn on’:

sitecore ifttt applet.png

Once it’s on we can talk to any of the Google devices on our account. I tested this by saying the following to my Google Home… “Ok Google, create Sitecore item test item” I hear the response “Ok, creating item test item on your Sitecore instance”, you can hear her response on this recording. We can then confirm that our device has added the item to Sitecore:

test item sitecore

We also get a nice phone notification:

sitecore-item-created-ifttt

The possibilities for this one are endless – Sitecore, it’s invading our homes!

Using the OData Item Service in Sitecore 9 – Step by step

Pre Sitecore 9 if we wanted to retrieve Item data we could make use of RESTful services easily with a simple GET HTTP Request. If for example we wanted to get children of an item we could call an endpoint such as:

http://YourInstance/sitecore/api/ssc/item/Some_Guid/children?fields=ItemId,ItemName,TemplateName

In Sitecore 9 we also have the introduction of the  OData Item Service so I thought I’d take us through a step by step.

First go to the following item in the Core database:

/sitecore/system/Settings/Services/API Key

Right click the API Key folder and insert an ‘OData Item API Key’. An explanation of the fields for this item can be found in the documentation but here’s my setup with an explanation:

ODataItemAPIKey

Database – fairly self explanatory, the Sitecore database you’re retrieving an item from.

Search Filter – we can apply search options using an OData filter format, I’ve left this as the default which will return the latest version of items.

CORS Origins – which origins are allowable? Add your required origins separated with a semicolon. As I’m testing this on Helix Base I have added the relevant origin.

AllowedControllers – you may wish to restrict this API Key to certain controllers and can list those here separated with a semicolon. I’m allowing all so have opted for the wildcard *

Impersonation User – you could leave this blank in which case Sitecore will use the default from your Sitecore.Services.AnonymousUser setting. The default is most likely sitecore\ServicesAPI but I’ve added this to the field anyway.

Once we’ve saved the item it’s time to copy the item Id which we can then use in our request headers, or on the endpoint. For example if my item Id is 1234 I could use a Key of sc_apikey  and value 1234 in my headers, or apply it to the endpoint as follows:

http://demo.helixbase/sitecore/api/ssc/aggregate/content/Items(‘/sitecore/content/Home’)?sc_apikey=1234

Using either approach we can then see the fruits of our labour…

SitecorePostman

To see a list of endpoints for the OData Item Service see the Sitecore documentation

The Little Book of Sitecore® Tips volume 1 now available

sitecore_netI’m pleased to announce the launch of volume 1 of The Little Book of Sitecore® Tips

The book was created after converting a rather large amount of notes (that were created during my day to day workings with Sitecore) into an initial book, and what will soon become an ongoing series of books.

The main purpose of The Little Book of Sitecore Tips is a light-hearted look at some useful Sitecore tips. These tips are targeted at all levels of user but whethersitecore_book you’re recapping or learning, I hope you find the material either useful, or at least of some entertainment value.

ALL royalties are donated to worthy causes, so if you take nothing from the book at least you’ll know that your purchase wasn’t in vain. So have some fun, support some charity and order yourself a paperback copy from the likes of Amazon or Barnes & Noble – The book is also available as an eBook on the iBooks Store, and Google Play.

Thanks is due to the technical reviewers Adam Conn and Radosław Kozłowski – and a big thanks to Tamas Varga who also supplied a great deal of help, support, and the following review:

sitecore_rocket

“This is the book I should have written 5 years ago! Having a book with tips is something we haven’t got so far in the Sitecore community. It goes beyond just Sitecore tips, but some of the best practices the community created and collected during the last couple of years. It not only helps your daily work with Sitecore, but also demonstrates the power and flexibility of it. All the tips in this book is really helpful and the nice illustrations helps you remember them.”

amazon-appsstore-us-black-v2Get_it_on_iBooks_Badge_US_1114google-play-badge