diff --git a/source/Constants.cs b/source/Constants.cs new file mode 100644 index 0000000..7689975 --- /dev/null +++ b/source/Constants.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; + +namespace SharedSource.RedirectModule +{ + public static class Constants + { + public static class Paths + { + public static string VisitorIdentification = "/layouts/system/visitoridentification"; + public static string MediaLibrary = "/sitecore/media library/"; + } + + public static class Settings + { + public static string RedirExactMatch = "SharedSource.RedirectModule.RedirectionType.ExactMatch"; + public static string RedirPatternMatch = "SharedSource.RedirectModule.RedirectionType.Pattern"; + public static string QueryExactMatch = "SharedSource.RedirectModule.QueryType.ExactMatch"; + public static string QueryPatternMatch = "SharedSource.RedirectModule.QueryType.PatternMatch"; + public static string RedirectRootNode = "SharedSource.RedirectModule.RedirectRootNode"; + + } + public static class Templates + { + public static string RedirectUrl = "Redirect Url"; + public static string VersionedRedirectUrl = "Versioned Redirect Url"; + public static string RedirectPattern = "Redirect Pattern"; + public static string VersionedRedirectPattern = "Versioned Redirect Pattern"; + } + public static class Fields + { + public static string RequestedUrl = "Requested Url"; + public static string RedirectTo = "redirect to"; + public static string RequestedExpression = "requested expression"; + public static string SourceItem = "source item"; + public static string ItemProcessRedirects = "Items Which Always Process Redirects"; + } + + } +} diff --git a/source/RedirectModule.csproj b/source/RedirectModule.csproj index 3946eff..40cdad9 100644 --- a/source/RedirectModule.csproj +++ b/source/RedirectModule.csproj @@ -46,6 +46,7 @@ + diff --git a/source/Redirects.cs b/source/Redirects.cs index aca9a86..af0aef6 100644 --- a/source/Redirects.cs +++ b/source/Redirects.cs @@ -13,146 +13,163 @@ namespace SharedSource.RedirectModule { - /// - /// Redirection Module which handles 301 redirects. Both exact matches and regular expression pattern matches are supported. - /// - public class Redirects : HttpRequestProcessor - { - /// - /// The main method for the processor. It simply overrides the Process method. - /// - public override void Process(HttpRequestArgs args) - { - // This processer is added to the pipeline after the Sitecore Item Resolver. We want to skip everything if the item resolved successfully. - // Also, skip processing for the visitor identification items related to DMS. - Assert.ArgumentNotNull(args, "args"); - if ((Context.Item == null || AllowRedirectsOnFoundItem(Context.Database)) && args.LocalPath != "/layouts/system/visitoridentification" && Context.Database != null) - { - // Grab the actual requested path for use in both the item and pattern match sections. - var requestedUrl = HttpContext.Current.Request.Url.ToString(); - var requestedPath = HttpContext.Current.Request.Url.AbsolutePath; - var requestedPathAndQuery = HttpContext.Current.Request.Url.PathAndQuery; - var db = Context.Database; + /// + /// Redirection Module which handles 301 redirects. Both exact matches and regular expression pattern matches are supported. + /// + public class Redirects : HttpRequestProcessor + { + /// + /// The main method for the processor. It simply overrides the Process method. + /// + public override void Process(HttpRequestArgs args) + { + // This processer is added to the pipeline after the Sitecore Item Resolver. We want to skip everything if the item resolved successfully. + // Also, skip processing for the visitor identification items related to DMS. + Assert.ArgumentNotNull(args, "args"); + if ((Context.Item == null || AllowRedirectsOnFoundItem(Context.Database)) && args.LocalPath != Constants.Paths.VisitorIdentification && Context.Database != null) + { + // Grab the actual requested path for use in both the item and pattern match sections. + var requestedUrl = HttpContext.Current.Request.Url.ToString(); + var requestedPath = HttpContext.Current.Request.Url.AbsolutePath; + var requestedPathAndQuery = HttpContext.Current.Request.Url.PathAndQuery; + var db = Context.Database; - // First, we check for exact matches because those take priority over pattern matches. - if (Sitecore.Configuration.Settings.GetBoolSetting("SharedSource.RedirectModule.RedirectionType.ExactMatch", true)) - { - // Loop through the exact match entries to look for a match. - foreach (Item possibleRedirect in GetRedirects(db, "Redirect Url", Sitecore.Configuration.Settings.GetSetting("SharedSource.RedirectModule.QueryType.ExactMatch"))) - { - if (requestedUrl.Equals(possibleRedirect["Requested Url"], StringComparison.OrdinalIgnoreCase) || - requestedPath.Equals(possibleRedirect["Requested Url"], StringComparison.OrdinalIgnoreCase)) - { - var redirectToItem = db.GetItem(ID.Parse(possibleRedirect.Fields["redirect to"])); - if (redirectToItem != null) - { - SendResponse(redirectToItem, HttpContext.Current.Request.Url.Query, args); - } - } - } - } + // First, we check for exact matches because those take priority over pattern matches. + if (Sitecore.Configuration.Settings.GetBoolSetting(Constants.Settings.RedirExactMatch, true)) + { + // Loop through the exact match entries to look for a match. + foreach (Item possibleRedirect in GetRedirects(db, Constants.Templates.RedirectUrl, Constants.Templates.VersionedRedirectUrl, Sitecore.Configuration.Settings.GetSetting(Constants.Settings.QueryExactMatch))) + { + if (requestedUrl.Equals(possibleRedirect[Constants.Fields.RequestedUrl], StringComparison.OrdinalIgnoreCase) || + requestedPath.Equals(possibleRedirect[Constants.Fields.RequestedUrl], StringComparison.OrdinalIgnoreCase)) + { + var redirectToItem = db.GetItem(ID.Parse(possibleRedirect.Fields[Constants.Fields.RedirectTo])); + if (redirectToItem != null) + { + SendResponse(redirectToItem, HttpContext.Current.Request.Url.Query, args); + } + } + } + } - // Second, we check for pattern matches because we didn't hit on an exact match. - if (Sitecore.Configuration.Settings.GetBoolSetting("SharedSource.RedirectModule.RedirectionType.Pattern", true)) - { - // Loop through the pattern match items to find a match - foreach (Item possibleRedirectPattern in GetRedirects(db, "Redirect Pattern", Sitecore.Configuration.Settings.GetSetting("SharedSource.RedirectModule.QueryType.ExactMatch"))) - { - var redirectPath = string.Empty; - if (Regex.IsMatch(requestedUrl, possibleRedirectPattern["requested expression"], RegexOptions.IgnoreCase)) - { - redirectPath = Regex.Replace(requestedUrl, possibleRedirectPattern["requested expression"], - possibleRedirectPattern["source item"], RegexOptions.IgnoreCase); - } - else if (Regex.IsMatch(requestedPathAndQuery, possibleRedirectPattern["requested expression"], RegexOptions.IgnoreCase)) - { - redirectPath = Regex.Replace(requestedPathAndQuery, - possibleRedirectPattern["requested expression"], - possibleRedirectPattern["source item"], RegexOptions.IgnoreCase); - } - if (string.IsNullOrEmpty(redirectPath)) continue; + // Second, we check for pattern matches because we didn't hit on an exact match. + if (Sitecore.Configuration.Settings.GetBoolSetting(Constants.Settings.RedirPatternMatch, true)) + { + // Loop through the pattern match items to find a match + foreach (Item possibleRedirectPattern in GetRedirects(db, Constants.Templates.RedirectPattern, Constants.Templates.VersionedRedirectPattern, Sitecore.Configuration.Settings.GetSetting(Constants.Settings.QueryExactMatch))) + { + var redirectPath = string.Empty; + if (Regex.IsMatch(requestedUrl, possibleRedirectPattern[Constants.Fields.RequestedExpression], RegexOptions.IgnoreCase)) + { + redirectPath = Regex.Replace(requestedUrl, possibleRedirectPattern[Constants.Fields.RequestedExpression], + possibleRedirectPattern[Constants.Fields.SourceItem], RegexOptions.IgnoreCase); + } + else if (Regex.IsMatch(requestedPathAndQuery, possibleRedirectPattern[Constants.Fields.RequestedExpression], RegexOptions.IgnoreCase)) + { + redirectPath = Regex.Replace(requestedPathAndQuery, + possibleRedirectPattern[Constants.Fields.RequestedExpression], + possibleRedirectPattern[Constants.Fields.SourceItem], RegexOptions.IgnoreCase); + } + if (string.IsNullOrEmpty(redirectPath)) continue; - // Query portion gets in the way of getting the sitecore item. - var pathAndQuery = redirectPath.Split('?'); - var path = pathAndQuery[0]; - if (LinkManager.Provider != null && - LinkManager.Provider.GetDefaultUrlOptions() != null && - LinkManager.Provider.GetDefaultUrlOptions().EncodeNames) - { - path = MainUtil.DecodeName(path); - } - var redirectToItem = db.GetItem(path); - if (redirectToItem != null) - { - var query = pathAndQuery.Length > 1 ? "?" + pathAndQuery[1] : ""; - SendResponse(redirectToItem, query, args); - } - } - } - } - } + // Query portion gets in the way of getting the sitecore item. + var pathAndQuery = redirectPath.Split('?'); + var path = pathAndQuery[0]; + if (LinkManager.Provider != null && + LinkManager.Provider.GetDefaultUrlOptions() != null && + LinkManager.Provider.GetDefaultUrlOptions().EncodeNames) + { + path = MainUtil.DecodeName(path); + } + var redirectToItem = db.GetItem(path); + if (redirectToItem != null) + { + var query = pathAndQuery.Length > 1 ? "?" + pathAndQuery[1] : ""; + SendResponse(redirectToItem, query, args); + } + } + } + } + } - private static bool AllowRedirectsOnFoundItem(Database db) - { - if (db == null) - return false; - var redirectRoot = Sitecore.Configuration.Settings.GetSetting("SharedSource.RedirectModule.RedirectRootNode"); - var redirectFolderRoot = db.SelectSingleItem(redirectRoot); - if (redirectFolderRoot == null) - return false; - var allowRedirectsOnItemIDs = redirectFolderRoot["Items Which Always Process Redirects"]; - return allowRedirectsOnItemIDs != null && - allowRedirectsOnItemIDs.Contains(Context.Item.ID.ToString()); - } + private static bool AllowRedirectsOnFoundItem(Database db) + { + if (db == null) + return false; + var redirectRoot = Sitecore.Configuration.Settings.GetSetting(Constants.Settings.RedirectRootNode); + var redirectFolderRoot = db.SelectSingleItem(redirectRoot); + if (redirectFolderRoot == null) + return false; + var allowRedirectsOnItemIDs = redirectFolderRoot[Constants.Fields.ItemProcessRedirects]; + return allowRedirectsOnItemIDs != null && + allowRedirectsOnItemIDs.Contains(Context.Item.ID.ToString()); + } - /// - /// This method return all of the possible matches for either the exact matches or the pattern matches - /// - private static IEnumerable GetRedirects(Database db, string templateName, string queryType) - { - // Based off the config file, we can run different types of queries. - IEnumerable ret = null; - var redirectRoot = Sitecore.Configuration.Settings.GetSetting("SharedSource.RedirectModule.RedirectRootNode"); - switch (queryType) - { - case "fast": // fast query - { - ret = db.SelectItems(String.Format("fast:{0}//*[@@templatename='{1}']", redirectRoot, templateName)); - break; - } - case "query": // Sitecore query - { - ret = db.SelectItems(String.Format("{0}//*[@@templatename='{1}']", redirectRoot, templateName)); - break; - } - default: // API LINQ - { - Item redirectFolderRoot = db.SelectSingleItem(redirectRoot); - if (redirectFolderRoot != null) - ret = redirectFolderRoot.Axes.GetDescendants().Where(i => i.TemplateName == templateName); - break; - } - } + /// + /// This method return all of the possible matches for either the exact matches or the pattern matches + /// Note: Because Fast Query does not guarantee to return items in the current language context + /// (e.g. while in US/English, results may include other language items as well, even if the + /// US/EN language has no active versions), an additional LINQ query has to be run to filter for language. + /// Choose your query type appropriately. + /// + private static IEnumerable GetRedirects(Database db, string templateName, string versionedTemplateName, string queryType) + { + // Based off the config file, we can run different types of queries. + IEnumerable ret = null; + var redirectRoot = Sitecore.Configuration.Settings.GetSetting(Constants.Settings.RedirectRootNode); + switch (queryType) + { + case "fast": // fast query + { + //process shared template items + ret = db.SelectItems(String.Format("fast:{0}//*[@@templatename='{1}']", redirectRoot, templateName)); - // make sure to return an empty list instead of null - return ret ?? new Item[0]; - } + //because fast query requires to check for active versions in the current language + //run a separate query for versioned items to see if this is even necessary. + //if only shared templates exist in System/Modules, this step is extraneous and unnecessary. + IEnumerable versionedItems = db.SelectItems(String.Format("fast:{0}//*[@@templatename='{1}']", redirectRoot, versionedTemplateName)); - /// - /// Once a match is found and we have a Sitecore Item, we can send the 301 response. - /// - private static void SendResponse(Item redirectToItem, string queryString, HttpRequestArgs args) - { - var redirectToUrl = GetRedirectToUrl(redirectToItem); - args.Context.Response.Status = "301 Moved Permanently"; - args.Context.Response.StatusCode = 301; - args.Context.Response.AddHeader("Location", redirectToUrl + queryString); - args.Context.Response.End(); - } + //if active versions of items in the current context exist, union the two IEnumerable lists together. + ret = versionedItems.Any(i => i.Versions.Count > 0) + ? ret.Union(versionedItems.Where(i => i.Versions.Count > 0)) + : ret; + + + break; + } + case "query": // Sitecore query + { + ret = db.SelectItems(String.Format("{0}//*[@@templatename='{1}' or @@templatename='{2}']", redirectRoot, templateName, versionedTemplateName)); + break; + } + default: // API LINQ + { + Item redirectFolderRoot = db.SelectSingleItem(redirectRoot); + if (redirectFolderRoot != null) + ret = redirectFolderRoot.Axes.GetDescendants().Where(i => i.TemplateName == templateName || i.TemplateName == versionedTemplateName); + break; + } + } + + // make sure to return an empty list instead of null + return ret ?? new Item[0]; + } + + /// + /// Once a match is found and we have a Sitecore Item, we can send the 301 response. + /// + private static void SendResponse(Item redirectToItem, string queryString, HttpRequestArgs args) + { + var redirectToUrl = GetRedirectToUrl(redirectToItem); + args.Context.Response.Status = "301 Moved Permanently"; + args.Context.Response.StatusCode = 301; + args.Context.Response.AddHeader("Location", redirectToUrl + queryString); + args.Context.Response.End(); + } private static string GetRedirectToUrl(Item redirectToItem) { - if (redirectToItem.Paths.Path.StartsWith("/sitecore/media library/")) + if (redirectToItem.Paths.Path.StartsWith(Constants.Paths.MediaLibrary)) { var mediaItem = (MediaItem)redirectToItem; var mediaUrl = MediaManager.GetMediaUrl(mediaItem); @@ -162,6 +179,6 @@ private static string GetRedirectToUrl(Item redirectToItem) return LinkManager.GetItemUrl(redirectToItem); } - } + } }