Executing a mass check-in of locked items in Sitecore

Recently I was tasked with finding a way to check-in all locked items in a client’s Sitecore environment. Since Sitecore allows you to lock items for editing to prevent concurrent modifications, and some users never removed their lock, we had a frequent problem with items that were locked by users that did not even work there anymore.

The method of accomplishing this was pretty clear from the get-go. It was time for another Sitecore PowerShell Extensions script!

Creating our Script

I knew PowerShell was the answer, but I wasn’t too well versed with it yet. Sitecore PowerShell Extensions (SPE) is pretty powerful though, since you can access the Sitecore item API through it just as you would through code, so I knew it wouldn’t be that hard once I got through the necessary hiccups.

The logic:

  • Start in the context of the master database.
  • Loop through every item in the content tree, recursively.
  • Then, loop through every version of the current item, since it’s possible that only some of the versions of an item will be locked.
  • Check if the item is locked. If so, open it for editing and unlock it. Then close it for editing.

The script:

<# .SYNOPSIS Check in all locked items in master. .BY Paul Aldrich .DATE 5/3/2017 .UPDATE LOG #>
Write-Host "Starting work in the context of the 'master' database."
Set-Location -Path master:\content
$itemsProcessed = 0;
$itemsUnlocked = 0;
foreach ($item in Get-ChildItem . -Recurse)
{
   foreach ($version in $item.Versions.GetVersions())
   {
      $itemsProcessed = $itemsProcessed + 1;

      <# For any item in the content node, check if locked. If so, check it in. #>
      if ($version.Locking.IsLocked())
      {
         Write-Host "Unlocking item"
         $version.Editing.BeginEdit();
         $version.Locking.Unlock();
         $version.Editing.EndEdit();
         $itemsUnlocked = $itemsUnlocked + 1;
      }
   }
}
Write-Host "The script has completed."
Write-Host ("{0} items have been processed." -f $itemsProcessed)
Write-Host ("{0} items were locked and are now checked in." -f $itemsUnlocked)

The script should be pretty straightforward, but let me know if you have any questions in the comments.

Completion

This was a really simple, yet powerful script to make, and the best part is its reusability. Don’t forget, you can check your work by running the Locked Items script that comes with SPE and search for locked items by user.

Advertisements

Creating a dynamic image in Rich Text fields

Have you ever inserted an image, then changed its alt or title tags in the Media Library and noticed that the change does not propagate into the rich text field? It’s because Sitecore only inserts static values into the field HTML when you insert an image. If you’ve ever wanted or needed those images to act dynamically – so that changing alt or title text affects the field HTML -then this article might help you.

The Secret? Tokens!

We can modify the rich text editor dialogs so that when a user inserts Sitecore media, the ALT and TITLE text being sent is actually just a token – i.e. {{alt}}. There is no specific syntax required, just use something that you like. That means that we’re actually inserting “{{alt}}” and “{{title}}” into the field HTML, not the actual (static) values. The goal then is to create a pipeline processor that will read the field and look for those tokens, and replace them with the current values of the Alt and Title fields of the media item.

Customizing the InsertImage dialog

The first thing we will want to do is modify the InsertImage dialog. The InsertImage dialog is a Sitecore dialog, and the source can be found with any Sitecore installation. You would need two files:

  1. The InsertImage.xml file for the dialog, which can be found in the webroot of your site under sitecore\shell\Controls\Rich Text Editor/InsertImage.
  2. The InsertImageForm.cs file (code behind file) which can be extracted from the Sitecore.Client.dll. You can use .NET Reflector, DotPeek, or JustDecompile (I personally use the last one). Look for a Sitecore.Shell.Controls.RichTextEditor.InsertImage entry.

You will want to take these two files and preferably put them in your solution. Place these in a new folder, e.g. Controls/RichTextEditor/InsertSitecoreMediaExtended and make the following changes:

  • Keep the .xml file name the same. In the .xml file, there is a line which points to the code behind file – update that with the location of the file in the solution and the assembly name.
  • Update the name of the .cs file to match the parent folder (InsertSitecoreMediaExtended.cs). In that file, remove everything except the OnOK and SetDimensions methods. You will need to modify the OnOK method so that alt and title are set using tokens:
image.Add("Alt", StringUtil.EscapeQuote("{{alt}}"));
image.Add("Title", StringUtil.EscapeQuote("{{title}}"));

Optional: You can edit the Telerik.Web.UI.Editor.DialogControls.SetImageProperties dialog by adding a title field and checkboxes next to the alt and title fields to make it easier for content editors to specify this option.

Let’s make a new pipeline!

You will want to extend the RenderField pipeline in Sitecore.Kernel, specifically the GetFieldValue processor. So when Sitecore gets the value of the RTE field, we’re going to modify the HTML by swapping tokens for media item values. Create a new folder in your pipelines folder or project named GetFieldValueExtended, and a new class file under it with the same name.

Now you’ll need to tell Sitecore to run your custom processor. There are a few options – patch:before, patch:after or overwrite completely. I personally used the patch:after option. If you choose to overwrite, make sure your processor implements all of the same code as the original. Create a new patch file in the App_Config/Include folder similar to the below. (In my case, the solution has a Processor project.)

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <pipelines>
            <renderField>
                <processor
                    type="MyProject.Processor.RenderField.GetFieldValueExtended, MyProject.Processor"
                    patch:after="processor[@type='Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel']" />
            </renderField>
        </pipelines>
    </sitecore>
</configuration>

In your custom processor class file, set up the Process event with some Asserts and other logic needed before doing the swap. Create the tokens as constants for flexibility. Here is the full code used, which is currently running in a production environment with no issues. I highly recommend HtmlAgilityPack for spelunking in the HTML. (NOTE: The code assumes your production environment Sitecore database is web, which may not be true in your case.)

    public class GetFieldValueExtended
    {
        private const string AltToken = "{{alt}}";
        private const string TitleToken = "{{title}}";

        public void Process(RenderFieldArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Item, "args.Item");
            Assert.ArgumentNotNull(args.Item.Database, "args.Item.Database");
            Assert.ArgumentNotNull(args.GetField(), "args.GetField()");

            if (args.Item.Database.Name.Equals("web"))
            {
                if (args.GetField().Value.Contains(AltToken) || args.GetField().Value.Contains(TitleToken))
                {
                    args.Result.FirstPart =  ReplaceTokens(args.GetField().Value);
                }
            }
        }

        private string ReplaceTokens(string fieldValue)
        {
            if (fieldValue.Contains("img") == false) return fieldValue;

            var document = new HtmlAgilityPack.HtmlDocument();
            document.LoadHtml(fieldValue);

            foreach (var img in document.DocumentNode.Descendants("img"))
            {
                var imgSrc = img.Attributes["src"];
                if (imgSrc == null) continue;

                // Remove query string from src (if it exists)
                var mediaUrl = CleanImageSource(imgSrc.Value);

                // Get media item ID based on media url
                DynamicLink dynamicLink;
                if (!DynamicLink.TryParse(mediaUrl, out dynamicLink)) continue;
                MediaItem mediaItem = Sitecore.Context.Database.GetItem(dynamicLink.ItemId,
                    dynamicLink.Language ?? Sitecore.Context.Language);

                // Replace the tokens
                if (img.Attributes["alt"] != null && img.Attributes["alt"].Value.Trim() == AltToken)
                {
                    img.Attributes["alt"].Value = mediaItem.Alt;
                }

                if (img.Attributes["title"] != null && img.Attributes["title"].Value.Trim() == TitleToken)
                {
                    img.Attributes["title"].Value = mediaItem.Title;
                }
            }

            return document.DocumentNode.OuterHtml;
        }

        private string CleanImageSource(string imgSrcValue)
        {
            // First match the SHA1
            var shaMatcher = new Regex("\b[0-9a-f]{5,40}\b");
            var match = shaMatcher.Match(imgSrcValue);

            if (match.Success)
            {
                // Setup media url
                return string.Format("/~/media/{0}.ashx", match.Value);
            }

            return imgSrcValue;
        }
    }

 

Completion

Overall, this was a great feature that the client needed as they tried to make their website ADA compliant, by including the appropriate alt and title text values. I hope this helps you in your Sitecore development adventures.

Data at the root level is invalid. Line 1, position 1.

I recently ran into this error in Sitecore 8.2 when trying to change a link in a field in Sitecore. The field appeared blank,  so I clicked “Insert Link” and a Sitecore modal popped up displaying this error: “Data at the root level is invalid. Line 1, position 1.”

Sometimes when an invalid value is entered into a link field in Sitecore, the field will still appear blank to the casual observer. This of course, does not mean that the field contains no value at all. It just means that the field does not contain a valid link.

To resolve this issue, in the Content Editor click the View tab, then check Raw Values. The editor will refresh, and you should see something in that field of yours. It might be an ID or some form of garbage data. Clear that out completely, save the item, then uncheck Raw values. You should now be able to insert a link into the field. Hope this helps!

 

 

Resolving ClearScript V8 assembly issues

I recently worked with a colleague on a ASP.NET MVC / Sitecore project that had some references to ClearScript, which is a 3rd party assembly that lets you add and run scripts from C#/.NET. We noticed issues with trying to publish files from the solution to the webroot, which always seemed to break the site. The most common issue was with ClearScript. These issues resurfaced as we refactored the solution and made subsequent QA deployments to the client’s servers.

The first thing we did to fix this was reinstall ClearScript and JSEngineSwitcher through Nuget. We then kept making code pushes to the webroot until we encountered a new error, which we usually had to install through Nuget as well, or troubleshoot/fix in other ways. Many of the assemblies were way out of date, since the solution had not been updated in a long time. Also, because the webroot was part of the solution, we found that some Nuget packages were installed into the webroot project, not the Web project that we were building out of, which also caused issues. To update this, we right clicked the solution and selected Manage Nuget packages for this solution, then checked each package and clicked Manage, and checked the box for the Web project.

We eventually got the code pushes to the webroot to work. The next problem was moving the code changes to QA. The main issue we ran into was an error stating Could not load file or assembly 'ClearScriptV8-(32 or 64).DLL' or one of its dependencies. The specified module could not be found. Some solutions for this issue:

  • Right click the IIS application pool and select Advanced Settings. Change the value for Enable 32-bit Applications to true.
    • Some people have had success with the opposite (setting it to false). I am still not sure what effect this value has.
  • The error can mean that the ClearScript DLLs reference another DLL that is not present on the current machine. This makes sense considering I did not get this specific error on my machine with the same bin folder and site files. I looked into this and it seems like different versions of ClearScript require different versions of Visual C++ Redistributable:
    • For ClearScript versions 5.0-5.3, install Visual C++ Redistributable 2012.
    • For ClearScript versions 5.4 -?, install Visual C++ Redistributable 2013
    • I installed both 32-bit and 64-bit versions of both, although I’m not sure if that was required.
  • In my case, neither of the previous solutions worked, and it turned out that instead of copying over the bin to the QA server files and merging, I had to delete the bin folder and copy over new from my local webroot, which ended up fixing the issue.

I hope this helps others out there, this issue was quite an annoyance!

 

 

TDS: The package builder failed. Please see build output log for more details

In our Sitecore team, we use Team Development for Sitecore (TDS) by Hedgehog Development to sync out Sitecore items to/from a TDS project/Sitecore dev site. It’s a pretty useful addon, but it has some weaknesses. One problem encountered with using it is a package build failure. When we build our QA or Authoring packages, sometimes TDS will fail to build the package with the error:

“The package builder failed. Please see build output log for more details.”

Once I had gotten this error repeatedly, I contacted the TDS support team. At first they just suggested to clean and rebuild the solution, which worked occasionally, but was definitely not a real fix for the problem. So I followed up and they gave some helpful advice.

First, TDS packages can be huge, especially if you opt to include both code and Sitecore items in the packages. This can be done by going to TDS.Master properties -> Update Package tab -> Package Generation Options and select "Generate separate code and item packages". We never deploy Sitecore items via packages (we use Razl, another Hedgehog product) so we opted out of that early on to decrease the package size. That reduced our sizes to around 150-165K on average.

Next, TDS suggested that we disable concurrent project builds. We were able to do so by navigating to Tools -> Options -> Projects and Solutions -> Build and Run in VS and changing the "maximum number of parallel project builds" to 1. Make sure to save the solution and restart VS before attempting to build a package again.

Finally, we decided to lower the package size in other ways. We opened our Configuration Manager and unchecked Build on some projects that did not need to be built every time. For example, I believe there 3 or 4 projects that we did not need to build for QA packages, so we unchecked those, closed the window and committed the solution change.

Since we’ve made these changes and merged them to the branches and trunks that need them, we haven’t seen this error crop up much at all. I hope this helps other TDS users.