Files
Umbraco-CMS/src/Umbraco.Core/Routing/PublishedRouter.cs

764 lines
34 KiB
C#
Raw Normal View History

using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
2017-05-30 10:50:09 +02:00
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Security;
namespace Umbraco.Web.Routing
{
2019-01-31 09:08:51 +01:00
/// <summary>
/// Provides the default <see cref="IPublishedRouter"/> implementation.
/// </summary>
public class PublishedRouter : IPublishedRouter
2017-07-20 11:21:28 +02:00
{
private readonly WebRoutingSettings _webRoutingSettings;
2016-10-13 21:08:07 +02:00
private readonly ContentFinderCollection _contentFinders;
2017-07-20 11:21:28 +02:00
private readonly IContentLastChanceFinder _contentLastChanceFinder;
2018-11-27 13:46:43 +01:00
private readonly IProfilingLogger _profilingLogger;
2018-06-03 17:21:15 +02:00
private readonly IVariationContextAccessor _variationContextAccessor;
2020-09-28 08:26:21 +02:00
private readonly ILogger<PublishedRouter> _logger;
private readonly IPublishedUrlProvider _publishedUrlProvider;
private readonly IRequestAccessor _requestAccessor;
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly IPublicAccessChecker _publicAccessChecker;
private readonly IFileService _fileService;
private readonly IContentTypeService _contentTypeService;
private readonly IPublicAccessService _publicAccessService;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
/// <summary>
2017-10-31 12:48:24 +01:00
/// Initializes a new instance of the <see cref="PublishedRouter"/> class.
/// </summary>
2017-10-31 12:48:24 +01:00
public PublishedRouter(
IOptions<WebRoutingSettings> webRoutingSettings,
2016-10-13 21:08:07 +02:00
ContentFinderCollection contentFinders,
IContentLastChanceFinder contentLastChanceFinder,
2018-06-03 17:21:15 +02:00
IVariationContextAccessor variationContextAccessor,
IProfilingLogger proflog,
2020-09-28 08:26:21 +02:00
ILogger<PublishedRouter> logger,
IPublishedUrlProvider publishedUrlProvider,
IRequestAccessor requestAccessor,
IPublishedValueFallback publishedValueFallback,
IPublicAccessChecker publicAccessChecker,
IFileService fileService,
IContentTypeService contentTypeService,
IPublicAccessService publicAccessService,
IUmbracoContextAccessor umbracoContextAccessor)
2017-07-20 11:21:28 +02:00
{
_webRoutingSettings = webRoutingSettings.Value ?? throw new ArgumentNullException(nameof(webRoutingSettings));
2017-05-12 14:49:44 +02:00
_contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders));
_contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder));
_profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog));
2018-06-03 17:21:15 +02:00
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
2020-09-15 09:00:14 +02:00
_logger = logger;
_publishedUrlProvider = publishedUrlProvider;
_requestAccessor = requestAccessor;
_publishedValueFallback = publishedValueFallback;
_publicAccessChecker = publicAccessChecker;
_fileService = fileService;
_contentTypeService = contentTypeService;
_publicAccessService = publicAccessService;
_umbracoContextAccessor = umbracoContextAccessor;
2017-07-20 11:21:28 +02:00
}
2019-01-31 09:08:51 +01:00
/// <inheritdoc />
public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(uri, _fileService);
2019-01-31 09:08:51 +01:00
/// <inheritdoc />
public bool TryRouteRequest(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
FindDomain(request);
2017-05-12 14:49:44 +02:00
// TODO: This was ported from v8 but how could it possibly have a redirect here?
if (request.IsRedirect())
{
return false;
}
2017-05-12 14:49:44 +02:00
// TODO: This was ported from v8 but how could it possibly have content here?
if (request.HasPublishedContent())
{
return true;
}
2017-05-12 14:49:44 +02:00
FindPublishedContent(request);
return request.Build().Success();
2017-07-20 11:21:28 +02:00
}
2017-05-30 10:50:09 +02:00
2018-06-03 17:21:15 +02:00
private void SetVariationContext(string culture)
{
var variationContext = _variationContextAccessor.VariationContext;
if (variationContext != null && variationContext.Culture == culture) return;
_variationContextAccessor.VariationContext = new VariationContext(culture);
}
2019-01-31 09:08:51 +01:00
/// <inheritdoc />
public IPublishedRequest RouteRequest(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
//// trigger the Preparing event - at that point anything can still be changed
//// the idea is that it is possible to change the uri
//request.OnPreparing();
// find domain
2017-07-20 11:21:28 +02:00
FindDomain(request);
2017-07-20 11:21:28 +02:00
// if request has been flagged to redirect then return
// whoever called us is in charge of actually redirecting
if (request.IsRedirect())
2017-07-20 11:21:28 +02:00
{
return request.Build();
2017-07-20 11:21:28 +02:00
}
2017-07-20 11:21:28 +02:00
// set the culture on the thread - once, so it's set when running document lookups
// TODO: Set this on HttpContext!
2017-07-20 11:21:28 +02:00
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
2018-06-03 17:21:15 +02:00
SetVariationContext(request.Culture.Name);
2020-12-08 16:33:50 +11:00
// find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
// with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method
// to setup the rest of the pipeline but we don't want to run the finders since there's one assigned.
2017-07-20 11:21:28 +02:00
if (request.PublishedContent == null)
{
// find the document & template
FindPublishedContentAndTemplate(request);
2017-07-20 11:21:28 +02:00
}
// handle wildcard domains
HandleWildcardDomains(request);
// set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
2018-06-03 17:21:15 +02:00
SetVariationContext(request.Culture.Name);
//// trigger the Prepared event - at that point it is still possible to change about anything
//// even though the request might be flagged for redirection - we'll redirect _after_ the event
//// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change
//request.OnPrepared();
// we don't take care of anything so if the content has changed, it's up to the user
// to find out the appropriate template
2020-12-08 16:33:50 +11:00
// complete the PCR and assign the remaining values
2017-07-20 11:21:28 +02:00
return ConfigureRequest(request);
}
/// <summary>
/// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method
/// finalizes the PCR with the values assigned.
/// </summary>
/// <returns>
/// Returns false if the request was not successfully configured
/// </returns>
/// <remarks>
/// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values
/// but need to finalize it themselves.
/// </remarks>
internal IPublishedRequest ConfigureRequest(IPublishedRequestBuilder frequest)
{
if (!frequest.HasPublishedContent())
{
return frequest.Build();
}
// set the culture on the thread -- again, 'cos it might have changed in the event handler
// TODO: Set this on HttpContext!
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture;
2018-06-03 17:21:15 +02:00
SetVariationContext(frequest.Culture.Name);
return frequest.Build();
}
// TODO: This shouldn't be required and should be handled differently during route building
///// <inheritdoc />
//public void UpdateRequestToNotFound(IPublishedRequest request)
//{
// // clear content
// var content = request.PublishedContent;
// request.PublishedContent = null;
// HandlePublishedContent(request); // will go 404
// FindTemplate(request);
// // if request has been flagged to redirect then return
// // whoever called us is in charge of redirecting
// if (request.IsRedirect)
// {
// return;
// }
// if (request.HasPublishedContent == false)
// {
// // means the engine could not find a proper document to handle 404
// // restore the saved content so we know it exists
// request.PublishedContent = content;
// return;
// }
// if (request.HasTemplate == false)
// {
// // means we may have a document, but we have no template
// // at that point there isn't much we can do and there is no point returning
// // to Mvc since Mvc can't do much either
// return;
// }
//}
2017-07-20 11:21:28 +02:00
/// <summary>
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
2017-07-20 11:21:28 +02:00
/// </summary>
/// <returns>A value indicating whether a domain was found.</returns>
internal bool FindDomain(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
const string tracePrefix = "FindDomain: ";
2017-07-20 11:21:28 +02:00
// note - we are not handling schemes nor ports here.
2020-09-15 09:00:14 +02:00
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
IDomainCache domainsCache = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains;
var domains = domainsCache.GetAll(includeWildcards: false).ToList();
2018-05-08 11:06:07 +02:00
// determines whether a domain corresponds to a published document, since some
// domains may exist but on a document that has been unpublished - as a whole - or
// that is not published for the domain's culture - in which case the domain does
// not apply
bool IsPublishedContentDomain(Domain domain)
{
2018-05-08 11:06:07 +02:00
// just get it from content cache - optimize there, not here
IPublishedContent domainDocument = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId);
2018-05-08 11:06:07 +02:00
// not published - at all
if (domainDocument == null)
{
return false;
}
2018-05-08 11:06:07 +02:00
// invariant - always published
2018-06-20 14:18:57 +02:00
if (!domainDocument.ContentType.VariesByCulture())
{
return true;
}
2018-05-08 11:06:07 +02:00
// variant, ensure that the culture corresponding to the domain's language is published
return domainDocument.Cultures.ContainsKey(domain.Culture.Name);
2018-05-08 11:06:07 +02:00
}
domains = domains.Where(IsPublishedContentDomain).ToList();
2018-04-26 16:03:08 +02:00
var defaultCulture = domainsCache.DefaultCulture;
2017-07-20 11:21:28 +02:00
// try to find a domain matching the current request
DomainAndUri domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
2017-07-20 11:21:28 +02:00
// handle domain - always has a contentId and a culture
if (domainAndUri != null)
{
// matching an existing domain
2020-09-15 09:00:14 +02:00
_logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture);
request
.SetDomain(domainAndUri)
.SetCulture(domainAndUri.Culture);
// canonical? not implemented at the moment
// if (...)
// {
// _pcr.RedirectUrl = "...";
// return true;
// }
}
else
2017-07-20 11:21:28 +02:00
{
// not matching any existing domain
2020-09-15 09:00:14 +02:00
_logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix);
request.SetCulture(defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture));
2017-07-20 11:21:28 +02:00
}
2020-09-15 09:00:14 +02:00
_logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name);
2017-07-20 11:21:28 +02:00
return request.Domain != null;
}
2017-07-20 11:21:28 +02:00
/// <summary>
/// Looks for wildcard domains in the path and updates <c>Culture</c> accordingly.
/// </summary>
internal void HandleWildcardDomains(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
const string tracePrefix = "HandleWildcardDomains: ";
if (request.PublishedContent == null)
{
2017-07-20 11:21:28 +02:00
return;
}
2017-07-20 11:21:28 +02:00
var nodePath = request.PublishedContent.Path;
2020-09-15 09:00:14 +02:00
_logger.LogDebug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath);
var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null;
Domain domain = DomainUtilities.FindWildcardDomainInPath(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId);
// always has a contentId and a culture
2017-07-20 11:21:28 +02:00
if (domain != null)
{
request.SetCulture(domain.Culture);
2020-09-15 09:00:14 +02:00
_logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name);
}
2017-07-20 11:21:28 +02:00
else
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("{TracePrefix}No match.", tracePrefix);
2017-07-20 11:21:28 +02:00
}
}
internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions)
{
if (directory == null || directory.Exists == false)
{
return false;
}
var pos = alias.IndexOf('/');
if (pos > 0)
{
// recurse
DirectoryInfo subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault();
alias = alias.Substring(pos + 1);
return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions);
}
// look here
return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e)));
}
2017-07-20 11:21:28 +02:00
/// <summary>
/// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly.
2017-07-20 11:21:28 +02:00
/// </summary>
private void FindPublishedContentAndTemplate(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath);
2017-07-20 11:21:28 +02:00
// run the document finders
FindPublishedContent(request);
// if request has been flagged to redirect then return
// whoever called us is in charge of actually redirecting
// -- do not process anything any further --
if (request.IsRedirect())
{
2017-07-20 11:21:28 +02:00
return;
}
var foundContentByFinders = request.HasPublishedContent();
2017-07-20 11:21:28 +02:00
// not handling umbracoRedirect here but after LookupDocument2
// so internal redirect, 404, etc has precedence over redirect
2017-07-20 11:21:28 +02:00
// handle not-found, redirects, access...
HandlePublishedContent(request);
2017-07-20 11:21:28 +02:00
// find a template
FindTemplate(request, foundContentByFinders);
2017-07-20 11:21:28 +02:00
// handle umbracoRedirect
FollowExternalRedirect(request);
}
2017-07-20 11:21:28 +02:00
/// <summary>
/// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
/// </summary>
/// <exception cref="InvalidOperationException">There is no finder collection.</exception>
internal void FindPublishedContent(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
const string tracePrefix = "FindPublishedContent: ";
2017-07-20 11:21:28 +02:00
// look for the document
// the first successful finder, if any, will set this.PublishedContent, and may also set this.Template
// some finders may implement caching
2017-10-31 12:48:24 +01:00
using (_profilingLogger.DebugDuration<PublishedRouter>(
$"{tracePrefix}Begin finders",
$"{tracePrefix}End finders"))
2017-07-20 11:21:28 +02:00
{
// iterate but return on first one that finds it
2017-07-20 11:21:28 +02:00
var found = _contentFinders.Any(finder =>
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("Finder {ContentFinderType}", finder.GetType().FullName);
2017-07-20 11:21:28 +02:00
return finder.TryFindContent(request);
});
}
}
/// <summary>
/// Handles the published content (if any).
/// </summary>
/// <remarks>
/// Handles "not found", internal redirects, access validation...
/// things that must be handled in one place because they can create loops
/// </remarks>
private void HandlePublishedContent(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
// because these might loop, we have to have some sort of infinite loop detection
int i = 0, j = 0;
const int maxLoop = 8;
do
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("HandlePublishedContent: Loop {LoopCounter}", i);
2017-07-20 11:21:28 +02:00
// handle not found
if (request.PublishedContent == null)
2017-07-20 11:21:28 +02:00
{
request.SetIs404();
2020-09-15 09:00:14 +02:00
_logger.LogDebug("HandlePublishedContent: No document, try last chance lookup");
2017-07-20 11:21:28 +02:00
// if it fails then give up, there isn't much more that we can do
if (_contentLastChanceFinder.TryFindContent(request) == false)
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("HandlePublishedContent: Failed to find a document, give up");
2017-07-20 11:21:28 +02:00
break;
}
2020-09-15 09:00:14 +02:00
_logger.LogDebug("HandlePublishedContent: Found a document");
2017-07-20 11:21:28 +02:00
}
// follow internal redirects as long as it's not running out of control ie infinite loop of some sort
j = 0;
while (FollowInternalRedirects(request) && j++ < maxLoop)
{ }
// we're running out of control
if (j == maxLoop)
{
2017-07-20 11:21:28 +02:00
break;
}
2017-07-20 11:21:28 +02:00
// ensure access
if (request.PublishedContent != null)
{
2017-07-20 11:21:28 +02:00
EnsurePublishedContentAccess(request);
}
2017-07-20 11:21:28 +02:00
// loop while we don't have page, ie the redirect or access
// got us to nowhere and now we need to run the notFoundLookup again
// as long as it's not running out of control ie infinite loop of some sort
} while (request.PublishedContent == null && i++ < maxLoop);
2017-07-20 11:21:28 +02:00
if (i == maxLoop || j == maxLoop)
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("HandlePublishedContent: Looks like we are running into an infinite loop, abort");
request.SetPublishedContent(null);
2017-07-20 11:21:28 +02:00
}
2020-09-15 09:00:14 +02:00
_logger.LogDebug("HandlePublishedContent: End");
2017-07-20 11:21:28 +02:00
}
/// <summary>
/// Follows internal redirections through the <c>umbracoInternalRedirectId</c> document property.
/// </summary>
/// <returns>A value indicating whether redirection took place and led to a new published document.</returns>
/// <remarks>
/// <para>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</para>
/// <para>As per legacy, if the redirect does not work, we just ignore it.</para>
/// </remarks>
private bool FollowInternalRedirects(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
if (request.PublishedContent == null)
{
2017-07-20 11:21:28 +02:00
throw new InvalidOperationException("There is no PublishedContent.");
}
2017-07-20 11:21:28 +02:00
// don't try to find a redirect if the property doesn't exist
if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false)
{
2017-07-20 11:21:28 +02:00
return false;
}
2017-05-30 10:50:09 +02:00
var redirect = false;
2017-07-20 11:21:28 +02:00
var valid = false;
IPublishedContent internalRedirectNode = null;
var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId, defaultValue: -1);
2017-07-20 11:21:28 +02:00
if (internalRedirectId > 0)
{
// try and get the redirect node from a legacy integer ID
valid = true;
internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(internalRedirectId);
2017-07-20 11:21:28 +02:00
}
else
{
GuidUdi udiInternalRedirectId = request.PublishedContent.Value<GuidUdi>(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId);
2017-07-20 11:21:28 +02:00
if (udiInternalRedirectId != null)
{
// try and get the redirect node from a UDI Guid
valid = true;
internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid);
2017-07-20 11:21:28 +02:00
}
}
if (valid == false)
{
// bad redirect - log and display the current page (legacy behavior)
_logger.LogDebug(
"FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.",
request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
2017-05-30 10:50:09 +02:00
}
if (internalRedirectNode == null)
{
_logger.LogDebug(
"FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.",
request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
2017-05-30 10:50:09 +02:00
}
2017-07-20 11:21:28 +02:00
else if (internalRedirectId == request.PublishedContent.Id)
{
// redirect to self
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FollowInternalRedirects: Redirecting to self, ignore");
2017-07-20 11:21:28 +02:00
}
else
{
2017-05-30 10:50:09 +02:00
request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here
2017-07-20 11:21:28 +02:00
redirect = true;
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId);
2017-07-20 11:21:28 +02:00
}
2017-07-20 11:21:28 +02:00
return redirect;
}
2017-07-20 11:21:28 +02:00
/// <summary>
/// Ensures that access to current node is permitted.
/// </summary>
/// <remarks>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</remarks>
private void EnsurePublishedContentAccess(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
if (request.PublishedContent == null)
{
2017-07-20 11:21:28 +02:00
throw new InvalidOperationException("There is no PublishedContent.");
}
2017-07-20 11:21:28 +02:00
var path = request.PublishedContent.Path;
Attempt<PublicAccessEntry> publicAccessAttempt = _publicAccessService.IsProtected(path);
if (publicAccessAttempt)
2017-07-20 11:21:28 +02:00
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("EnsurePublishedContentAccess: Page is protected, check for access");
PublicAccessStatus status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id);
switch (status)
2017-07-20 11:21:28 +02:00
{
case PublicAccessStatus.NotLoggedIn:
2020-09-15 09:00:14 +02:00
_logger.LogDebug("EnsurePublishedContentAccess: Not logged in, redirect to login page");
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.LoginNodeId);
break;
case PublicAccessStatus.AccessDenied:
2020-09-15 09:00:14 +02:00
_logger.LogDebug("EnsurePublishedContentAccess: Current member has not access, redirect to error page");
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
break;
case PublicAccessStatus.LockedOut:
2020-09-15 09:00:14 +02:00
_logger.LogDebug("Current member is locked out, redirect to error page");
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
break;
case PublicAccessStatus.NotApproved:
2020-09-15 09:00:14 +02:00
_logger.LogDebug("Current member is unapproved, redirect to error page");
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
break;
case PublicAccessStatus.AccessAccepted:
2020-09-15 09:00:14 +02:00
_logger.LogDebug("Current member has access");
break;
2017-07-20 11:21:28 +02:00
}
}
else
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("EnsurePublishedContentAccess: Page is not protected");
2017-07-20 11:21:28 +02:00
}
}
private void SetPublishedContentAsOtherPage(IPublishedRequestBuilder request, int errorPageId)
{
if (errorPageId != request.PublishedContent.Id)
{
request.SetPublishedContent(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId));
}
}
2017-07-20 11:21:28 +02:00
/// <summary>
/// Finds a template for the current node, if any.
/// </summary>
/// <param name="request">The request builder.</param>
/// <param name="contentFoundByFinders">If the content was found by the finders, before anything such as 404, redirect... took place.</param>
private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByFinders)
2017-07-20 11:21:28 +02:00
{
// TODO: We've removed the event, might need to re-add?
2017-07-20 11:21:28 +02:00
// NOTE: at the moment there is only 1 way to find a template, and then ppl must
// use the Prepared event to change the template if they wish. Should we also
// implement an ITemplateFinder logic?
if (request.PublishedContent == null)
{
request.SetTemplate(null);
return;
}
2017-07-20 11:21:28 +02:00
// 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 = contentFoundByFinders
|| (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent);
var altTemplate = useAltTemplate
? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate)
2017-07-20 11:21:28 +02:00
: null;
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())
2017-07-20 11:21:28 +02:00
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FindTemplate: Has a template already, and no alternate template.");
2017-07-20 11:21:28 +02:00
return;
}
2019-02-28 11:01:53 +10:00
// 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
2017-07-20 11:21:28 +02:00
var templateId = request.PublishedContent.TemplateId;
ITemplate template = GetTemplate(templateId);
request.SetTemplate(template);
_logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
2017-07-20 11:21:28 +02:00
}
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())
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FindTemplate: Has a template already, but also an alternative template.");
}
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate);
2017-07-20 11:21:28 +02:00
2018-09-06 14:10:10 +02:00
// IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings
if (request.PublishedContent.IsAllowedTemplate(
_fileService,
_contentTypeService,
_webRoutingSettings.DisableAlternativeTemplates,
_webRoutingSettings.ValidateAlternativeTemplates,
altTemplate))
2017-07-20 11:21:28 +02:00
{
2018-09-06 14:10:10 +02:00
// allowed, use
ITemplate template = _fileService.GetTemplate(altTemplate);
2018-09-06 14:10:10 +02:00
if (template != null)
{
request.SetTemplate(template);
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
2018-09-06 14:10:10 +02:00
}
else
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate);
2018-09-06 14:10:10 +02:00
}
2017-07-20 11:21:28 +02:00
}
else
{
2020-09-15 09:00:14 +02:00
_logger.LogWarning("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id);
2018-09-06 14:10:10 +02:00
// no allowed, back to default
var templateId = request.PublishedContent.TemplateId;
ITemplate template = GetTemplate(templateId);
request.SetTemplate(template);
_logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
2017-07-20 11:21:28 +02:00
}
}
if (!request.HasTemplate())
2017-07-20 11:21:28 +02:00
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("FindTemplate: No template was found.");
2017-07-20 11:21:28 +02:00
// initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true
// then reset _pcr.Document to null to force a 404.
//
// but: because we want to let MVC hijack routes even though no template is defined, we decide that
// a missing template is OK but the request will then be forwarded to MVC, which will need to take
// care of everything.
//
// so, don't set _pcr.Document to null here
}
}
private ITemplate GetTemplate(int? templateId)
2018-09-06 14:10:10 +02:00
{
if (templateId.HasValue == false || templateId.Value == default)
2018-09-06 14:10:10 +02:00
{
2020-09-15 09:00:14 +02:00
_logger.LogDebug("GetTemplateModel: No template.");
2018-09-06 14:10:10 +02:00
return null;
}
2020-09-15 09:00:14 +02:00
_logger.LogDebug("GetTemplateModel: Get template id={TemplateId}", templateId);
2018-09-06 14:10:10 +02:00
if (templateId == null)
{
throw new InvalidOperationException("The template is not set, the page cannot render.");
}
ITemplate template = _fileService.GetTemplate(templateId.Value);
2018-09-06 14:10:10 +02:00
if (template == null)
{
2018-09-06 14:10:10 +02:00
throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render.");
}
2020-09-15 09:00:14 +02:00
_logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
2018-09-06 14:10:10 +02:00
return template;
}
2017-07-20 11:21:28 +02:00
/// <summary>
/// Follows external redirection through <c>umbracoRedirect</c> document property.
/// </summary>
/// <remarks>As per legacy, if the redirect does not work, we just ignore it.</remarks>
private void FollowExternalRedirect(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
if (request.PublishedContent == null)
{
return;
}
2017-07-20 11:21:28 +02:00
// don't try to find a redirect if the property doesn't exist
if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false)
{
2017-07-20 11:21:28 +02:00
return;
}
2017-05-30 10:50:09 +02:00
var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1);
2017-07-20 11:21:28 +02:00
var redirectUrl = "#";
if (redirectId > 0)
{
redirectUrl = _publishedUrlProvider.GetUrl(redirectId);
2017-07-20 11:21:28 +02:00
}
else
{
// might be a UDI instead of an int Id
GuidUdi redirectUdi = request.PublishedContent.Value<GuidUdi>(_publishedValueFallback, Constants.Conventions.Content.Redirect);
2017-07-20 11:21:28 +02:00
if (redirectUdi != null)
{
redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid);
}
2017-07-20 11:21:28 +02:00
}
2017-05-30 10:50:09 +02:00
if (redirectUrl != "#")
{
request.SetRedirect(redirectUrl);
}
2017-07-20 11:21:28 +02:00
}
}
}