Friday, 28 February 2014

RedDot 11.x new security settings


The new security setting implemented by OpenText in the 11.x version of RedDot are causing problems for a few people. Unless your RedDot servers are public facing (probably not in most cases) you can safely disable the cross site scripting and session checking code.

Why would I want to do that?

Because they can prevent various plugins and extensions from working properly.

How?

Edit \OpenText\WS\MS\Web\Navigation\web.config
Comment out or remove the line:
<add name="HttpSessionModule" type="OpenText.WS.MS.Interop.Security.HttpSessionModule,OpenText.WS.MS.Server.Ui"/>

Edit \OpenText\WS\MS\ASP\web.config
Comment out or remove the following:
<add name="AntiCsrfModule" type="OpenText.WS.MS.Core.Security.Csrf.AntiCsrfModule,OpenText.WS.MS.Core, Version=11.0.1.0, Culture=neutral, PublicKeyToken=9763136D9E6661AD"/>

N.B. You will have to reapply these changes after an upgrade or re-install.

Sunday, 29 January 2012

Speeding up the UI

What if we could speed up the UI for the users and make it simpler at the same time. RQL caching to the rescue!
I have been reviewing old blog posts and forum entries and came across this little gem by Kim Dezen RedDot CMS Plugin - Add Page.

Resulting in a page interface like this:


I thought I'd have a go myself but found the performance a little slow for users while the RQL churns in the background to find the template pre-assignments. Seeing as the template pre-assignments will hardly ever change once the project is built I decided to cache the results in a database resulting in an instantaneous population of the dropdown.
If a new option needs to be added to the dropdown simply delete the cache and let it be generated again!
[The only caveat is that the pre-assignments have to be taken from the template level and not the page instance itself but as this only happens very rarely (when the cache for a template is initialised) I can live with it.]

Ta,
John

Tuesday, 6 December 2011

RustyLogic RedDotNet Library Update

A new version of the RustyLogic RedDotNet library including .asmx for working with AJAX is now uploaded to the RedDot Solutions Exchange, available here.

The latest version is the dated file.


Ta,
John

Sunday, 4 December 2011

RedDot SEO (Part 2)

How to implement unique filenames across a project - everything you need to know for a robust approach. (As I'm busy destroying fixing our house with various DIY projects it's going to be a short and sweet post.)

Caveats:
Only works in smart tree mode.
Doesn't work with direct edit of headline enabled (the cache of the page isn't cleared  / updated).
How does it work?
The logic is fairly straightforward if you look at the template code.
There are two checks, one for filename changes and one for headline changes and the filename is then updated accordingly. A mandatory field is used to stop the page from being released until a unique filename has been set (the user must supply one if a clash has occured).

We need some extra fields in our content class:
  • stfPreviousFilename (stf string)
  • stfPreviousHeadline (stf string)
  • Unique_Filename (stf string) (Mandatory field)
  • infPageGuid (Guid of page)

Here is the extra code we need in our template:


<!IoRangeRedDotEditOnly>
<script type="text/javascript">
    head(function ()
    {
        var h1headline = $('h1').html();
        var reload = false;
        // If user updates headline (or creates new page)
        if (("<%Unique_Filename%>" != "Disabled") && ("<%!! Escape:HtmlEncode(<%hdlHeadline%>) !!%>" != "<%!! Escape:HtmlEncode(<%stfPreviousHeadline%>) !!%>"))
        {
            // parse headline
            var parsedHeadline = h1headline.toLowerCase().replace(/(\s|\.|_|"|')+/g, "-");
            var parsedHeadline = parsedHeadline.replace(/(\?)+/g, "");
            if (IsUniqueFilenameTest("<%infPageGuid%>", parsedHeadline ))
            {
                SetFilename("<%infPageGuid%>", parsedHeadline);
                SetElement("<%infPageGuid%>", "Unique_Filename", "Unlocked");
                SetElement("<%infPageGuid%>", "stfPreviousHeadline", h1headline);
                SetElement("<%infPageGuid%>", "stfPreviousFilename", Filename("<%infPageGuid%>"));
                reload = true;
            }
            else
            {
                if ("<%!! Escape:HtmlEncode(<%stfPreviousFilename%>) !!%>" != "" || "<%Unique_Filename%>" != "" || Filename("<%infPageGuid%>") != "")
                {
                    // Stop the updated filename check from firing
                    SetFilename("<%infPageGuid%>", "");
                    SetElement("<%infPageGuid%>", "stfPreviousFilename", "");
                    // set flag to block page release.
                    SetElement("<%infPageGuid%>", "Unique_Filename", "");
                    reload = true;
                }
            }
        }
        // If user updates filename
        if (("<%Unique_Filename%>" != "Disabled"))
        {
            var filename = Filename("<%infPageGuid%>");
            if (filename != "<%stfPreviousFilename%>")
            {
                if (IsUniqueFilename("<%infPageGuid%>") && filename != "")
                {
                    SetElement("<%infPageGuid%>", "stfPreviousHeadline", h1headline );
                    SetElement("<%infPageGuid%>", "stfPreviousFilename", filename);
                    SetElement("<%infPageGuid%>", "Unique_Filename", "Unlocked");
                    reload = true;
                }
                else
                {
                    if ("<%Unique_Filename%>" != "")
                    {
                        // set flag to block page release.
                        SetElement("<%infPageGuid%>", "Unique_Filename", "");
                        reload = true;
                    }
                }
            }
        }
        if (reload)
        {
            ClearCache("<%infPageGuid%>");
            //location.reload();
        }
    });
</script>
<!/IoRangeRedDotEditOnly>

You will also need to include the following somewhere:

function ExecuteRql(rqlCommand)
{
    var result = "";
            $.each(rqlCommand, function (commandName, commandParameters) {
                    $.ajax({
                        type: "POST",
                        async: false,
                        contentType: "application/json; charset=utf-8",
                        data: JSON.stringify(commandParameters),
                        url: "/services/services.asmx/" + commandName,
                        dataType: "json",
                        
                        success: function (response) 
                        {
                            result = response.d;
                        },
                        error: function (response) 
                        {
                        },
                        complete: function (response) 
                        {
                        }
                    });
            });
    return result;
}

function DeletePage(pageGuid) 
{
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": pageGuid
                 };
            $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                data: JSON.stringify(params),
                url: "/services/services.asmx/Delete",
                dataType: "json"
            });
}
function Filename(pageGuid) 
{
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": pageGuid
                 };
    var rqlCommand = { "Filename": params };
    return ExecuteRql(rqlCommand);
}

function SetFilename(pageGuid, filename) 
{
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": pageGuid,
                    "filename": filename
                 };
    var rqlCommand = { "SetFilename": params };
    return ExecuteRql(rqlCommand);
}

function IsUniqueFilename(pageGuid) 
{
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": pageGuid
                 };
    var rqlCommand = { "IsUniqueFilename": params };
    return ExecuteRql(rqlCommand);
}

function IsUniqueFilenameTest(pageGuid, filename) 
{
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": pageGuid,
                    "filename": filename
                 };
    var rqlCommand = { "IsUniqueFilenameTest": params };
    return ExecuteRql(rqlCommand);
}

function SetElement(pageGuid, elementName, elementValue) 
{
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": pageGuid,
                    "elementName": elementName,
                    "elementValue": elementValue
                 };
    var rqlCommand = { "SetElement": params };
    return ExecuteRql(rqlCommand);
}

function ClearCache(pageGuid) 
{
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": pageGuid
                 };
            $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                data: JSON.stringify(params),
                url: "/services/services.asmx/ClearCache",
                dataType: "json"
            });
}
 
Last but not least you will need the *updated* RustyLogic RedDotNet library including the .asmx (which I will upload to solutions exchange when I get a chance and post a link here).

A very dry post I'm afraid but I hope still useful :)

Ta,
John


Saturday, 19 November 2011

RedDot SEO (Part 1)

SEO is important for commercial websites and that's a given. As for local government (where I work) it has never been seen as quite so relevant. The rather blunt reasoning being that the public don't get a choice in which council they use, it's a captive audience so why bother? One thing is for certain, a users first view of your site (whatever its intended audience) is often not your homepage (therefore ignoring the exceptional navigation you have implemented), it's generally via search engine results.
This is where SEO can help us all; pointing the user in the right direction before they even step foot on your site. Anyway, enough of the waffle as I'm no doubt preaching to the converted here.

SEO and RedDot

I decided to revisit an area I expect every RedDotter has looked into at least once, that of enforcing unique & meaningful urls. The usual first part of this is easy enough - manually define publication packages with sensible directory names; all well and good, and usually done by some kind of RedDot admin so no worries about users getting in a pickle.
The second part is a bit more tricky, making sure published filenames are SEO friendly. There are already a couple of plugins available on the Solutions Exchange that will set page filenames to mirror the page headline but RedDot does not currently enable enforcement of unique filenames within a publication directory. Not an ideal situation and if a duplication occurs files are overwritten.


What process would we like RedDot to follow? Probably something like the following:

  • When a page is created or renamed, set the filename to a parsed version of it (no nasty characters etc.).
  • If a user manually sets a filename don't overwrite it with the page headline (unless the headline subsequently changes).
  • If there is a duplciate filename in existence; prompt the user for an alternative or suggest they change the headline to something more unique.
  • Don't let the user release the page if it contains a duplicate filename (ALA mandatory field warning).
  • Provide a SmartTree accessible toggle to disable the functionality for individual pages.

 

SEO and RQL

This is all possible with javascript and RQL and it's something I've been toying with the last couple of days. My main priorities for the solution:
  • Automated as much as possible; I don't want to bother the user unless really necessary.
  • As few RQL calls as possible within SmartEdit (Pure JS calls for any mandatory calls), I don't want the editing process slowed down.
So far I have been concentrating on embedding the processing in SmartEdit page-open.
I have the RQL down to one single mandatory call (I need the current filename and this is only available via RQL).

 

Impact on users

I am however, having some second thoughts on this approach. Maybe it should be done as part of the workflow process?

So what to do:
  • Stick with SmartEdit page processing which slows the editing process a little.
  • Let workflow do the file naming duplication checks and reject the page to an admin for fixing if required (but they won't thank me for the extra work).
  • As above but reject the page back to the user (unfortunately users hate workflow rejections and won't thank RedDot for not prompting them in SmartEdit before the released the page).
I think I'm going to stick with the SmartEdit option and finish off the implementation next week; I can always change tact at a later date if I think it's not really working.

Once completed, I will explain the RQL / JS in the second part of this post and (time allowing) post a newer version of the RustyLogic RedDotNet library with the backend code you will need if you want to follow the same approach.
Any thoughts on the subject more than welcome :)

Ta,
John

Sunday, 13 November 2011

RedDot: Advanced Page Defintions

Page Definitions are great but what if we could expand them to allow the use of page references and even inheritance? Here's one approach to the challenge.
  
Background...
I love users but sometimes its best to keep things simple for them. Options are the enemy especially if they really don't use the CMS very often. Too many options can lead to mistakes and a general mess which requires someone to mop up and the users confidence is dented.

A good way to keep things simple is by using template pre-assignments and page definitions. I have seen many projects use this approach successfully, but, it can lead to multiple (but very similar) base templates which can become a chore to maintain with endless duplicate page definitions cascading off each.






Problem...
With these thoughts in mind, I started putting a new site together based upon the wonderfully simple 960grid css framework. Determined not to have multiple base templates but to still keep things simple for the user I experimented with page definitions a little deeper. The main problem I ran into was that you cannot assign references to page definitions.



Solution...
I'm not going to ramble on about why you would want or need to do this (it would take too long and I'm sure you will have your own opinions about it) we'll just move on to the how part...
With some basic jquery and knowledge of RQL you can achieve simple page referencing and much more...

Firstly you'll need an initialisation template for each reference (you assign this to the page definition in the place you want the actual reference to appear), here's one of mine:

<!IoRangeRedDotMode>
<script type="text/javascript">
    if (!rqlCommands)
    {
        var rqlCommands = [];
    }

    head(function() {
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": "<%!! Context:Pages.GetPage(Guid:<%infPageGuid%>).GetElementByName(conLeftNav).Value[Int32:0].Id  !!%>",
                    "linkGuid": "<%!! Context:Pages.GetPage(Guid:<%infPageGuid%>).MainLink.Id !!%>"
                 };
        var rqlCommand = { "SetReference": params };
        rqlCommands.push(rqlCommand);

        params = {  "loginGuid": "<%infLoginGuid%>", 
                        "sessionKey": "<%infSessionKey%>", 
                        "pageGuid": "<%infPageGuid%>"
                     };
        rqlCommand = { "Delete": params };
        rqlCommands.push(rqlCommand);
    });
</script>
<!/IoRangeRedDotMode>


So what's it doing?
Two commands are being formed here and added to an array (There maybe several of these initialisation classes in one page all adding commands to the array).
The first is defining parameters to be used in our RQL to replace the instance of our initialisation page with a reference of the actual page we want.
The second is to delete our initialisation instance so it doesn't hang around in unconnected pages.
Don't worry about the head(function()); it's there because I'm using head.js, if you're using vanilla jquery it would be $document.ready() or similar.

At the bottom of our base template we include a javascript file to process the queue of commands. Here I'm using an AJAX (or SJAX to be more precise) call to a custom web service which in turn calls my RustyLogic RedDotNet library, you could of course call the RQL functions directly in javascript or any other language you choose.

Here is my version:

<script type="text/javascript">

<!IoRangeRedDotMode>
    head(function () {
    if (!(typeof rqlCommands === "undefined")) {
            while (rqlCommands.length > 0) {
                $('#loading').replaceWith('<div id="loading" class="loading" style="width: 100%; height: 100%; top: 100px; display: inline-block; position:absolute; text-align: center; z-index:10000;"><p><img src="/cms/WebClient/App_Themes/Standard/Images/spinner.gif" />Please wait while page is initialised...' + rqlCommands.length + '</p></div>');
                var rqlCommand = rqlCommands.pop();
                ExecuteRql(rqlCommand);
            }
            location.reload();
        }
    });
<!/IoRangeRedDotMode>


function ExecuteRql(rqlCommand)
{
            $.each(rqlCommand, function (commandName, commandParameters) {

                    $.ajax({
                        type: "POST",
                        async: false,
                        contentType: "application/json; charset=utf-8",
                        data: JSON.stringify(commandParameters),
                        url: "/services/RustyLogic.asmx/" + commandName,
                        dataType: "json",
                        
                        success: function (response) 
                        {
                        },
                        error: function (response) 
                        {
                        },
                        complete: function (response) 
                        {
                        }
                    });
            });
}


</script>
 
When a new page is created in SmartEdit the queue is built and executed followed by a page refresh to show the changes to the user.

It's a simple example of what can be achieved but opens up many possibilities. It is easily expanded to allow inheritance from parent pages (I am using it to inherit top navigation when a child page is created).
This has resulted in a simple base class which can be used throughout the site and automation of page initialisation which means less for the user to worry about.

I hope you have enjoyed my first blog post,

John