Searching and Modifying Output In Tridion Template Building Blocks With HtmlAgilityPack

In your Tridion career, you’ve probably written countless of C# Template Building Blocks. You’ve probably rolled your own custom Link Resolver, your own “Add or Extract Binaries From…”, your own cleanup templates… dozens of Building Blocks where the goal was to search for specific html patterns and attributes or modify the output in some way. And, if you’re like me, you’ve probably had to write numerous regex expressions to accomplish your tasks. I’ll admit that I’m no regex ninja or anything, its usually through trial and error that I get my regular expressions working correctly. Recently however, I attempted to write an expression that handled nested elements that could go any number of levels deep, with possibility to have several different variations of the pattern that I was looking for. My regex skills was just not good enough, and I thought for sure there must be a better way to parse the html output string of these patterns.

The search was short, but I found exactly what I was looking for: Html Agility Pack

Using HtmlAgilityPack in your Tridion Template Building Block is simple… just reference the DLL, and make sure you install the DLL into the GAC on the CMS and Publishing servers.

Now that we are set up and ready to run, let’s go ahead and write some code that will take the output from the package, and load it into an HtmlDocument.  Your code should look something like the following:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Tridion.ContentManager.Templating;
using Tridion.ContentManager.Templating.Assembly;
using HtmlAgilityPack;

namespace CodedWeapon.Samples
{
    [TcmTemplateTitle("HtmlAgilityPack Tester")]
    public class HtmlAgilityPackTester : ITemplate
    {
        private TemplatingLogger _logger = null;

	protected TemplatingLogger Logger
	{
		get
		{
			if (_logger == null) 
                            _logger = TemplatingLogger.GetLogger(this.GetType());

			return _logger;
		}
	}

        public override void Transform(Engine engine, Package package)
        {
            Item outputItem = package.GetByName(Package.OutputName);
            string outputString = outputItem.GetAsString();

            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(outputString);
        }
    }
}

If you’re familiar with XmlDocument, then HtmlDocument should not look so strange to you. All we are doing in the above is grabbing the output, and passing the output (as a string) into our HtmlDocument instance. Simple enough right? And now for the fun… seeing how simple it is to use this library to get the exact elements that we are looking for. Since a lot of the time we work with links, I’ll show some examples of grabbing some anchor tags.

Grabbing Every Anchor

Lets say we want to grab every anchor element present on the output:

HtmlNodeCollection nodes = doc.DocumentNode.Descendants("a");

You can loop over each of the nodes and perform any necessary operations that you want:

foreach (var node in nodes)
{
    Logger.Debug("Found node with url: " + anchor.Attributes["href"].Value); 
}

Just like with XmlDocument, you can also use XPath:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a");

Querying for Specific Elements

You can use XPath to search for the specific elements that you are looking for. For example, if we wanted to match all links with a specific url:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[@href=\"http://www.example.com\"]");

Or what if we wanted to grab every link that actually contains a title attribute:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[@title]");

Or better yet… what if we want check any node that does not contain the title attribute at all?

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[not(@title)]")

If you’re not a fan of XPath, then you’ll be happy to know that you can also use LINQ like in this following example where we are looking for any link that has an href attribute of “#”:

var nodes = doc.DocumentNode.Descendants()
    .Where(n => n.Attributes["href"] != null && n.Attributes["href"].Value.Equals("#"));

Modifying Output

Normally when we’re searching for elements it’s because we may need to modify the markup in someway, either by adding attributes, removing elements, etc. In the following example, we are adding a custom attribute onto our anchors that contain a value of “#” in the href attribute:

foreach (var node in doc.DocumentNode.SelectNodes("//a[@href=\"#\"]"))
{
    node.Attributes.Add("data-my-custom-attribute", "true");
}

But what if we have a more complicated requirement where we need to strip a <tcdl:ComponentPresentation> tag (but ensure that we keep the inside markup). One may be tempted to try the following:

foreach (var node in doc.DocumentNode.Descendants("tcdl:ComponentPresentation"))
{
    node.ParentNode.RemoveChild(node, true); // this strips the tcdl tag while preserving the children... but its modifying the collection...
}

package.Remove(outputItem);
outputItem.SetAsString(doc.DocumentNode.OuterHtml);
package.PushItem(Package.OutputName, outputItem);

However, while running the above, you’ll get an error that says Collection was modified; enumeration operation may not execute. This is because you cannot modify the collection (add/remove) while enumerating it. But, we can keep a record of the nodes and operate on them after:

List nodesToChange = new List();
foreach (var node in doc.DocumentNode.Descendants("tcdl:ComponentPresentation"))
{
    nodesToChange.Add(node);
}

// Now we strip the wrapping tags...
foreach (var node in nodesToChange)
{
    node.ParentNode.RemoveChild(node, true);
}

package.Remove(outputItem);
outputItem.SetAsString(doc.DocumentNode.OuterHtml);
package.PushItem(Package.OutputName, outputItem);

Hopefully this will end the regex blues out there for anyone who may be struggling!

Reloading/Refreshing Lists in the Shortcuts Area

A recent question on stack exchange regarding the ability to refresh or reload the favorites section using the Anguilla API has sparked this post, where I thought I would expand a little bit on the answer.  What the asker was doing was modifying the favorites (in this case the names), but needed the favorites section to be reloaded in order to reflect these changes. The shortcuts area is within an iFrame, so my first thought was to use some regular JavaScript to reload the iFrame itself via:

$('#FavoritesTree iframe').contentWindow.location.reload(true);

But as the asker of the question realized, this actually causes the entire Tridion GUI to be loaded into the shortcuts area!  Definitely not what we want to do!

Luckily for us, Anguilla has a way for us to unload the lists which will automatically cause them to reload. If we want to refresh all of the lists inside of the shortcuts area, we can do the folllowing:

$models.getItem("cme:userfavs").unloadLists();

So what’s going on in the above statement? $models.getItem("cme:userfavs") retrieves the shortcuts area which is represented by the type Tridion.Cme.Model.FavoritesFolder. This object contains the method unloadLists() which does the magic for us. But suppose you only want to refresh a specific list in the shortcuts area?

$models.getItem("cme:shortcuts").unloadLists();
$models.getItem("cme:custompgs").unloadLists();
$models.getItem("cme:usrtsks").unloadLists();

Again, with each of these items you can just call the unloadLists() method to refresh that specific list. You can even target the child lists of these lists… for example, $models.getItem("cme:chklst"); which would get the Checked-out Items from the My Tasks. If you’re trying to figure out how you can get the identifier to pass to $models.getItem(), a good place to look is the address bar after clicking on the list you wish to target with your code. For example, after clicking on the “Checked-out Items”, you should see “Dashboard.aspx#locationId=cme:chklst” at the end of the address bar. The value of the locationId is what you want to use.

Happy coding everyone!

Checking If An Item Is In Workflow With Core Service

During your quest of making custom applications using Tridion’s Core Service API, you may have come across the need to check whether or not an item is currently in a Workflow Process. Luckily, unlike some other tasks you may need to do with the Core Service API, this task is easily done by checking the WorkflowInfo property that is included in the PageData and ComponentData classes.

// Open a page in Core Service.
PageData page = client.Read(yourPageUri, new ReadOptions()) as PageData;

if (page.WorkflowInfo != null)
{
    // Ladies and gentlemen, WorkflowInfo property was not null, this page is in workflow
}
else
{
    // WorkflowInfo property is null, the page is not in workflow
}

Great, but now that you know that your item is in workflow, you’ll probably be wanting to grab some Workflow related items. The good news is, the WorkflowInfo class contains most of the data you’ll need to interact with the Workflow for that item. Here are some common things you’ll probably do with this property:

// Get the ProcessInstanceData from the WorkflowInfo property
ProcessInstanceData processInstance = 
    (ProcessInstanceData)client.Read(page.WorkflowInfo.ProcessInstance.IdRef, null);

// Get the ProcessDefinitionData from the process instnace
ProcessDefinitionData processDefinition =
    (ProcessDefinitionData)client.Read(processInstance.ProcessDefinition.IdRef, null);

// Getting the ActivityInstanceData from the WorkflowInfo property...
ActivityInstanceData activityInstance = 
    (ActivityInstanceData)client.Read(page.WorkflowInfo.ActivityInstance.IdRef, null);

// Get the ActivityDefinitionData from the activity instance
ActivityDefinitionData activityDefinition =
    (ActivityDefinitionData)client.Read(activityInstance.ActivityDefinition.IdRef, null);

// Note that if you only wanted to get the ActivityDefinition's Description, you can get that from the WorkflowInfo's ActivityDefinitionDescription property.
if (page.WorkflowInfo.ActivityDefinitionDescription.Equals(activityDefinition.Description))
{
    // true of course...
}

// Check how long its been since the activity was started...
if (page.WorkflowInfo.StartDate.HasValue)
{
    TimeSpan timeSinceStart = DateTime.Now - page.WorkflowInfo.StartDate.Value;
}

Other Useful Properties of WorkflowInfo

Here are the other properties of WorkflowInfo that wasn’t included in the samples above:

ActivityState: The enum value of the activity’s current state.
Assignee: The Link<TrusteeData> of the assignee.
CreationDate: The Nullable<DateTime> that the activity was created.
FinishDate: The Nullable<DateTime> that the previsou activity instance was finished.
Performer: The Link<UserData> of the activity’s performer.
PreviousMessage: The FinishMessage of the previous Activity.

Getting Using and Used Items With TOM.NET

Periodically I will receive an e-mail from someone in the Tridion community asking questions like “How do I get the pages where a component is used?” or “How do I get all the components using a particular schema?”. Although there is already some good examples out there on retrieving Where Using and Where Used, I figured some more Tridion samples out there could never hurt.

GetUsingItems

The Tridion.ContentManager.IdentifiableObject (the base class that most of the other items in Tridion inherits) has a method called “GetUsingItems” that allows you to get all items using this item. Note that these are the items you see in the “Used in” tab when you use the “Where Used” functionality in the GUI. The UsingItemsFilter argument allows you to refine your query of using items even further.


IdentifiableObject.GetUsingItems()
IdentifiableObject.GetUsingItems(UsingItemsFilter filter)

Getting all pages used by a component:

UsingItemsFilter filter = new UsingItemsFilter(component.Session);
filter.ItemTypes = new List<ItemType> { ItemType.Page };
filter.IncludedVersions = VersionCondition.OnlyLatestVersions;

IEnumerable<IdentifiableObject> pages = component.GetUsingItems(filter);

Getting all components used by a schema:

UsingItemsFilter filter = new UsingItemsFilter(schema.Session);
filter.ItemTypes = new List<ItemType> { ItemType.Component };
filter.IncludedVersions = VersionCondition.OnlyLatestVersions;
filter.InRepository = (Repository)publication; // assuming variable publication is of type Tridion.ContentManager.CommunicationManagement.Publication and the only publication you want the results to return components for

IEnumerable<IdentifiableObject> components = schema.GetUsingItems(filter);

Getting all schemas that used a given embedded schema:

UsingItemsFilter filter = new UsingItemsFilter(embeddedSchema.Session);
filter.ItemTypes = new ItemType[] { ItemType.Schema }; // Note can be an array too
filter.IncludedVersions = VersionCondition.OnlyLatestVersions;

IEnumerable<Schema> schemas = (IEnumerable<Schema>)embeddedSchema.GetUsingItems(filter);

Getting all components using a gien keyword:

UsingItemsFilter filter = new UsingItemsFilter(keyword.Session);

filter.ItemTypes = new List<ItemType> { ItemType.Component };
filter.IncludedVersions = VersionCondition.OnlyLatestVersions;

IEnumerable<IdentifiableObject> components = keyword.GetUsingItems(filter);

Note that the above could have just been retrieved using the keyword’s GetClassifiedItems() method:

IEnumerable<RepositoryLocalObject> components = keyword.GetClassifiedItems();

GetUsedItems

The “GetUsedItems” method of IdentifiableObject works very similarly to the “GetUsingItems” method, but in reverse: it’ll grab items that this item uses. This is the same functionality as the “Uses” tab in the “Where Used” functionality of the GUI.


IdentifiableObject.GetUsedItems();
IdentifiableObject.GetUsedItems(UsedItemsFilter filter);

Get the embedded schemas used in a given schema:

UsedItemsFilter filter = new UsedItemsFilter(schema.Session);
filter.ItemTypes = new ItemType[] { ItemType.Schema };

IEnumerable<IdentifiableObject> embeddedSchemas = schema.GetUsedItems(filter);

Get Components Within ComponentLinks of a given component:

UsedItemsFilter filter = new UsedItemsFilter(component.Session);
filter.ItemTypes = new ItemType[] { ItemType.Component };

var compLinks = component.GetUsedItems(filter);

Getting the List as XML

When working with a pretty large set of items that may get returned, you may want to work with an XML of the used items instead for performance reasons. The following methods return a type of XmlElement:


IdentifiableObject.GetListUsedItems();
IdentifiableObject.GetListUsedItems(UsedItemsFilter filter);
IdentifiableObject.GetListUsingItems();
IdentifiableObject.GetListUsingItems(UsingItemsFilter filter);

Anguilla Framework: Getting Items From Tridion With $tcm.getItem() And Load Events

For anyone wondering if you can get items from Tridion using the Anguilla Framework in your GUI Extensions, the answer is yes, yes you can.

var yourPage = $tcm.getItem("tcm:10-1234-64");

Simple, no? Unfortunately your work is not done here. The getItem() method loads data from a cached state. That means, if the properties that you are looking for hasn’t been loaded already, they will return undefined.

var templateID = yourPage.getPageTemplateId();
// templateID is "undefined".

To load the data, you can call the load() method. You’ll also want to create an event handler for when the event has been triggered.

var yourPage = $tcm.getItem("tcm:10-1234-64");
$evt.addEventHandler(yourPage, "load", function (event) {
    doSomethingWithYourPage(yourPage); // event.source also contains the page instance.
});
yourPage.load();

Remember to take advantage of how Tridion handles the caching. The above script is inefficient, because you’ll always load the item regardless of whether or not it is in the cache. To check if the items has already been loaded, you could do the following:

var yourPage = $tcm.getItem("tcm:10-1234-64");
if (yourPage.isLoaded()) {
    doSomethingWithYourPage(yourPage);
} else {
    $evt.addEventHandler(yourPage, "load", function (event) {
        doSomethingWithYourPage(yourPage); // event.source also contains the page instance.
    });
    yourPage.load();
}

Another possible gotcha is that .load() doesn’t load everything. For example, the WebDav URL. After the .load() call, the following will still not return anything.

var webDavUrl = yourPage.getWebDavUrl();

To get the data you need in the above example, you would have to do something along the lines of:

var yourPage = $tcm.getItem("tcm:10-1234-64"),
    webDavUrl = yourPage.getWebDavUrl();

if (!webDavUrl) {
    $evt.addEventHandler(yourPage, "loadwebdavurl", function (event) {
        webDavUrl = yourPage.getWebDavUrl(); // also could do event.source.getWebDavUrl()
    });
    yourPage.loadWebDavUrl();
}

There are other load functions that load different types of data, each with their own events. Here is a list of them with their associated load and fail event names.

Function Name Load Event Load Fail Event
load() load loadfailed
loadBlueprintInfo() loadblueprintinfo loadblueprintinfofailed
loadItemCompareInfo() compareinfoload compareinfoloadfailed
loadRollBackComment() rollbackcommentload rollbackcommentloadfailed
loadWebDavUrl() loadwebdavurl loadwebdavurlfailed
staticLoad() staticload staticloadfailed

GUI Extensions: Experimenting with Extended Areas

A little back I posted Event System code on Inheriting the Page Process setting on Structure Groups. I mentioned the missing piece: an “Inherit Page Process” like check box to have full control over the inheriting process. I decided to investigate completing this little extension and started looking at Site Edit 2012′s code where they added check boxes for “Enable Inline Editing”. This brought me to experiment with the <ext:extendedareas /> section of the GUI Extension configuration, and I thought I’d share what I learned in case anyone was wondering what these areas were for or how to use them.  NOTE that this is just my experimentation, and if you see anything stated incorrectly (or done in an inefficient way) please do comment!

The Good News

Extended Areas allows you to add your custom controls and code to existing areas in Views. Since I was looking for a way to add a “Inherit Page Process” check box right below the “Associated Page Process” dropdown in the Workflow Tab of the StructureGroup view, I thought that this would be the perfect way of adding my little extension.

How It Works

The extendedarea section contains another section where you get to target the view and the ExtendableArea control. The following comes from the configuration in the SiteEdit 2012 GUI Extension.

<ext:extension assignid="EnableSEForPublicationTarget" name="{Resources: Tridion.Web.UI.Editors.SiteEdit.Strings, SE}">
    <ext:control>~/Extensions/PublicationTarget/EnableSiteEdit.ascx</ext:control>
    <ext:pagetype></ext:pagetype>
    <ext:renderinblock>false</ext:renderinblock>
    <ext:apply>
        <ext:view name="PublicationTargetView">
            <ext:control id="PublicationTargetTab_ExtendableArea" />
        </ext:view>
    </ext:apply>
</ext:extension>

The above example looks for the Control <c:ExtendableArea id=”PublicationTargetTab_ExtendableArea” runat=”server” RenderInBlock=”false” /> on the PublicationTarget View (PublicationTarget.aspx). It will inject a custom control you define in the <ext:control>~/YourControl.ascx</ext:control> into the ExtendableArea control. Sounds good, right?

The Bad News

Unfortunately, not every view and area contains an ExtendableArea control (hopefully this changes in the future!). And unfortunately for me, the WorkflowTab on StructureGroups is one of those unlucky areas that does NOT contain an ExtendableArea control. :(

With the real extension I plan on posting once its finished up, I’ll probably be doing a lil JavaScript injection to get my customization showing up. But for the sake of science and still wanting to complete my experiment using extended areas, I committed an abomination and a no no for this little experiment…

Adding the ExtendedableArea Control Manually

To see my check box get added using an extended area, I added my own ExtendableArea Control to Tridion’s WorkflowStructureGroup.ascx (\WebUI\Editors\CME\Tabs\Workflow\).

<%@ Import Namespace="Tridion.Web.UI"%>
<%@ Control Language="C#" AutoEventWireup="true" Inherits="Tridion.Web.UI.Editors.CME.Tabs.WorkflowStructureGroup" ClassName="WorkflowStructureGroup" %>

<div id="ItemWorkflow">
    <div id="Workflow_StructureGroup">
        <fieldset>
            <div class="field">
                <label for="PageProcess">
                    <asp:Literal runat="server" Text="<%$ Resources: Tridion.Web.UI.Strings, TextPageProcess %>" />
                </label>
                <c:Dropdown id="PageProcess" runat="server" NullText="<%$ Resources: Tridion.Web.UI.Strings, None %>" disabled="true"/>
            </div>
        </fieldset>
        <c:ExtendableArea id="WorkflowStructureGroup_ExtendableArea" runat="server" RenderInBlock="false" />
    </div>
</div>

InheritFromParent.ascx

The following is just the markup for my custom checkbox.

<%@ Control Language="C#" AutoEventWireup="true" %>
<div class="form stack-elem fieldgroup">
    <div class="field">
        <label for="InheritPageProcess">Inherit Page Process</label>
        <div>
            <input type="checkbox" id="InheritPageProcess" />
        </div>
    </div>
</div>
<div class="stack-elem hr"></div>

InheritFromParent.ascx.js

Again, much thanks to the Tridion UI 2012 code for the PublicationTarget extension for me to look at as a refererence. The following JavaScript was used just for some quick testing.

Type.registerNamespace("Tahzoo.Extensions.StructureGroup");

Tahzoo.Extensions.StructureGroup.InheritFromParent = function Tahzoo$Extensions$InheritFromParent() {
    Tridion.OO.enableInterface(this, "Tahzoo.Extensions.StructureGroup.InheritFromParent");
    this.addInterface("Tridion.DisposableObject");

    this.properties.controls = {};
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype.initialize = function Tahzoo$Extensions$InheritFromParent$initialize() {
    var c = this.properties.controls;
    console.log($display.getItem().getTitle() + " Initialized!");
    c.inheritPageProcess = $("#InheritPageProcess");
    c.inheritPageProcess.disabled = true;

    $evt.addEventHandler(c.inheritPageProcess, "click", this.getDelegate(this._onInheritPageProcessClicked));

    var item = $display.getItem();
    if (item) {
        $evt.addEventHandler(item, "load", this.getDelegate(this._onItemLoaded));
        $evt.addEventHandler(item, "change", this.getDelegate(this._onItemChanged));
        $evt.addEventHandler(item, "undocheckoutfailed", this.getDelegate(this._onItemChanged));
        $evt.addEventHandler(item, "checkinfailed", this.getDelegate(this._onItemChanged));

        if (item.isLoaded()) {
            this._onItemLoaded();
        }
    }

};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype.disposeInterface = Tridion.OO.nonInheritable(function Tahzoo$Extensions$InheritFromParent$disposeInterface() {
    var item = $display.getItem();
    if (item) {
        $evt.removeEventHandler(item, "load", this.getDelegate(this._onItemLoaded));
        $evt.removeEventHandler(item, "change", this.getDelegate(this._onItemChanged));
        $evt.removeEventHandler(item, "undocheckoutfailed", this.getDelegate(this._onItemChanged));
        $evt.removeEventHandler(item, "checkinfailed", this.getDelegate(this._onItemChanged));
    }
});

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onItemLoaded = function Tahzoo$Extensions$InheritFromParent$_onItemLoaded() {
    console.log("Item Loaded");
    var item = $display.getItem();

    this._updateCheckbox(item);
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onItemChanged = function Tahzoo$Extensions$InheritFromParent$_onItemChanged(event) {
    console.log("Item Changed");
    this._updateCheckbox($display.getItem());
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onInheritPageProcessClicked = function Tahzoo$Extensions$InheritFromParent$_onInheritPageProcessClicked(event) {
    var c = this.properties.controls;
    console.log("Clicked To: " + c.inheritPageProcess.checked.toString());
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._updateCheckbox = function Tahzoo$Extensions$InheritFromParent$_updateCheckbox(item) {
    this.properties.controls.inheritPageProcess.disabled = item.isReadOnly() || item.isLoading();
};

Tridion.Controls.Deck.registerInitializeExtender("WorkflowTab", Tahzoo.Extensions.StructureGroup.InheritFromParent);

The Extension Configuration (InheritedWFSG.config)

And finally we have our extension’s actual configuration file which pieces everything together.

<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge" xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration" xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions" xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">
    <resources cache="true">
        <cfg:filters/>
        <cfg:groups>
            <cfg:group name="Tridion.Extensions.GUI.InheritedWFSG.StructureGroup">
                <cfg:fileset>
                    <cfg:file type="script">/InheritFromParent.ascx.js</cfg:file>
                </cfg:fileset>
            </cfg:group>
        </cfg:groups>
    </resources>
    <definitionfiles/>
    <extensions>
        <ext:editorextensions>
        <ext:editorextension target="CME">
        <ext:editurls />
        <ext:listdefinitions />
        <ext:itemicons />
        <ext:taskbars />
        <ext:commands />
        <ext:commandextensions />
        <ext:contextmenus />
        <ext:lists />
        <ext:tabpages />
        <ext:toolbars />
        <ext:ribbontoolbars />
        <ext:extendedareas>
            <ext:add>
                <ext:extension assignid="InheritPageProcessFromParent" name="InheritPageProcessFromParent">
                    <ext:control>~/InheritFromParent.ascx</ext:control>
                    <ext:pagetype></ext:pagetype>
                    <ext:renderinblock>false</ext:renderinblock>
                    <ext:dependencies>
                        <cfg:dependency>Tridion.Extensions.GUI.InheritedWFSG.StructureGroup</cfg:dependency>
                    </ext:dependencies>
                    <ext:apply>
                        <ext:view name="StructureGroupView">
                            <ext:control id="WorkflowStructureGroup_ExtendableArea" />
                        </ext:view>
                    </ext:apply>

                </ext:extension>
            </ext:add>
        </ext:extendedareas>
        </ext:editorextension>
        </ext:editorextensions>
        <ext:dataextenders />
    </extensions>
    <commands/>
    <contextmenus/>
    <localization/>
    <settings>
        <defaultpage />
        <navigatorurl />
        <editurls/>
        <listdefinitions/>
        <itemicons/>
        <theme>
            <path/>
        </theme>
        <customconfiguration/>
    </settings>
</Configuration>

And there you have it! If you need help understanding how to deploy the above, check out this tutorial here.

Inherited Page Workflow Process Settings On Structure Groups

If you’ve worked with Tridion Workflows, you know that the process of assigning Component workflows differs greatly from the process of assigning Page workflows.  With components, you just add the Component Process to the Schema (and of course make sure you check the “Enable Workflow Process Associations in Shared Schemas and Structure Groups” from your Content Publication’s properties’s Workflow tab).  However, Page Processes are attached to Structure Groups.  And unfortunately Structure Groups do not inherit this setting, so if you wanted every location where pages get created to be affected, you’ll have to add this setting to each and every Structure Group.

Being inspired by Nuno’s post on inheriting metadata schemas on folders, I thought I’d write up a quick Event System example of how to mock inheritance of Page Processes on Structure Groups.

First lets write an event handler that will set the Associated Page Process of new Structure Groups to that of their parent Structure Group.

public WorkflowStructureGroupHandler()
{
    EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
}

private void SetNewStructureGroupProcessDefinition(StructureGroup sg, LoadEventArgs args, EventPhases phase)
{
    if (sg.Id.IsUriNull)
    {
        StructureGroup parentSG = sg.OrganizationalItem as StructureGroup;
        sg.PageProcess = parentSG.PageProcess;
    }
}

Fairly easy right? This takes care of the automatic setting of Page Processes for new Structure Groups, but what about existing Structure Groups? Normally by the time we start attaching process definitions, there could be a ton of existing Structure Groups already. What if we wanted to modify a parent Structure Group, and have all the children automatically updated? Lets add a Save event to the Structure Groups.

public WorkflowStructureGroupHandler()
{
    EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
    EventSystem.Subscribe<StructureGroup, SaveEventArgs>(SetInheritedStructureGroupProcessDefinitions, EventPhases.TransactionCommitted);
}

private void SetInheritedStructureGroupProcessDefinitions(StructureGroup sg, SaveEventArgs args, EventPhases phase)
{
    if (args.EventStack.Count() > 1)
    {
        return;
    }

    ProcessDefinition pageProcess = sg.PageProcess;

    OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(sg.Session);
    filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

    SetChildrenStructureGroups(sg, filter, pageProcess);
}

private void SetChildrenStructureGroups(StructureGroup sg, OrganizationalItemItemsFilter sgFilter, ProcessDefinition pageProcess)
{
    IEnumerable<RepositoryLocalObject> children = sg.GetItems(sgFilter);

    foreach (StructureGroup structureGroup in children)
    {
        if (structureGroup.PageProcess != pageProcess)
        {
            structureGroup.PageProcess = pageProcess;
            structureGroup.Save();
        }

        SetChildrenStructureGroups(structureGroup, sgFilter, pageProcess);
    }
}

And there you have it, modifying a parent Structure Group will not automatically set its children to have the same Associated Page Process, and creating a new SG will now automatically set its Page Process to that of its parent. The entire code will now look like:

using System.Collections.Generic;
using System.Linq;
using Tridion.ContentManager;
using Tridion.ContentManager.CommunicationManagement;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.Extensibility;
using Tridion.ContentManager.Extensibility.Events;
using Tridion.ContentManager.Workflow;

namespace Tahzoo.EventSystem.Samples
{
    [TcmExtension("WorkflowStructureGroupHandlerExtension")]
    public class WorkflowStructureGroupHandler : TcmExtension
    {
        public WorkflowStructureGroupHandler()
        {
            EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
            EventSystem.Subscribe<StructureGroup, SaveEventArgs>(SetInheritedStructureGroupProcessDefinitions, EventPhases.TransactionCommitted);
        }

        private void SetNewStructureGroupProcessDefinition(StructureGroup sg, LoadEventArgs args, EventPhases phase)
        {
            if (sg.Id.IsUriNull)
            {
                StructureGroup parentSG = sg.OrganizationalItem as StructureGroup;
                sg.PageProcess = parentSG.PageProcess;
            }
        }

        private void SetInheritedStructureGroupProcessDefinitions(StructureGroup sg, SaveEventArgs args, EventPhases phase)
        {
            if (args.EventStack.Count() > 1)
            {
                return;
            }

            ProcessDefinition pageProcess = sg.PageProcess;

            OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(sg.Session);
            filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

            SetChildrenStructureGroups(sg, filter, pageProcess);
        }

        private void SetChildrenStructureGroups(StructureGroup sg, OrganizationalItemItemsFilter sgFilter, ProcessDefinition pageProcess)
        {
            IEnumerable<RepositoryLocalObject> children = sg.GetItems(sgFilter);

            foreach (StructureGroup structureGroup in children)
            {
                if (structureGroup.PageProcess != pageProcess)
                {
                    structureGroup.PageProcess = pageProcess;
                    structureGroup.Save();
                }

                SetChildrenStructureGroups(structureGroup, sgFilter, pageProcess);
            }
        }
    }
}

The solution above is not perfect and there’s still some work that would need to go into this before I’d put it into production (unless of course your project is a one process fits all and everything needs to be affected the same). For example, you’ve configured your Structure Groups that have a combination of different processes and even some without. Edit the root Structure Group, and say good-bye to all of your work! An “Inherit from Parent” eXtension would be perfect here, so you can then tell Structure Groups to not inherit (and thus not change when one of the parent Structure Groups gets saved!) Unfortunately this story is coming to a close, and the GUI eXtension’s story is a tale for another day.

Working With Tridion Workflow Automatic Activities and VBScript

My preference for working with Tridion Workflows is to use the Event System and the Tridion API (TOM API with 5.3 and 2009, and TOM.NET API with Tridion 2011).  This is probably due to my favoritism to .NET over using VBScript (who wouldn’t favor that?).  But knowing how to use the TOM API in the VBScript sections of Automatic Activities does come in handy, and just in case anyone is working with these Automatic Activities, I thought I’d put together some quick samples of how to do some basic things. When working with the VBScrit from “Edit Script…” button, remember to reference the TOM API documentation (not the TOM.NET API docs!).

Getting the Component or Page of the workflow work item.

Dim obtItem
Set objItem = CurrentWorkItem.GetItem()

Remember that VBScript works dynamically here, and the variable Item above will be a Page or a Component based on which WorkItem it is.

Dim strMetaInfo, strPageUrl
If Not objItem.MetadataSchema Is Nothing Then
    strMetaInfo = "Metadata Schema: " + objItem.MetadataSchema.Title
    If Not objItem.MetadataFields.Item("Keywords") Is Nothing Then
        strMetaInfo = strMetaInfo + "Keywords: " + objItem.MetadataFields.Item("Keywords").Value(1)
    End If
End If
strPageUrl =  objItem.Info.PublishLocationUrl

When working with Components, you can access the component fields in a similar way to metadata fields.

strSomeField = objItem.Fields.Item("SomeField").Value(1)

When working with Workflows, you’ll probably want to deal with the various workflow objects like ProcessInstance, ProcessDefinition, ActivityInstance and ActivityDefinition.

' Get the current Process Instance object
Dim objProcessInstance
Set objProcessInstance = CurrentWorkItem.ActivityInstance.ProcessInstance

' Get all Activity Instances that has happened thus far in this Process Instance
Dim objActivityInstances
Set objActivityInstances = objProcessInstance.ActivityInstances

' Get the previous Activity Instance (usually the manual activity that led to this automatic activity)
Dim objLastActivityInstance
Set objLastActivityInstance = objActivityInstances(objActivityInstances.Count - 1)

' Get the Finish Message that was input from the previous Activity Instance
Dim strFinishMessage
strFinishMessage = objLastActivityInstance.FinishMessage

' Get the performer who finished the last activity instance
Dim objLastPerformer, strLastPerformerName
Set objLastPerformer = objLastActivityInstance.Performer
' User object's use "Name" instead of "Title" (and same with groups)
strLastPerformerName = objLastPerformer.Name

' Get the first Activity Instance
Dim objOrigActivityInstance
Set objOrigActivityInstance = objActivityInstances(1)

' Get a specific ActivityDefinition in the Process matching a specific title
Dim objAct, objActivityDefinition
For Each objAct In objProcessInstance.ProcessDefinition.ActivityDefinitions
    If objAct.Title = "Some Specific Title" Then
        Set objActivityDefinition = objAct
    End If
Next

' Get a comma separated list of user IDs from an assigned group in an Activity Definition.
Dim objTrustee, objMembersXml, strMembers
strMembers = ""
Set objMembersXml = CreateObject("MSXML2.DOMDocument.4.0")
Call objMembersXml.LoadXml(objActivityDefinition.Assignee.GetMembersList)
For Each objTrustee In objMembersXml.documentElement.childNodes
    If Len(strMembers) > 0 Then
        strMembers = strMembers + ","
    End if
    strMembers = strMembers & objTrustee.getAttribute("xlink:title")
Next

Finally another important topic of working with Workflows… automatic publishing!

Call objItem.Publish("tcm:0-1-65538", True, True, True)

You’ll want to pay attention to that third argument, especially when you want to publish a work item that hasn’t completed a workflow process yet. You’ll notice that typically, only the last COMPLETED version gets published when you put something in the queue. Setting this third argument to True ensures that the version in the work list gets published. The full method definition for Publish is as follows:

Public Function Publish( ByVal targets As Variant, ByVal activateBlueprinting As Boolean, ByVal activateWorkflow As Boolean, ByVal rollbackOnFailure As Boolean, Optional ByVal publishTime As Date = 0, Optional ByVal unpublishTime As Date = 0, Optional ByVal deployTime As Date = 0, Optional ByVal resolveComponentLinks As Boolean = True, Optional ByVal priority As TDSDefines.EnumPublishPriority = Normal, Optional ByVal ignoreRenderFailures As Boolean = False, Optional ByVal maximumRenderFailures As Long = 0 ) As String

The arguments are as follows:

targets – Specifies to/from which target(s) to (un-/re-)publish. Can be one of the following:
A TargetType object or URI
An array of TargetType URIs
A TargetTypes collection object
A PublicationTarget object or URI
An array of PublicationTarget URIs
A PublicationTarget collection object
activateBlueprinting – Indicates whether the item should also be (un-/re-)published in child publications.
activateWorkflow – Indicates whether the item is being (un-/re-)published from the user’s work list.
rollbackOnFailure – Indicates if the entire publish session should be rolled back if a failure occurs while deploying
publishTime – If specified, the item is published (rendered) at the given date/time.
unpublishTime – If specified, the item is un-published at the given date/time. Should be later than publishTime (if specified)
deployTime – If specified, the item is deployed at the given date/time. Should be later than publishTime and earlier than unpublishTime (if specified). If not specified, the item will be deployed on publishTime (i.e. immediately after rendering).
resolveComponentLinks If specified, it resolves the component links. Default is set to true.
priority If specified, it gives a priority on the publish action. Default is set to normal.
ignoreRenderFailures – If specified, it gives the possibility to continue a publish action when there are render failures. Default is set to false.
maximumRenderFailures – If specified, it sets the limit on the number of render failures and ignoreRenderFailures must be set to true.

To further extend your functionality that you can do in Automatic Workflows, you can also create your own classes and functions that are COM visible, and call those methods and objects from your VBScript.  From your .NET code, you can use the Core Services API or even the old TOM API using the interop DLLs.  Remember, although the TOM.NET API has workflow objects, using it in this manner is not supported, and you should stick with one of the other two APIs.

Razor Helpers and Functions

Now that the latest version of the Razor Mediator for Tridion (v 1.2) allows you to import, I thought perhaps it would be helpful if I go over the Razor syntax for creating Razor Helper functions, or just adding normal functions in general.  Using these in your Razor Templates greatly enhances the functionality that you can perform in your templates.  On my latest project at a client, we developed some pretty advanced templates, and I found myself thinking (and shuddering) of how it would have to have been done if done using Dreamweaver templating.

Normal Functions

You can declare functions in your Razor code and call it just like you were calling any of the built in functions using @functions. Remember, you can have as many @functions sections as you want in your templates, you are not just limited to one.

@functions {
    public string HelloWorld(string name) {
        return "Hello " + name;
    }
}

The above defines a function called “HelloWorld” that you can start using in your template code (or callable in other functions).

<strong>@HelloWorld("John")</strong>

Helper Functions

Helper Functions are also callable via your template code, however Helper Functions allows for Razor syntax and HTML markup from within the function itself.

@helper HelloWorld(string name) {
    <div>Hello <em>@name</em>!</div>
}

The Helper Function would then work exactly the same as the non-Helper Function created above.

<strong>@HelloWorld("John")</strong>

You can mix and match your functions as well.  Following is just a couple more quick examples.

@functions {
    public string GetExtraAttributes(Models.ComponentModel component) {
        string attributes = String.Empty;
        if (component.Metadata.KeyValuePairs != null) {
            foreach (var values in component.Metadata.KeyValuePairs) {
                attributes += String.Format("{0}="{1}"", values.Key, values.Value);
            }
        }
        return attributes;
    }
}

@helper RenderComponentLink(Models.ComponentModel component) {
    <a href="@component.ID" @GetExtraAttributes(component)>@component.Title</a>
}

@helper WrapInParagraphTags(string input) {
    if (input != null && !input.StartsWith("<p")) {
        <p>@input</p>
    } else {
        @input
    }
}

Tridion PublishEngine – Of Transactions and Publish Information

Today I thought I would give some examples of querying publish transactions (publishing queue) as well as getting publish information from specific items, like seeing which Publication Targets a page has been published to and when, or just seeing if an item has been published in general. This post is inspired by a recent question asked in the forums, but I have noticed it come up from time to time. Luckily this task is easy using the TOM.NET API and the static PublishEngine class.

IsPublished

One task you might need to attempt in your Tridion development day to day activities is to check to see if a given item has been published, or published to a particular Publication Target.  PublishEngine contains the following overloaded methods.

bool IsPublished(IdentifiableObject item)
bool IsPublished(IdentifiableObject item, PublicationTarget publicationTarget)
bool IsPublished(IdentifiableObject item, PublicationTarget publicationTarget, bool isPublishedInContext)

The first IsPublished method is useful if you just want to see if an item is published, regardless of which Publication Target it has been published to.

if (PublishEngine.IsPublished(page))
{
    // This page has been published. Do cool stuff here.
}

The second IsPublished method will return true only if the item has been published to the Publication Target passed in the 2nd argument. If the 2nd argument is null, then it’ll act just like the first method and return true if the item has been published to any Publication Target.

if (PublishEngine.IsPublished(page, pubTarget))
{
    // This page has been published to a specific publication target. Do cool stuff here.
}

The third method allows for even finer control. I haven’t personally played with this one as of yet, but the documentation for the isPublishedInContext argument states “Indicates if state should be returned regardless of the context Publication. true only check if item is published in the context Publication; otherwise, false.” I’m assuming this means that, if this argument is set to true, the method will only pass if the item is published to a particular Publication Target, and only if the item is published in it’s own context Publication.

GetPublishInfo

What if you need to get more information about an item? Like, what if you not only wanted to see what Publication Targets it was published to, but at what time too? That’s where the following method comes in hand.

foreach (PublishInfo info in PublishEngine.GetPublishInfo(page))
{
    Console.WriteLine("Published To: " + info.PublicationTarget.Title);
    Console.WriteLine("Published At: " + info.PublishedAt); // The time the page was published
    Console.WriteLine("Published By: " + info.PublishedBy.Title); // The user who published
    Console.WriteLine("Rendered With: " + info.RenderedWith.Title); // The title of the template used
}

The above will loop through each Publication Target that the item has been published to and give you some useful information about the publishing.

GetPublishTransactions

And what if you actually need to check the publishing queue to see if an item has recently been published in the past hour? The PublishEngine allows you to also query the publish transactions.

XmlElement GetListPublishTransactions(PublishTransactionsFilter filter)
IEnumerable<PublishTransaction> GetPublishTransactions(PublishTransactionsFilter filter)

Note that the above methods will throw an AccessDeniedException if the user is not a System Administrator or have PublishManagement rights in any publication. That means if your code relies on always needing to be able to check the transactions, regardless of rights, you’ll need to impersonate.

Session session = new Session("DOMAIN\username"); // Impersonate System Admin or at minimum user with PublishManagement rights.
 
PublishTransactionsFilter filter = new PublishTransactionsFilter(session);
filter.StartDate = DateTime.Now.AddHours(-1); // Add some criteria for when the publishing was started.
filter.PublishTransactionState = PublishTransactionState.Success;

IEnumerable<PublishTransaction> transactions = PublishEngine.GetPublishTransactions(filter);

foreach (PublishTransaction transaction in transactions)
{
    // Check cool stuff with the transaction, like the transaction.Items property.
}

The above will loop through all the successful transactions that has happened in the past hour. Notice the PublishTransactionState property… it allows you to only filter based on one state. But what if you need to grab anything that’s not Success or Failed perhaps, or any other combination of states? You’ll have to grab the items and filter programatically.

PublishTransactionsFilter filter = new PublishTransactionsFilter(session);
filter.StateDate = DateTime.Now.AddHours(-1);

IEnumerable<PublishTransaction> transactions = PublishEngine.GetPublishTransactions(filter)
    .Where(t => t.State != PublishTransactionState.Success && t.State != PublishTransactionState.Failed);

foreach (PublishTransaction transaction in transactions)
{
    // Do cool stuff with transactions that are not Success or Failed
}

And with that I leave. Happy developing everyone!