Sitecore PXM 8.0: Add Vulgar Fraction Support

Background

Sitecore Print Experience Manager (PXM) is great; you can create dazzling printable documents that reuse your existing Sitecore content. You can enable your designers to add a personal touch to each document, or configure automation to allow document creation on the fly.

One of the primary components of PXM is its InDesign Connector plugin (IDC). It facilitates communication between Sitecore PXM and InDesign, allowing a designer to:

  • add Sitecore content to an existing InDesign document
  • create and update PXM Projects, which are a Sitecore representation of an InDesign document
  • manipulate master documents, which are essentially InDesign templates
  • save content changes directly into Sitecore items

“Vulgar” Fractions

Vulgar fractions are simply fractions that are “non-standard.” Examples of standard fractions are below. These are the ones Sitecore PXM supports out-of-the-box:

  • ¼ – ¼
  • ½ – ½
  • ¾ – ¾

There are a whole host of others, however, that can be added. Some include:

  • ⅛ – ⅛
  • ⅗ – ⅗
  • ⅚ – ⅚

How To Add Support for the Vulgar Fractions to PXM

  1. Open Website\Print Studio\Data\special.xml – this is the file that is used to map an HTML code (e.g. ⅛) to the corresponding UTF-8 character value (e.g. 8539).
  2. If you want to keep your file (roughly) in order by index, scroll down to the entry with index=”8501″.
  3. Insert the following elements:
     <codeposition index="8528" entity="frac17"/>
     <codeposition index="8529" entity="frac19"/>
     <codeposition index="8530" entity="frac110"/>
     <codeposition index="8531" entity="frac13"/>
     <codeposition index="8532" entity="frac23"/>
     <codeposition index="8533" entity="frac15"/>
     <codeposition index="8534" entity="frac25"/>
     <codeposition index="8535" entity="frac35"/>
     <codeposition index="8536" entity="frac45"/>
     <codeposition index="8537" entity="frac16"/>
     <codeposition index="8538" entity="frac56"/>
     <codeposition index="8539" entity="frac18"/>
     <codeposition index="8540" entity="frac38"/>
     <codeposition index="8541" entity="frac58"/>
     <codeposition index="8542" entity="frac78"/>
    
  4. Recycle the Sitecore PXM AppPool.

Bonus

If you're curious, this configuration is used by the Sitecore.PrintStudio.PublishingEngine.Text.Parsers.Html.HtmlEntityHelper to map between the HTML code  (e.g. &frac18;) to the corresponding UTF-8 character value (e.g. 8539).

Advertisements

Sitecore Query XPath for Finding all Fields with Field Fallback Disabled

Summary

The query below returns all fields on your templates that have the field Enable field level fallback disabled. This can be useful if you are creating a multi-lingual site and want to turn field fallback on for the fields on your data templates, but aren’t sure for which it’s already been done.

/sitecore/templates/User Defined//*[@@templatename='Template field' and @Enable Shared Language Fallback!='1']

How Does it Work?

Let’s break down the components of the query:

/sitecore/templates/User Defined//*[@@templatename='Template field' and @Enable Shared Language Fallback!='1']

Start at the root of the user-defined templates. This should be adjusted if you’re creating your templates elsewhere. (In a Habitat module, for example.)

/sitecore/templates/User Defined//*[@@templatename='Template field' and @Enable Shared Language Fallback!='1']

Look at all descendants (recursively check all children).

/sitecore/templates/User Defined//*[@@templatename='Template field' and @Enable Shared Language Fallback!='1']

Only return items of template Template field whose Enable Shared Language Fallback checkbox is not checked (this can include an empty value or 0).

** Note that the Item Name of the Enable Shared Language Fallback field is used here; the name of the field you see when editing a field in the content editor will be its title: Enable field level fallback.

Issues or suggestions? Let me know with a comment.

Sitecore Query XPath for Finding all Locked Items

Summary

Do you need to find all items in Sitecore that have been locked by any user? Below is an XPath query that will show you just that. Keep in mind that depending on the number of items in your installation, this query could take quite a while. I would not recommend running it in your code in a production environment, but rather using the XPath Builder to find these items and resolving them manually.

/sitecore//*[@__lock!='' and @__lock!='<r />']

How Does it Work?

Let’s break down the components of the query:

/sitecore//*[@__lock!='' and @__lock!='<r />']

Start at the root of the content tree: the sitecore item.

/sitecore//*[@__lock!='' and @__lock!='<r />']

Look at all descendants (recursively check all children).

/sitecore//*[@__lock!='' and @__lock!='<r />']

Only return items whose __lock field is not empty and does not contain <r />, signifying that a lock has been cleared.

Issues or suggestions? Sound off in the comments.

Sitecore PXM 8.0: Add a custom task to PXM and InDesign

Background

Sitecore Print Experience Manager (PXM) is great; you can create dazzling printable documents that reuse your existing Sitecore content. You can enable your designers to add a personal touch to each document, or configure automation to allow document creation on the fly.

One of the primary components of PXM is its InDesign Connector plugin (IDC). It facilitates communication between Sitecore PXM and InDesign, allowing a designer to:

  • add Sitecore content to an existing InDesign document
  • create and update PXM Projects, which are a Sitecore representation of an InDesign document
  • manipulate master documents, which are essentially InDesign templates
  • save content changes directly into Sitecore items

One of its lesser-known features, however, is the ability to execute a custom Task in PXM.

PXM Tasks

A PXM Task is simply a method that can be executed on the Sitecore PXM server from InDesign. It accepts a Dictionary with some parameters and allows you to return a string that will be output in a dialog box in InDesign.

Task Dictionary Contents

The dictionary will contain the following parameters

  1. ItemID (GUID of the Task item in Sitecore)
  2. LanguageIndex (int)
  3. CurrentUserName (logged in user)
  4. ci_projectPanel (GUID of the item selected in the Project panel)
  5. ci_contentBrowser (GUID of the item selected in the Content panel)
  6. ci_libraryBrowser (GUID of the item selected in the Library panel)
  7. ci_imageViewer (I’m not sure what this does; let me know in the comments!)
  8. ci_workBox (GUID of the item selected in the Workbox panel)

How To Create and Execute a Custom PXM Task

Create the Task Class

  1. Create a public class (does not need to inherit any base class or implement an interface)
  2. Add a method that accepts a Dictionary<string, object> and returns a string
  3. Do something in your task
  4. Return whatever text you would like to display in a dialog box in InDesign when the task completes

A simple class example

public class CustomTask
{
  public string ExecuteTask(Dictionary<string, object> dictionary)
  {
    return string.Format("Hello World! You selected {0} in your Project panel.", dictionary["ci_projectPanel"]);
  }
}

Create the Task Item in Sitecore

Add the Task item below the /sitecore/Print Studio/Libraries/Tasks library folder and fill in the fields

2016-03-11_1631

  • Assembly should be the DLL that contains your class (including the file extension, e.g. TestPxm.dll)
  • TypeName should be the fully-qualified type name of your class (e.g. TestPxm.Tasks.CustomTask
  • MethodName should be the name of the method you created (e.g. ExecuteTask)

Add the Tasks Library to Your Extensions Browser Library Nodes

  1. Navigate to /sitecore/Print Studio/Modules/InDesign connector/Other Settings/Extensions browser/Libraries
  2. Add /sitecore/Print Studio/Libraries/Tasks library in one of the empty nodes

2016-03-11_1633

Execute the Task

  1. Open InDesign and log into Sitecore
  2. Open the Extensions panel and select your task
  3. Click the Load button (the icon with the document and a little arrow on it)

2016-03-11_1636

Add Support for Content Workflow with Sitecore PXM

Background

Sitecore Print Experience Manager (PXM) is great; you can create dazzling printable documents that reuse your existing Sitecore content. You can enable your designers to add a personal touch to each document, or configure automation to allow document creation on the fly.

One of the primary components of PXM is its InDesign Connector plugin (IDC). It facilitates communication between Sitecore PXM and InDesign, allowing a designer to:

  • add Sitecore content to an existing InDesign document
  • create and update PXM Projects, which are a Sitecore representation of an InDesign document
  • manipulate master documents, which are essentially InDesign templates
  • save content changes directly into Sitecore items

That last feature is what we’ll focus on in this post.

The Problem

When saving Sitecore content changes from InDesign, Sitecore PXM does not consider the workflow state of the content item. This causes an issue if the item being edited is in a final workflow state. Out of the box, PXM will make the change directly to the item without creating a new version in draft state. Many governance policies forbid that kind of change.

Solution 1: Disable Saving Content from InDesign

If you want to avoid further customization and also prevent content edits from happening on items in a final workflow state, you could just disable content editing from InDesign altogether. It removes the option from InDesign and does not affect other features of the InDesign Connector.

Simply uncheck the box at

/sitecore/Print Studio/Modules/InDesign connector/Other Settings/Default Settings/Default Settings/Allow content updates from InDesign

Solution 2: Add Workflow Support to Save from InDesign Connector

If you don’t mind a little customization, however, you can support workflow on content items when they’re saved from the InDesign Connector plugin.

Step 1: Create an item:saving event handler class


public class SaveProcessor
{
    private Template _StandardTemplate;
    private const string PRINT_STUDIO_SITE_NAME = "printstudio";
    private static readonly List<Guid> _ActiveItemIds = new List<Guid>();

    public void OnItemSaving(object sender, EventArgs args)
    {
        // Only intercept updates from PXM
        if (Context.Site.Name.Equals(PRINT_STUDIO_SITE_NAME))
        {
            var sitecoreEventArgs = args as SitecoreEventArgs;
            var updatedItem = sitecoreEventArgs?.Parameters[0] as Item;

            if (updatedItem != null)
            {
                // If we're already working with this item, allow this save to continue normally (prevents infinite recursion)
                if (!_ActiveItemIds.Contains(updatedItem.ID.Guid))
                {
                    var originalItem = Context.Database.GetItem(updatedItem.ID);
                    if (originalItem != null)
                    {
                        var workflow = Context.Database.WorkflowProvider.GetWorkflow(originalItem);
                        var workflowState = workflow?.GetState(originalItem);

                        // If the current item is not in workflow, or it is in workflow but the current state is not final, allow the save to continue normally
                        if (workflowState != null && workflowState.FinalState)
                        {
                            var differences = new Dictionary<string, string>();
                            foreach (Field field in updatedItem.Fields)
                            {
                                var updatedItemField = updatedItem.Fields[field.ID];
                                var originalItemField = originalItem.Fields[field.ID];

                                // Find all the differences that are not standard fields
                                if (updatedItemField != null &&
                                    !IsStandardField(updatedItemField) &&
                                    originalItemField != null &&
                                    !updatedItemField.Value.Equals(originalItemField.Value))
                                {
                                    differences.Add(field.Name, updatedItemField.Value);
                                }
                            }

                            // If there are no differences, allow the save to continue normally
                            if (differences.Count > 0)
                            {
                                // Add this item ID to the list of currently-processing item IDs
                                _ActiveItemIds.Add(updatedItem.ID.Guid);

                                try
                                {
                                    originalItem.Editing.BeginEdit();
                                    var newVersion = originalItem.Versions.AddVersion();
                                    newVersion.Editing.BeginEdit();
                                    foreach (var difference in differences)
                                    {
                                        newVersion[difference.Key] = difference.Value;
                                    }
                                    newVersion.Editing.EndEdit();
                                    originalItem.Editing.EndEdit();
                                }
                                finally
                                {
                                    // Remove this item ID from the list of currently-processing item IDs
                                    _ActiveItemIds.Remove(updatedItem.ID.Guid);
                                }

                                sitecoreEventArgs.Result.Cancel = true;
                            }
                        }
                    }
                }
            }
        }
    }

    public bool IsStandardField(Field field)
    {
        if (_StandardTemplate == null)
            _StandardTemplate = TemplateManager.GetTemplate(Sitecore.Configuration.Settings.DefaultBaseTemplate, field.Database);

        return _StandardTemplate.ContainsField(field.ID);
    }
}

Step 2: Patch your web.config to add the event handler

Make sure you update the type to reference your event handler. This patch configuration file should go in the App_Config\Include folder.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
    <events>
        <event name="item:saving">
            <handler type="TestPxm.Pxm.SaveProcessor,TestPxm" method="OnItemSaving" patch:before="handler[@type='Sitecore.Tasks.ItemEventHandler, Sitecore.Kernel']"/>
        </event>
    </events>
</sitecore>
</configuration>

Explanation

What is the code doing? Basically, after checking if the content item is in a final workflow state, we add a version to it and make our changes to the new version. We then cancel the original save event to prevent the changes from being made against the version in final workflow state. If it’s any other case, we simply allow the save to happen as normal.

Acknowledgements

I wanted to thank my team at Horizontal Integration for their brainstorming, and Mark Demeny of Sitecore for his responsiveness and helpfulness proposing ideas for possible solutions.