Sunday 10 November 2013

Orchard CMS Taxonomy Term Autoroute Token Slug v2.0

This is a new and improved solution to my original post http://sheltonial.blogspot.com.au/2013/10/orchard-cms-taxonomy-term-autoroute.html.

The purpose of this post is to provide a method of returning a full taxonomy term path slug as a token in Orchard CMS. I am currently using this solution in Orchard 1.7.1 as of changeset 91d0048f2796f093a8cefe2111ff013818d220fb.

The problem with the previous solution was that it only rendered the 1 selected taxonomy term into the token result, which was great if you were working with the root terms, but as soon as you went deeper it did not render the full taxonomy term path into the token result.

The following solution allows a full taxonomy term path to be slugified and rendered into a token result.

using System;
using System.Linq;
using Orchard.Taxonomies.Fields;
using Orchard.Localization;
using Orchard.Tokens;

namespace Orchard.Taxonomies.Tokens {
    public class TaxonomyTokens : ITokenProvider {

        public TaxonomyTokens() {
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }

        public void Describe(DescribeContext context) {
            // Usage:
            // Content.Fields.Article.Categories.Terms -> 'Science, Sports, Arts'
            // Content.Fields.Article.Categories.Terms:0 -> 'Science'

            // When used with an indexer, it can be chained with Content tokens
            // Content.Fields.Article.Categories.Terms:0.DisplayUrl -> http://...

            context.For("TaxonomyField", T("Taxonomy Field"), T("Tokens for Taxonomy Fields"))
                   .Token("Terms", T("Terms"), T("The terms (Content) associated with field."))
                   .Token("Terms[:*]", T("Terms"), T("A term by its index. Can be chained with Content tokens."))
                   .Token("TermPathSlug", T("Terms"), T("Selected taxonomy term full path slugified."))
                   ;
        }

        public void Evaluate(EvaluateContext context) {
            //context.For<TaxonomyField>("TaxonomyField").Data.TermsField.Value.ElementAt(0).Slug

            context.For<TaxonomyField>("TaxonomyField")
                   .Token("Terms", field => String.Join(", ", field.Terms.Select(t => t.Name).ToArray()))
                   .Token(
                       token => token.StartsWith("Terms:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Terms:".Length) : null,
                       (token, t) => {
                           var index = Convert.ToInt32(token);
                           return index + 1 > t.Terms.Count() ? null : t.Terms.ElementAt(index).Name;
                       })
                   .Token("TermPathSlug", field => context.For<TaxonomyField>("TaxonomyField").Data.TermsField.Value.ElementAt(0).Slug)
                // todo: extend Chain() in order to accept a filter like in Token() so that we can chain on an expression
                   .Chain("Terms:0", "Content", t => t.Terms.ElementAt(0))
                   .Chain("Terms:1", "Content", t => t.Terms.ElementAt(1))
                   .Chain("Terms:2", "Content", t => t.Terms.ElementAt(2))
                   .Chain("Terms:3", "Content", t => t.Terms.ElementAt(3))
                   ;
        }
    }
}

An example of an autoroute pattern that works is {Content.Fields.BlogPost.Category.TermPathSlug}, where [BlogPost] is the name of your content type, and [Category] is the name of the field within our content type, not the taxonomy or term.

This could be used is reflecting a taxonomy term hierarchy within a URL using an AutoRoute pattern.

Please be aware that this solution grabs the first selected taxonomy term, so your taxonomy field should be configured so that only 1 term can be selected.

Refer to https://orchard.codeplex.com/workitem/20215 regarding updates on the Orchard work request relating to this.

Hope this helps! Any questions or if you have an improved solution let me know.

Wednesday 16 October 2013

Orchard CMS Taxonomy Term Autoroute Token Slug

Edit 17/10/2013:
Refer to https://orchard.codeplex.com/workitem/20215 on updates regarding this feature.

Edit 11/11/2013:
Since writing this article I've discovered a better method which allows a full taxonomy term path to be slugified and returned into a token result: http://sheltonial.blogspot.com.au/2013/11/orchard-cms-taxonomy-term-autoroute.html

Recently (as of 17/10/2013) the Orchard CMS team has fixed a bug which was preventing taxonomy tokens from returning a value.

Currently I am working off changeset a62f0281ebdac780a1d3a583dbc5821ca8fbc6ce on version 1.7.1 and can confirm that this is working.

An example of an autoroute pattern that works is {Content.Fields.BlogPost.Category.Terms:0}, where [BlogPost] is the name of your content type, and [Category] is the name of the field within our content type, not the taxonomy or term.

This means we can start injecting taxonomy terms into our autoroute patterns and enjoy structuring a url hierarchy based on taxonomy terms and being able to interact with this new url data within queries and layer rules. This is fine if your taxonomy is a single word containing no spaces or special characters, but once you introduce other characters these are also injected into the URL.

The following code adds a new token called TermSlug which allows a taxonomy term to be slugified:

  • File to modify: \src\Orchard.Web\Modules\Orchard.Taxonomies\Tokens\TaxonomyTokens.cs
  • Usage: {Content.Fields.BlogPost.Category.TermsSlug:0} (refer to example above)
// Comment
using System;
using System.Linq;
using Orchard.Taxonomies.Fields;
using Orchard.Localization;
using Orchard.Tokens;
using Orchard.Autoroute.Services;

namespace Orchard.Taxonomies.Tokens {
    public class TaxonomyTokens : ITokenProvider {
        private readonly ISlugService _slugService;

        public TaxonomyTokens(ISlugService slugService)
        {
            T = NullLocalizer.Instance;
            _slugService = slugService;
        }

        public Localizer T { get; set; }

        public void Describe(DescribeContext context) {
            // Usage:
            // Content.Fields.Article.Categories.Terms -> 'Science, Sports, Arts'
            // Content.Fields.Article.Categories.Terms:0 -> 'Science'

            // When used with an indexer, it can be chained with Content tokens
            // Content.Fields.Article.Categories.Terms:0.DisplayUrl -> http://...

            context.For("TaxonomyField", T("Taxonomy Field"), T("Tokens for Taxonomy Fields"))
                   .Token("Terms", T("Terms"), T("The terms (Content) associated with field."))
                   .Token("Terms[:*]", T("Terms"), T("A term by its index. Can be chained with Content tokens."))
                   .Token("TermsSlug[:*]", T("Terms"), T("A term slug by its index. Can be chained with Content tokens."))
                   ;
        }

        public void Evaluate(EvaluateContext context) {

            context.For("TaxonomyField")
                   .Token("Terms", field => String.Join(", ", field.Terms.Select(t => t.Name).ToArray()))
                   .Token(
                       token => token.StartsWith("Terms:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Terms:".Length) : null,
                       (token, t) => {
                           var index = Convert.ToInt32(token);
                           return index + 1 > t.Terms.Count() ? null : t.Terms.ElementAt(index).Name;
                       })
                    .Token(
                       token => token.StartsWith("TermsSlug:", StringComparison.OrdinalIgnoreCase) ? token.Substring("TermsSlug:".Length) : null,
                       (token, t) =>
                       {
                           var index = Convert.ToInt32(token);
                           return index + 1 > t.Terms.Count() ? null :  _slugService.Slugify(t.Terms.ElementAt(index).Name);
                       })
                // todo: extend Chain() in order to accept a filter like in Token() so that we can chain on an expression
                   .Chain("Terms:0", "Content", t => t.Terms.ElementAt(0))
                   .Chain("Terms:1", "Content", t => t.Terms.ElementAt(1))
                   .Chain("Terms:2", "Content", t => t.Terms.ElementAt(2))
                   .Chain("Terms:3", "Content", t => t.Terms.ElementAt(3))
                   ;
        }
    }
}

Hope this helps!

Monday 21 February 2011

Windows 2008 R2 Terminal Server Error: The task you are trying to do can't be completed because the Remote Desktop Services is currently busy. Please try again in a few minutes. Other users should still be able to log on. Solution/Fix/Remedy

The following is not a permanent solution or fix, but the steps should help logging users back on the terminal server without having to reboot and inconvenience other users logged on.

  1. Log onto the terminal server and open the Remote Desktop Services Manager.
  2. Select the terminal server you are logged onto in the left hand pane. (if you are logged on remotely you may not have sufficient permissions to end processes).
  3. Get the Session ID of the hung session(s)
    1. Click on the Sessions tab. Look for sessions which display the following characteristics (as the image above):
       - Session: Disconnected
       - User: [BLANK]
       - State: Disconnected
       - ClientName: [BLANK]
       - LogOnTime: Unknown
    2. Take note of the ID field. In the screen shot above this is "6".
    3. NOT REQUIRED: This step is not required but can be useful: If you right click on the session, click "Connect", enter a domain administrators password and click ok, you can connect to the session and can see at what stage the login has hung.
  4. Free the hung session(s)
    1. Click on the Processes tab, click on the ID column to sort the processes by ID. Scroll down through the list and find processes matching the ID gathered from the previous step (in this example "6")
    2. Commonly with this particular error, only 3 processes will be visible - LogonUI.exe, winlogon.exe, and csrss.exe
    3. Right click on winlogon.exe and click End Process. Once you end this process the other 2 should disappear. WARNING: do not end csrss.exe, this will crash and reboot your terminal server.
  5. Now get the user who was having difficulties logging on to try again.
Cheers