/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> public async Task <bool> TryFindContent(IPublishedRequestBuilder frequest) { if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) { return(false); } IPublishedContent?node = null; // no alias if "/" if (frequest.Uri.AbsolutePath != "/") { node = FindContentByAlias( umbracoContext !.Content, frequest.Domain != null ? frequest.Domain.ContentId : 0, frequest.Culture, frequest.AbsolutePathDecoded); if (node != null) { frequest.SetPublishedContent(node); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, node.Id); } } } return(node != null); }
private static async Task <Attempt <UrlInfo?> > DetectCollisionAsync( ILogger logger, IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility) { // test for collisions on the 'main' URL var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute); if (uri.IsAbsoluteUri == false) { uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); } uri = uriUtility.UriToUmbraco(uri); IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri); IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound)); if (!pcr.HasPublishedContent()) { const string logMsg = nameof(DetectCollisionAsync) + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}"; logger.LogDebug(logMsg, url, uri, culture); var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture); return(Attempt.Succeed(urlInfo)); } if (pcr.IgnorePublishedContentCollisions) { return(Attempt <UrlInfo?> .Fail()); } if (pcr.PublishedContent?.Id != content.Id) { IPublishedContent?o = pcr.PublishedContent; var l = new List <string>(); while (o != null) { l.Add(o.Name(variationContextAccessor) !); o = o.Parent; } l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent?.Id + ")"; var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture); return(Attempt.Succeed(urlInfo)); } // no collision return(Attempt <UrlInfo?> .Fail()); }
/// <summary> /// Tries to find an Umbraco document for a <c>PublishedRequest</c> and a route. /// </summary> /// <returns>The document node, or null.</returns> protected IPublishedContent FindContent(IPublishedRequestBuilder docreq, string route) { if (!UmbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) { return(null); } if (docreq == null) { throw new System.ArgumentNullException(nameof(docreq)); } _logger.LogDebug("Test route {Route}", route); IPublishedContent node = umbracoContext.Content.GetByRoute(umbracoContext.InPreviewMode, route, culture: docreq.Culture); if (node != null) { docreq.SetPublishedContent(node); _logger.LogDebug("Got content, id={NodeId}", node.Id); } else { _logger.LogDebug("No match."); } return(node); }
/// <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) { if (request.PublishedContent == null) { return; } // don't try to find a redirect if the property doesn't exist if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) { return; } var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; if (redirectId > 0) { redirectUrl = _publishedUrlProvider.GetUrl(redirectId); } else { // might be a UDI instead of an int Id GuidUdi?redirectUdi = request.PublishedContent.Value <GuidUdi>(_publishedValueFallback, Constants.Conventions.Content.Redirect); if (redirectUdi is not null) { redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid); } } if (redirectUrl != "#") { request.SetRedirect(redirectUrl); } }
private async Task SetUmbracoRouteValues(ActionExecutingContext context, IPublishedContent content) { if (content != null) { IUmbracoContextAccessor umbracoContextAccessor = context.HttpContext.RequestServices.GetRequiredService <IUmbracoContextAccessor>(); IPublishedRouter router = context.HttpContext.RequestServices.GetRequiredService <IPublishedRouter>(); var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); IPublishedRequestBuilder requestBuilder = await router.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); requestBuilder.SetPublishedContent(content); IPublishedRequest publishedRequest = requestBuilder.Build(); var routeValues = new UmbracoRouteValues( publishedRequest, (ControllerActionDescriptor)context.ActionDescriptor); context.HttpContext.Features.Set(routeValues); } else { // if there is no content then it should be a not found context.Result = new NotFoundResult(); } }
private async Task SetUmbracoRouteValues(ActionExecutingContext context, IPublishedContent?content) { if (content != null) { UriUtility uriUtility = context.HttpContext.RequestServices.GetRequiredService <UriUtility>(); var originalRequestUrl = new Uri(context.HttpContext.Request.GetEncodedUrl()); Uri cleanedUrl = uriUtility.UriToUmbraco(originalRequestUrl); IPublishedRouter router = context.HttpContext.RequestServices.GetRequiredService <IPublishedRouter>(); IPublishedRequestBuilder requestBuilder = await router.CreateRequestAsync(cleanedUrl); requestBuilder.SetPublishedContent(content); IPublishedRequest publishedRequest = requestBuilder.Build(); var routeValues = new UmbracoRouteValues( publishedRequest, (ControllerActionDescriptor)context.ActionDescriptor); context.HttpContext.Features.Set(routeValues); } else { // if there is no content then it should be a not found context.Result = new NotFoundResult(); } }
/// <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 bool FindPublishedContent(IPublishedRequestBuilder request) { const string tracePrefix = "FindPublishedContent: "; // 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 using (_profilingLogger.DebugDuration <PublishedRouter>( $"{tracePrefix}Begin finders", $"{tracePrefix}End finders")) { // iterate but return on first one that finds it var found = _contentFinders.Any(finder => { _logger.LogDebug("Finder {ContentFinderType}", finder.GetType().FullName); return(finder.TryFindContent(request)); }); _logger.LogDebug( "Found? {Found}, Content: {PublishedContentId}, Template: {TemplateAlias}, Domain: {Domain}, Culture: {Culture}, StatusCode: {StatusCode}", found, request.HasPublishedContent() ? request.PublishedContent.Id : "NULL", request.HasTemplate() ? request.Template?.Alias : "NULL", request.HasDomain() ? request.Domain.ToString() : "NULL", request.Culture ?? "NULL", request.ResponseStatusCode); return(found); } }
/// <summary> /// Looks for wildcard domains in the path and updates <c>Culture</c> accordingly. /// </summary> internal void HandleWildcardDomains(IPublishedRequestBuilder request) { const string tracePrefix = "HandleWildcardDomains: "; if (request.PublishedContent == null) { return; } var nodePath = request.PublishedContent.Path; _logger.LogDebug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null; var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); Domain domain = DomainUtilities.FindWildcardDomainInPath(umbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); // always has a contentId and a culture if (domain != null) { request.SetCulture(domain.Culture); _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture); } else { _logger.LogDebug("{TracePrefix}No match.", tracePrefix); } }
/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> public bool TryFindContent(IPublishedRequestBuilder frequest) { if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) { return(false); } if (umbracoContext == null || (umbracoContext != null && umbracoContext.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdPath)) { return(false); } IPublishedContent node = null; var path = frequest.AbsolutePathDecoded; var nodeId = -1; // no id if "/" if (path != "/") { var noSlashPath = path.Substring(1); if (int.TryParse(noSlashPath, NumberStyles.Integer, CultureInfo.InvariantCulture, out nodeId) == false) { nodeId = -1; } if (nodeId > 0) { _logger.LogDebug("Id={NodeId}", nodeId); node = umbracoContext.Content.GetById(nodeId); if (node != null) { var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture"); // if we have a node, check if we have a culture in the query string if (!string.IsNullOrEmpty(cultureFromQuerystring)) { // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though frequest.SetCulture(cultureFromQuerystring); } frequest.SetPublishedContent(node); _logger.LogDebug("Found node with id={PublishedContentId}", node.Id); } else { nodeId = -1; // trigger message below } } } if (nodeId == -1) { _logger.LogDebug("Not a node id"); } return(node != null); }
/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> /// <remarks>Optionally, can also assign the template or anything else on the document request, although that is not required.</remarks> public async Task <bool> TryFindContent(IPublishedRequestBuilder frequest) { if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) { return(false); } var route = frequest.Domain != null ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded) : frequest.AbsolutePathDecoded; IRedirectUrl?redirectUrl = await _redirectUrlService.GetMostRecentRedirectUrlAsync(route, frequest.Culture); if (redirectUrl == null) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("No match for route: {Route}", route); } return(false); } IPublishedContent?content = umbracoContext.Content?.GetById(redirectUrl.ContentId); var url = content == null ? "#" : content.Url(_publishedUrlProvider, redirectUrl.Culture); if (url.StartsWith("#")) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Route {Route} matches content {ContentId} which has no URL.", route, redirectUrl.ContentId); } return(false); } // Appending any querystring from the incoming request to the redirect URL url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query; if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, content?.Id, url); } frequest .SetRedirectPermanent(url) // From: http://stackoverflow.com/a/22468386/5018 // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads // to problems if you rename a page back to it's original name or create a new page with the original name .SetNoCacheHeader(true) .SetCacheExtensions(new List <string> { "no-store, must-revalidate" }) .SetHeaders(new Dictionary <string, string> { { "Pragma", "no-cache" }, { "Expires", "0" } }); return(true); }
private async Task RouteRequestInternalAsync(IPublishedRequestBuilder builder, bool skipContentFinders = false) { // if request builder was already flagged to redirect then return // whoever called us is in charge of actually redirecting if (builder.IsRedirect()) { return; } // set the culture SetVariationContext(builder.Culture); var foundContentByFinders = false; // Find the published content if it's not assigned. // This could be manually assigned with a custom route handler, etc... // which in turn could call this method // to setup the rest of the pipeline but we don't want to run the finders since there's one assigned. if (!builder.HasPublishedContent() && !skipContentFinders) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", builder.Uri.AbsolutePath); } // run the document finders foundContentByFinders = await FindPublishedContent(builder); } // if we are not a redirect if (!builder.IsRedirect()) { // handle not-found, redirects, access... await HandlePublishedContent(builder); // find a template FindTemplate(builder, foundContentByFinders); // handle umbracoRedirect FollowExternalRedirect(builder); // handle wildcard domains HandleWildcardDomains(builder); // set the culture -- again, 'cos it might have changed due to a finder or wildcard domain SetVariationContext(builder.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(builder); 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 }
/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> /// <remarks>If successful, also assigns the template.</remarks> public override bool TryFindContent(IPublishedRequestBuilder frequest) { var path = frequest.AbsolutePathDecoded; if (frequest.Domain != null) { path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path); } // no template if "/" if (path == "/") { _logger.LogDebug("No template in path '/'"); return(false); } // look for template in last position var pos = path.LastIndexOf('/'); var templateAlias = path.Substring(pos + 1); path = pos == 0 ? "/" : path.Substring(0, pos); ITemplate template = _fileService.GetTemplate(templateAlias); if (template == null) { _logger.LogDebug("Not a valid template: '{TemplateAlias}'", templateAlias); return(false); } _logger.LogDebug("Valid template: '{TemplateAlias}'", templateAlias); // look for node corresponding to the rest of the route var route = frequest.Domain != null ? (frequest.Domain.ContentId + path) : path; IPublishedContent node = FindContent(frequest, route); if (node == null) { _logger.LogDebug("Not a valid route to node: '{Route}'", route); return(false); } // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id)) { _logger.LogWarning("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); frequest.SetPublishedContent(null); // clear return(false); } // got it frequest.SetTemplate(template); return(true); }
/// <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 async Task <bool> FindPublishedContent(IPublishedRequestBuilder request) { const string tracePrefix = "FindPublishedContent: "; // 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 DisposableTimer?profilingScope = null; try { if (_logger.IsEnabled(LogLevel.Debug)) { profilingScope = _profilingLogger.DebugDuration <PublishedRouter>( $"{tracePrefix}Begin finders", $"{tracePrefix}End finders"); } // iterate but return on first one that finds it var found = false; foreach (IContentFinder contentFinder in _contentFinders) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Finder {ContentFinderType}", contentFinder.GetType().FullName); } found = await contentFinder.TryFindContent(request); if (found) { break; } } if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "Found? {Found}, Content: {PublishedContentId}, Template: {TemplateAlias}, Domain: {Domain}, Culture: {Culture}, StatusCode: {StatusCode}", found, request.HasPublishedContent() ? request.PublishedContent?.Id : "NULL", request.HasTemplate() ? request.Template?.Alias : "NULL", request.HasDomain() ? request.Domain?.ToString() : "NULL", request.Culture ?? "NULL", request.ResponseStatusCode); } return(found); } finally { profilingScope?.Dispose(); } }
public void Setting_Domain_Also_Sets_Culture() { IPublishedRequestBuilder sut = GetBuilder(); Assert.IsNull(sut.Culture); sut.SetDomain( new DomainAndUri( new Domain(1, "test", 2, "en-AU", false), new Uri("https://example.com/en-au"))); Assert.IsNotNull(sut.Domain); Assert.IsNotNull(sut.Culture); }
/// <summary> /// Handles the published content (if any). /// </summary> /// <param name="request">The request builder.</param> /// <remarks> /// Handles "not found", internal redirects ... /// things that must be handled in one place because they can create loops /// </remarks> private void HandlePublishedContent(IPublishedRequestBuilder request) { // because these might loop, we have to have some sort of infinite loop detection int i = 0, j = 0; const int maxLoop = 8; do { _logger.LogDebug("HandlePublishedContent: Loop {LoopCounter}", i); // handle not found if (request.PublishedContent == null) { request.SetIs404(); _logger.LogDebug("HandlePublishedContent: No document, try last chance lookup"); // if it fails then give up, there isn't much more that we can do if (_contentLastChanceFinder.TryFindContent(request) == false) { _logger.LogDebug("HandlePublishedContent: Failed to find a document, give up"); break; } _logger.LogDebug("HandlePublishedContent: Found a document"); } // 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) { break; } // 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); if (i == maxLoop || j == maxLoop) { _logger.LogDebug("HandlePublishedContent: Looks like we are running into an infinite loop, abort"); request.SetPublishedContent(null); } _logger.LogDebug("HandlePublishedContent: End"); }
/// <summary> /// This method finalizes/builds 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 BuildRequest(IPublishedRequestBuilder builder) { IPublishedRequest result = builder.Build(); if (!builder.HasPublishedContent()) { return(result); } // set the culture -- again, 'cos it might have changed in the event handler SetVariationContext(result.Culture); return(result); }
private async Task <IPublishedRequest> TryRouteRequest(IPublishedRequestBuilder request) { FindDomain(request); if (request.IsRedirect()) { return(request.Build()); } if (request.HasPublishedContent()) { return(request.Build()); } await FindPublishedContent(request); return(request.Build()); }
private IPublishedRequest TryRouteRequest(IPublishedRequestBuilder request) { FindDomain(request); if (request.IsRedirect()) { return(request.Build()); } if (request.HasPublishedContent()) { return(request.Build()); } FindPublishedContent(request); return(request.Build()); }
private async Task <IPublishedRequest> RouteRequestAsync(IUmbracoContext umbracoContext) { // ok, process // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current url IPublishedRequestBuilder requestBuilder = await _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // TODO: This is ugly with the re-assignment to umbraco context but at least its now // an immutable object. The only way to make this better would be to have a RouteRequest // as part of UmbracoContext but then it will require a PublishedRouter dependency so not sure that's worth it. // Maybe could be a one-time Set method instead? IPublishedRequest routedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)); umbracoContext.PublishedRequest = routedRequest; return(routedRequest); }
/// <inheritdoc /> public async Task <IPublishedRequest> RouteRequestAsync(IPublishedRequestBuilder builder, RouteRequestOptions options) { // outbound routing performs different/simpler logic if (options.RouteDirection == RouteDirection.Outbound) { return(await TryRouteRequest(builder)); } // find domain if (builder.Domain == null) { FindDomain(builder); } await RouteRequestInternalAsync(builder); // complete the PCR and assign the remaining values return(BuildRequest(builder)); }
/// <inheritdoc/> public bool TryFindContent(IPublishedRequestBuilder frequest) { if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) { return(false); } if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int pageId)) { IPublishedContent doc = umbracoContext.Content.GetById(pageId); if (doc != null) { frequest.SetPublishedContent(doc); return(true); } } return(false); }
public void Setting_Published_Content_Clears_Template_And_Redirect() { IPublishedRequestBuilder sut = GetBuilder(); sut.SetTemplate(Mock.Of <ITemplate>()); Assert.IsNotNull(sut.Template); sut.SetInternalRedirect(Mock.Of <IPublishedContent>()); Assert.IsNull(sut.Template); Assert.IsTrue(sut.IsInternalRedirect); sut.SetTemplate(Mock.Of <ITemplate>()); sut.SetPublishedContent(Mock.Of <IPublishedContent>()); Assert.IsNull(sut.Template); Assert.IsFalse(sut.IsInternalRedirect); }
public void Builds_All_Values() { IPublishedRequestBuilder sut = GetBuilder(); IPublishedContent content = Mock.Of <IPublishedContent>(x => x.Id == 1); ITemplate template = Mock.Of <ITemplate>(x => x.Id == 1); string[] cacheExt = new[] { "must-revalidate" }; var auCulture = "en-AU"; var usCulture = "en-US"; var domain = new DomainAndUri( new Domain(1, "test", 2, auCulture, false), new Uri("https://example.com/en-au")); IReadOnlyDictionary <string, string> headers = new Dictionary <string, string> { ["Hello"] = "world" }; var redirect = "https://test.com"; sut .SetNoCacheHeader(true) .SetCacheExtensions(cacheExt) .SetDomain(domain) .SetCulture(usCulture) .SetHeaders(headers) .SetInternalRedirect(content) .SetRedirect(redirect) .SetTemplate(template); IPublishedRequest request = sut.Build(); Assert.AreEqual(true, request.SetNoCacheHeader); Assert.AreEqual(cacheExt, request.CacheExtensions); Assert.AreEqual(usCulture, request.Culture); Assert.AreEqual(domain, request.Domain); Assert.AreEqual(headers, request.Headers); Assert.AreEqual(true, request.IsInternalRedirect); Assert.AreEqual(content, request.PublishedContent); Assert.AreEqual(redirect, request.RedirectUrl); Assert.AreEqual(302, request.ResponseStatusCode); Assert.AreEqual(template, request.Template); Assert.AreEqual(_baseUri, request.Uri); }
/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> public virtual async Task <bool> TryFindContent(IPublishedRequestBuilder frequest) { if (!UmbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) { return(false); } string route; if (frequest.Domain != null) { route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded); } else { route = frequest.AbsolutePathDecoded; } IPublishedContent?node = FindContent(frequest, route); return(node != null); }
/// <summary> /// Tries to find an Umbraco document for a <c>PublishedRequest</c> and a route. /// </summary> /// <returns>The document node, or null.</returns> protected IPublishedContent?FindContent(IPublishedRequestBuilder docreq, string route) { if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { return(null); } if (docreq == null) { throw new ArgumentNullException(nameof(docreq)); } if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Test route {Route}", route); } IPublishedContent?node = umbracoContext.Content?.GetByRoute(umbracoContext.InPreviewMode, route, culture: docreq.Culture); if (node != null) { docreq.SetPublishedContent(node); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Got content, id={NodeId}", node.Id); } } else { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("No match."); } } return(node); }
/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> public bool TryFindContent(IPublishedRequestBuilder frequest) { if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) { return(false); } _logger.LogDebug("Looking for a page to handle 404."); int?domainContentId = null; // try to find a culture as best as we can string errorCulture = CultureInfo.CurrentUICulture.Name; if (frequest.Domain != null) { errorCulture = frequest.Domain.Culture; domainContentId = frequest.Domain.ContentId; } else { var route = frequest.AbsolutePathDecoded; var pos = route.LastIndexOf('/'); IPublishedContent node = null; while (pos > 1) { route = route.Substring(0, pos); node = umbracoContext.Content.GetByRoute(route, culture: frequest?.Culture); if (node != null) { break; } pos = route.LastIndexOf('/'); } if (node != null) { Domain d = DomainUtilities.FindWildcardDomainInPath(umbracoContext.PublishedSnapshot.Domains.GetAll(true), node.Path, null); if (d != null) { errorCulture = d.Culture; } } } var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( _contentSettings.Error404Collection.ToArray(), _entityService, new PublishedContentQuery(umbracoContext.PublishedSnapshot, _variationContextAccessor, _examineManager), errorCulture, domainContentId); IPublishedContent content = null; if (error404.HasValue) { _logger.LogDebug("Got id={ErrorNodeId}.", error404.Value); content = umbracoContext.Content.GetById(error404.Value); _logger.LogDebug(content == null ? "Could not find content with that id." : "Found corresponding content."); } else { _logger.LogDebug("Got nothing."); } frequest .SetPublishedContent(content) .SetIs404(); return(content != null); }
/// <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) { // TODO: We've removed the event, might need to re-add? // 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; } // 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) : 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()) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: Has a template already, and no alternate template."); } return; } // 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; ITemplate?template = GetTemplate(templateId); request.SetTemplate(template); if (template != null) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } else { _logger.LogWarning("FindTemplate: Could not find template with id {TemplateId}", 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()) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: Has a template already, but also an alternative template."); } } if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); } // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings if (request.PublishedContent.IsAllowedTemplate( _fileService, _contentTypeService, _webRoutingSettings.DisableAlternativeTemplates, _webRoutingSettings.ValidateAlternativeTemplates, altTemplate)) { // allowed, use ITemplate?template = _fileService.GetTemplate(altTemplate); if (template != null) { request.SetTemplate(template); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } else { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate); } } } else { _logger.LogWarning( "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; ITemplate?template = GetTemplate(templateId); request.SetTemplate(template); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template?.Id, template?.Alias); } } } if (!request.HasTemplate()) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: No template was found."); } // 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 } }
/// <summary> /// Follows internal redirections through the <c>umbracoInternalRedirectId</c> document property. /// </summary> /// <param name="request">The request builder.</param> /// <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) { if (request.PublishedContent == null) { throw new InvalidOperationException("There is no PublishedContent."); } // don't try to find a redirect if the property doesn't exist if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) { return(false); } var redirect = false; var valid = false; IPublishedContent?internalRedirectNode = null; var internalRedirectId = request.PublishedContent.Value( _publishedValueFallback, Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); if (internalRedirectId > 0) { // try and get the redirect node from a legacy integer ID valid = true; internalRedirectNode = umbracoContext.Content?.GetById(internalRedirectId); } else { GuidUdi?udiInternalRedirectId = request.PublishedContent.Value <GuidUdi>( _publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); if (udiInternalRedirectId is not null) { // try and get the redirect node from a UDI Guid valid = true; internalRedirectNode = umbracoContext.Content?.GetById(udiInternalRedirectId.Guid); } } if (valid == false) { // bad redirect - log and display the current page (legacy behavior) if (_logger.IsEnabled(LogLevel.Debug)) { _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()); } } if (internalRedirectNode == null) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId)?.GetSourceValue()); } } else if (internalRedirectId == request.PublishedContent.Id) { // redirect to self if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FollowInternalRedirects: Redirecting to self, ignore"); } } 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); } redirect = true; if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId); } } return(redirect); }
/// <summary> /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. /// </summary> /// <returns>A value indicating whether a domain was found.</returns> internal bool FindDomain(IPublishedRequestBuilder request) { const string tracePrefix = "FindDomain: "; // note - we are not handling schemes nor ports here. if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); } IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); IDomainCache? domainsCache = umbracoContext.PublishedSnapshot.Domains; var domains = domainsCache?.GetAll(false).ToList(); // 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) { // just get it from content cache - optimize there, not here IPublishedContent?domainDocument = umbracoContext.PublishedSnapshot.Content?.GetById(domain.ContentId); // not published - at all if (domainDocument == null) { return(false); } // invariant - always published if (!domainDocument.ContentType.VariesByCulture()) { return(true); } // variant, ensure that the culture corresponding to the domain's language is published return(domain.Culture is not null && domainDocument.Cultures.ContainsKey(domain.Culture)); } domains = domains?.Where(IsPublishedContentDomain).ToList(); var defaultCulture = domainsCache?.DefaultCulture; // try to find a domain matching the current request DomainAndUri?domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); // handle domain - always has a contentId and a culture if (domainAndUri != null) { // matching an existing domain if (_logger.IsEnabled(LogLevel.Debug)) { _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 { // not matching any existing domain if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix); } request.SetCulture(defaultCulture ?? CultureInfo.CurrentUICulture.Name); } if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture); } return(request.Domain != null); }
public async Task <bool> TryFindContent(IPublishedRequestBuilder frequest) => false;