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

804 lines
36 KiB
C#
Raw Normal View History

using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events;
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;
private readonly IEventAggregator _eventAggregator;
/// <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,
IEventAggregator eventAggregator)
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;
_eventAggregator = eventAggregator;
2017-07-20 11:21:28 +02:00
}
2019-01-31 09:08:51 +01:00
/// <inheritdoc />
public async Task<IPublishedRequestBuilder> CreateRequestAsync(Uri uri)
{
// trigger the Creating event - at that point the URL can be changed
// this is based on this old task here: https://issues.umbraco.org/issue/U4-7914 which was fulfiled by
// this PR https://github.com/umbraco/Umbraco-CMS/pull/1137
// It's to do with proxies, quote:
/*
"Thinking about another solution.
We already have an event, PublishedContentRequest.Prepared, which triggers once the request has been prepared and domain, content, template have been figured out -- but before it renders -- so ppl can change things before rendering.
Wondering whether we could have a event, PublishedContentRequest.Preparing, which would trigger before the request is prepared, and would let ppl change the value of the request's URI (which by default derives from the HttpContext request).
That way, if an in-between equipement changes the URI, you could replace it with the original, public-facing URI before we process the request, meaning you could register your HTTPS domain and it would work. And you would have to supply code for each equipment. Less magic in Core."
*/
// but now we'll just have one event for creating so if people wish to change the URL here they can but nothing else
var creatingRequest = new CreatingRequestNotification(uri);
await _eventAggregator.PublishAsync(creatingRequest);
var publishedRequestBuilder = new PublishedRequestBuilder(creatingRequest.Url, _fileService);
return publishedRequestBuilder;
}
2019-01-31 09:08:51 +01:00
/// <inheritdoc />
public Task<bool> TryRouteRequestAsync(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 Task.FromResult(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 Task.FromResult(true);
}
2017-05-12 14:49:44 +02:00
FindPublishedContent(request);
return Task.FromResult(request.Build().Success());
2017-07-20 11:21:28 +02:00
}
2017-05-30 10:50:09 +02:00
2021-01-07 20:37:36 +11:00
private void SetVariationContext(CultureInfo culture)
2018-06-03 17:21:15 +02:00
{
2021-01-07 22:05:23 +11:00
// set the culture on the thread - once, so it's set when running document lookups
// TODO: Set this on HttpContext!
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture;
2021-01-07 20:37:36 +11:00
VariationContext variationContext = _variationContextAccessor.VariationContext;
if (variationContext != null && variationContext.Culture == culture?.Name)
{
return;
}
_variationContextAccessor.VariationContext = new VariationContext(culture?.Name);
2018-06-03 17:21:15 +02:00
}
2019-01-31 09:08:51 +01:00
/// <inheritdoc />
public async Task<IPublishedRequest> RouteRequestAsync(IPublishedRequestBuilder request)
2017-07-20 11:21:28 +02:00
{
// 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
}
2021-01-07 22:05:23 +11:00
// set the culture
2021-01-07 20:37:36 +11:00
SetVariationContext(request.Culture);
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);
2021-01-07 22:05:23 +11:00
// set the culture -- again, 'cos it might have changed due to a finder or wildcard domain
2021-01-07 20:37:36 +11:00
SetVariationContext(request.Culture);
// trigger the routing request (used to be called 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
var routingRequest = new RoutingRequestNotification(request);
await _eventAggregator.PublishAsync(routingRequest);
// 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();
}
2021-01-07 22:05:23 +11:00
IPublishedRequest result = frequest.Build();
2021-01-07 20:37:36 +11:00
2021-01-07 22:05:23 +11:00
// set the culture -- again, 'cos it might have changed in the event handler
2021-01-07 20:37:36 +11:00
SetVariationContext(result.Culture);
2021-01-07 20:37:36 +11:00
return result;
}
// 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);
// 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, foundContentByFinders);
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>
/// <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>
2017-07-20 11:21:28 +02:00
/// <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, bool contentFoundByFinders)
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, contentFoundByFinders) && j++ < maxLoop)
2017-07-20 11:21:28 +02:00
{ }
// 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>
/// <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>
2017-07-20 11:21:28 +02:00
/// <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, bool contentFoundByFinders)
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
{
// save since it will be cleared
ITemplate template = request.Template;
request.SetInternalRedirect(internalRedirectNode); // don't use .PublishedContent here
// must restore the template if it's an internal redirect & the config option is set
if (request.IsInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate)
{
// restore
request.SetTemplate(template);
}
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.IsInternalRedirect);
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
}
}
}