Wednesday, May 30, 2007

Sitecore 5.3 AutoLink add-on

this post applies to Sitecore v5.3.1

So, i was thinking about the pipelines and processors available in Sitecore, and more specifically the availability of hooking into them and how it could be used on the rich text fields.. there's actually a great processor to use called uiSaveHtml that you can hook into - and so i wrote a small module/add-on that can be used to automatically convert word(s) to links, all specified in a sitecore structure and easily managed.

Here's what i ended up doing, and if you follow these steps you'll have a working AutoLink add-on:

step 1: creating the autolink item template and sample item

create a new template called 'autolinkitem'. this template we will use to define each of the substituting 'autolinks'. now, go ahead and add the following fields to it:


  • AutoLink - text

  • Target - link

  • NewWindow - checkbox

after that - save, build & publish it.

now we're going to wanna create a sample item so we'll know if it's working or not, so first create a folder somewhere called 'Autolinks' and then create a new autolinkitem (based on the above created template) in that folder (i've went with /sitecore/content/global/autolinks as the path for my folder) and set the fields to something nice..





screenshot #1: the template & sample item


step 2: create the processor

create a new C# class library (or add to an existing one) that we'll use to store our code in and reference in the processor later on. throughout this post the namespace will be 'usoniandream.autolink' but you can of course name it whatever you wish.


  • add a reference to System.Web (nice to have)

  • add a reference to Sitecore.Kernel (don't copy local).

  • add an empty class called AutoLinkProcessor.cs

now, go ahead and add the following code to your new processor:
using System;
using System.Collections.Generic;
using Sitecore;
using Sitecore.Data.Items;
using Sitecore.Data.Fields;
using
Sitecore.Pipelines;
using Sitecore.Pipelines.SaveHtml;
using
Sitecore.SecurityModel;
using System.Text;
using
System.Text.RegularExpressions;
namespace usoniandream.autolink.pipelines
{
public class AutoLinkProcessor
{
///
///
processor for handling the autolink replacement of words to links
///

/// SaveHtmlArgs
public void
Process(Sitecore.Pipelines.SaveHtml.SaveHtmlArgs args)
{
// make sure we
only do this for the items that actually has some body html to parse
if
(args.Body!="")
{
try
{
// find our root folder for the autolink
items
Item autolinkfolder =
Sitecore.Context.ContentDatabase.Items[Sitecore.Configuration.Settings.GetSetting("usoniandream.autolinks.rootfolder")];
if (autolinkfolder != null)
{
// try to get the autolink items
Item[] itms = autolinkfolder.Axes.SelectItems(autolinkfolder.Paths.Path +
"/*[@@templatekey='autolinkitem']");
if (itms != null)
{
// log it
for informational purposes
Sitecore.Diagnostics.Log.Info("found " +
itms.Length.ToString() + " autolinks to try and patch with..", "AutoLinks");
// handle it as a List instead of an array (simply easier)
List
autolinks = new List(itms);
// make sure we got some autolinks to
parse with
if (autolinks != null && autolinks.Count > 0)
{
// perform the replacement(s)
args.Body = AutoLink(args.Body,
autolinks);
}
}
else
{
// log that we didn't find any items
Sitecore.Diagnostics.Log.Info("unable to find autolink items..",
"autolink");
}
}
else
{
// log that the folder is missing
Sitecore.Diagnostics.Log.Info("unable to find autolink items root folder..",
"autolink");
}
}
catch (Exception exception)
{
// add the
message to the argument and log it for informational purposes
args.AddMessage(exception.ToString(),PipelineMessageType.Warning);
Sitecore.Diagnostics.Log.Error("autolink error:", exception, "autolink");
}
}
}
///
/// Initiates the process of iterating
through the autolink items
///

/// the body
to link within
/// Generic List of items
containing autolink information
/// the finished body html
now with linked words

private string AutoLink(string text,
List autolinks)
{
LinkField lf;
foreach (Item itm in
autolinks)
{
lf = (LinkField)itm.Fields["target"];
if (lf!=null)
{
text = ReplaceWithAutoLinks(text, itm["autolink"], lf.Url,
itm["newwindow"]);
}
}
return text;
}
///
///
Replaces all occurances of a word with the specified link text
///

/// body html to replace within
/// the word to search for
/// the url to link to
/// taken
from the checkbox field
/// System.String
private string ReplaceWithAutoLinks(string text, string search, string link,
string newWindow)
{
// make sure we don't parse any uneccessary item
data that isn't properly entered..
if (text!=string.Empty &&
search!=string.Empty && link!=string.Empty)
{
// build our
replacement link
string replacewith = " <a href="/" alt="'\">"
+ search + "</a> ";
// define our (sorry, but extremely weak)
regex pattern that'll match our words with whitespaces before and after it..
string pattern = @"\s" + search + @"\s";
// redefine the replacement
link if it's for a new window
if (newWindow == "1")
{
replacewith =
" <a href="/" target="'\" alt="'\">" + search +
"</a> ";
}
// log it so we know what's happening..
Sitecore.Diagnostics.Log.Info("autolink pattern '" + pattern + "' will be
replaced with '" + replacewith, "autolink");
// return the parsed text
text = System.Text.RegularExpressions.Regex.Replace(text, pattern,
replacewith, RegexOptions.IgnoreCase);
}
else
{
Sitecore.Diagnostics.Log.Error("autolink couldn't parse since the input data
wasn't properly supplied", "autolink");
}
return text;
}
}
}

once that's done we're gonna have to compile it and make sure it ends up in the bin folder so the system can find it when we're gonna call it.

step 3: editing the web.config file and hooking into the processor

now that we're ready to go and have our code put to use we'll need to tell sitecore that it should, so here's what we're gonna do:


  • locate the uiSaveHtml processor element in the web.config file

add the following line within it as the first processor in the list:

<processor type="usoniandream.autolink.pipelines.AutoLinkProcessor, usoniandream.autolink" mode="on">


  • add a setting so we can easily find our autolinks root folder

add the following line anywhere within the Settings section:

<setting name="usoniandream.autolinks.rootfolder" value="/sitecore/content/global/autolinks">

as you can see that setting points to a folder i mentioned earlier in the post, but as always you can name it anything you like and have it point anywhere you like.
  • save the web.config file

step 4: the finished product

now you're all set and ready to run the finished add-on! Open the content editor and go to any item that has a rich text field on it, double click it so it opens in the editor, write the autolink word(s) that you specified in your sample item (created in step 1) somewhere and click on Accept.




screenshot #2: text before it's automatically converted (just plain text in a richtext field)


Assuming you've followed the above steps correctly you should now see that the word has been replaced with a link instead.




screenshot #3: automatically converted the word 'sitecore' to a link to the sitecore website


step 5: go to bed..

well, that's it for me. time for some much needed sleep.

take care,

P.

4 comments:

Anonymous said...

Peter, Peter, Peter,

This is Soooooooo cool! And even with source!

Intefolio/Metamatrix can be proud having such a great Sitecore asset in you.

Anonymous said...

We are! Damn Proud!

Dennis, Interfolio/Metamatrix

Anonymous said...

Maybe totally irrelevant, but if you save twice, does it add a second anchor around the text?

Also for performance you might create the list of replacements as a member variable, then just check the count each time the processing method is called so you're not constantly recreating the structure.

Unknown said...

the regex pattern is (as it says in the comment) weak, but not that weak.. it parses the text for any of the 'autolinks' surrounded by whitespaces, so the answer to your question is 'No, it will not parse twice'.

of course the regex pattern can be updated to a stronger pattern, but at 3 in the morning i didn't really feel up to writing one :)

performance-wise iterating the list of autolinks takes no time at all, but you're more than welcome to adapt it :)

improvents, ideas and thoughts are more than welcome, and there are ways to improve it - adding the routine to the publish pipeline instead of the save would be one, but then you'd also have to iterate through the fields on each item which might be an unneccessary stress to each publishing action. in that approach one should probably instead create a stand-alone 'publish with autolinks' pipeline that could be selected.. hmm.. not a bad idea, think i gotta look into that one.