diff --git a/src/Umbraco.Abstractions/Constants-Conventions.cs b/src/Umbraco.Abstractions/Constants-Conventions.cs
index 6672f22239..0bfb890abd 100644
--- a/src/Umbraco.Abstractions/Constants-Conventions.cs
+++ b/src/Umbraco.Abstractions/Constants-Conventions.cs
@@ -208,6 +208,17 @@ namespace Umbraco.Core
public const string AllMembersListId = "all-members";
}
+ ///
+ /// Constants for Umbraco URLs/Querystrings.
+ ///
+ public static class Url
+ {
+ ///
+ /// Querystring parameter name used for Umbraco's alternative template functionality.
+ ///
+ public const string AltTemplate = "altTemplate";
+ }
+
///
/// Defines the alias identifiers for built-in Umbraco relation types.
///
@@ -274,6 +285,7 @@ namespace Umbraco.Core
//TODO: return a list of built in types so we can use that to prevent deletion in the uI
}
+
}
}
}
diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs
index 74fe9f93e6..9148ce2e31 100644
--- a/src/Umbraco.Web/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Web/Routing/PublishedRouter.cs
@@ -20,6 +20,7 @@ namespace Umbraco.Web.Routing
///
public class PublishedRouter : IPublishedRouter
{
+ private readonly IWebRoutingSection _webRoutingSection;
private readonly ContentFinderCollection _contentFinders;
private readonly IContentLastChanceFinder _contentLastChanceFinder;
private readonly ServiceContext _services;
@@ -33,6 +34,7 @@ namespace Umbraco.Web.Routing
/// Initializes a new instance of the class.
///
public PublishedRouter(
+ IWebRoutingSection webRoutingSection,
ContentFinderCollection contentFinders,
IContentLastChanceFinder contentLastChanceFinder,
IVariationContextAccessor variationContextAccessor,
@@ -41,6 +43,7 @@ namespace Umbraco.Web.Routing
IUmbracoSettingsSection umbracoSettingsSection,
IUserService userService)
{
+ _webRoutingSection = webRoutingSection ?? throw new ArgumentNullException(nameof(webRoutingSection));
_contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders));
_contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder));
_services = services ?? throw new ArgumentNullException(nameof(services));
@@ -641,14 +644,74 @@ namespace Umbraco.Web.Routing
return;
}
- if (request.HasTemplate)
- {
- _logger.Debug("FindTemplate: Has a template already, and no alternate template.");
- return;
- }
+ // read the alternate template alias, from querystring, form, cookie or server vars,
+ // only if the published content is the initial once, else the alternate template
+ // does not apply
+ // + optionally, apply the alternate template on internal redirects
+ var useAltTemplate = request.IsInitialPublishedContent
+ || (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent);
+ var altTemplate = useAltTemplate
+ ? request.UmbracoContext.HttpContext.Request[Constants.Conventions.Url.AltTemplate]
+ : null;
- var templateId = request.PublishedContent.TemplateId;
- request.TemplateModel = GetTemplateModel(templateId);
+ if (string.IsNullOrWhiteSpace(altTemplate))
+ {
+ // we don't have an alternate template specified. use the current one if there's one already,
+ // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...),
+ // else lookup the template id on the document then lookup the template with that id.
+
+ if (request.HasTemplate)
+ {
+ _logger.Debug("FindTemplate: Has a template already, and no alternate template.");
+ return;
+ }
+
+ // TODO: When we remove the need for a database for templates, then this id should be irrelevant,
+ // not sure how were going to do this nicely.
+
+ // TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type!
+ // if the template isn't assigned to the document type we should log a warning and return 404
+
+ var templateId = request.PublishedContent.TemplateId;
+ request.TemplateModel = GetTemplateModel(templateId);
+ }
+ else
+ {
+ // we have an alternate template specified. lookup the template with that alias
+ // this means the we override any template that a content lookup might have set
+ // so /path/to/page/template1?altTemplate=template2 will use template2
+
+ // ignore if the alias does not match - just trace
+
+ if (request.HasTemplate)
+ _logger.Debug("FindTemplate: Has a template already, but also an alternative template.");
+ _logger.Debug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate);
+
+ // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings
+ if (request.PublishedContent.IsAllowedTemplate(altTemplate))
+ {
+ // allowed, use
+ var template = _services.FileService.GetTemplate(altTemplate);
+
+ if (template != null)
+ {
+ request.TemplateModel = template;
+ _logger.Debug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
+ }
+ else
+ {
+ _logger.Debug("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate);
+ }
+ }
+ else
+ {
+ _logger.Warn("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id);
+
+ // no allowed, back to default
+ var templateId = request.PublishedContent.TemplateId;
+ request.TemplateModel = GetTemplateModel(templateId);
+ }
+ }
if (request.HasTemplate == false)
{