Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions source/Constants.cs
Original file line number Diff line number Diff line change
@@ -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";
}

}
}
1 change: 1 addition & 0 deletions source/RedirectModule.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Constants.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Redirects.cs" />
</ItemGroup>
Expand Down
279 changes: 148 additions & 131 deletions source/Redirects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,146 +13,163 @@

namespace SharedSource.RedirectModule
{
/// <summary>
/// Redirection Module which handles 301 redirects. Both exact matches and regular expression pattern matches are supported.
/// </summary>
public class Redirects : HttpRequestProcessor
{
/// <summary>
/// The main method for the processor. It simply overrides the Process method.
/// </summary>
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;
/// <summary>
/// Redirection Module which handles 301 redirects. Both exact matches and regular expression pattern matches are supported.
/// </summary>
public class Redirects : HttpRequestProcessor
{
/// <summary>
/// The main method for the processor. It simply overrides the Process method.
/// </summary>
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());
}

/// <summary>
/// This method return all of the possible matches for either the exact matches or the pattern matches
/// </summary>
private static IEnumerable<Item> GetRedirects(Database db, string templateName, string queryType)
{
// Based off the config file, we can run different types of queries.
IEnumerable<Item> 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;
}
}
/// <summary>
/// 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.
/// </summary>
private static IEnumerable<Item> GetRedirects(Database db, string templateName, string versionedTemplateName, string queryType)
{
// Based off the config file, we can run different types of queries.
IEnumerable<Item> 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<Item> versionedItems = db.SelectItems(String.Format("fast:{0}//*[@@templatename='{1}']", redirectRoot, versionedTemplateName));

/// <summary>
/// Once a match is found and we have a Sitecore Item, we can send the 301 response.
/// </summary>
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];
}

/// <summary>
/// Once a match is found and we have a Sitecore Item, we can send the 301 response.
/// </summary>
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);
Expand All @@ -162,6 +179,6 @@ private static string GetRedirectToUrl(Item redirectToItem)

return LinkManager.GetItemUrl(redirectToItem);
}
}
}
}