/// <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(PublishedRequest 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}Executing finders...", $"{tracePrefix}Completed executing finders")) { //iterate but return on first one that finds it var found = _contentFinders.Any(finder => { _logger.Debug <PublishedRouter>("Finder {ContentFinderType}", finder.GetType().FullName); return(finder.TryFindContent(request)); }); _profilingLogger.Debug <PublishedRouter>( "Found? {Found} Content: {PublishedContentId}, Template: {TemplateAlias}, Domain: {Domain}, Culture: {Culture}, Is404: {Is404}, StatusCode: {StatusCode}", found, request.HasPublishedContent ? request.PublishedContent.Id : "NULL", request.HasTemplate ? request.TemplateAlias : "NULL", request.HasDomain ? request.Domain.ToString() : "NULL", request.Culture?.Name ?? "NULL", request.Is404, request.ResponseStatusCode); } // indicate that the published content (if any) we have at the moment is the // one that was found by the standard finders before anything else took place. request.SetIsInitialPublishedContent(); }
/// <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(PublishedRequest request) { if (request.HasPublishedContent == false) { 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(Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; if (redirectId > 0) { redirectUrl = request.UmbracoContext.UrlProvider.GetUrl(redirectId); } else { // might be a UDI instead of an int Id var redirectUdi = request.PublishedContent.Value <GuidUdi>(Constants.Conventions.Content.Redirect); if (redirectUdi != null) { redirectUrl = request.UmbracoContext.UrlProvider.GetUrl(redirectUdi.Guid); } } if (redirectUrl != "#") { request.SetRedirect(redirectUrl); } }
/// <summary> /// Looks for wildcard domains in the path and updates <c>Culture</c> accordingly. /// </summary> internal void HandleWildcardDomains(PublishedRequest request) { const string tracePrefix = "HandleWildcardDomains: "; if (request.HasPublishedContent == false) { return; } var nodePath = request.PublishedContent.Path; _logger.Debug <PublishedRouter>("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null; var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); // always has a contentId and a culture if (domain != null) { request.Culture = domain.Culture; _logger.Debug <PublishedRouter>("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name); } else { _logger.Debug <PublishedRouter>("{TracePrefix}No match.", tracePrefix); } }
/// <inheritdoc /> public bool TryRouteRequest(PublishedRequest request) { // disabled - is it going to change the routing? //_pcr.OnPreparing(); FindDomain(request); if (request.IsRedirect) { return(false); } if (request.HasPublishedContent) { return(true); } FindPublishedContent(request); if (request.IsRedirect) { return(false); } // don't handle anything - we just want to ensure that we find the content //HandlePublishedContent(); //FindTemplate(); //FollowExternalRedirect(); //HandleWildcardDomains(); // disabled - we just want to ensure that we find the content //_pcr.OnPrepared(); return(request.HasPublishedContent); }
/// <summary> /// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly. /// </summary> /// <returns>A value indicating whether a document and template were found.</returns> private void FindPublishedContentAndTemplate(PublishedRequest request) { _logger.Debug <PublishedRouter>("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath); // 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) { return; } // not handling umbracoRedirect here but after LookupDocument2 // so internal redirect, 404, etc has precedence over redirect // handle not-found, redirects, access... HandlePublishedContent(request); // find a template FindTemplate(request); // handle umbracoRedirect FollowExternalRedirect(request); }
/// <inheritdoc /> public bool PrepareRequest(PublishedRequest request) { // note - at that point the original legacy module did something do handle IIS custom 404 errors // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support // "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk. // // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors // so that they point to a non-existing page eg /redirect-404.aspx // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means. // 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 FindDomain(request); // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting if (request.IsRedirect) { return(false); } // set the culture on the thread - once, so it's set when running document lookups Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; SetVariationContext(request.Culture.Name); //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. if (request.PublishedContent == null) { // find the document & template FindPublishedContentAndTemplate(request); } // 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; 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 //complete the PCR and assign the remaining values return(ConfigureRequest(request)); }
/// <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(PublishedRequest frequest) { IPublishedContent node = null; var path = frequest.Uri.GetAbsolutePathDecoded(); if (frequest.HasDomain) { path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path); } // no template if "/" if (path == "/") { Logger.Debug <ContentFinderByUrlAndTemplate>("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); var template = _fileService.GetTemplate(templateAlias); if (template == null) { Logger.Debug <ContentFinderByUrlAndTemplate, string>("Not a valid template: '{TemplateAlias}'", templateAlias); return(false); } Logger.Debug <ContentFinderByUrlAndTemplate, string>("Valid template: '{TemplateAlias}'", templateAlias); // look for node corresponding to the rest of the route var route = frequest.HasDomain ? (frequest.Domain.ContentId + path) : path; node = FindContent(frequest, route); // also assigns to published request if (node == null) { Logger.Debug <ContentFinderByUrlAndTemplate, string>("Not a valid route to node: '{Route}'", route); return(false); } // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings if (!node.IsAllowedTemplate(template.Id)) { Logger.Warn <ContentFinderByUrlAndTemplate, string, int>("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); frequest.PublishedContent = null; // clear return(false); } // got it frequest.TemplateModel = template; return(true); }
/// <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(PublishedRequest 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.Debug <PublishedRouter>("HandlePublishedContent: Loop {LoopCounter}", i); // handle not found if (request.HasPublishedContent == false) { request.Is404 = true; _logger.Debug <PublishedRouter>("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.Debug <PublishedRouter>("HandlePublishedContent: Failed to find a document, give up"); break; } _logger.Debug <PublishedRouter>("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) { } if (j == maxLoop) // we're running out of control { break; } // ensure access if (request.HasPublishedContent) { EnsurePublishedContentAccess(request); } // 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.HasPublishedContent == false && i++ < maxLoop); if (i == maxLoop || j == maxLoop) { _logger.Debug <PublishedRouter>("HandlePublishedContent: Looks like we are running into an infinite loop, abort"); request.PublishedContent = null; } _logger.Debug <PublishedRouter>("HandlePublishedContent: End"); }
/// <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(PublishedRequest frequest) { if (frequest.UmbracoContext != null && frequest.UmbracoContext.InPreviewMode == false && _webRoutingSection.DisableFindContentByIdPath) { return(false); } IPublishedContent node = null; var path = frequest.Uri.GetAbsolutePathDecoded(); var nodeId = -1; if (path != "/") // no id if "/" { var noSlashPath = path.Substring(1); if (int.TryParse(noSlashPath, out nodeId) == false) { nodeId = -1; } if (nodeId > 0) { _logger.Debug <ContentFinderByIdPath>("Id={NodeId}", nodeId); node = frequest.UmbracoContext.Content.GetById(nodeId); if (node != null) { //if we have a node, check if we have a culture in the query string if (frequest.UmbracoContext.HttpContext.Request.QueryString.ContainsKey("culture")) { //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.Culture = CultureInfo.GetCultureInfo(frequest.UmbracoContext.HttpContext.Request.QueryString["culture"]); } frequest.PublishedContent = node; _logger.Debug <ContentFinderByIdPath>("Found node with id={PublishedContentId}", frequest.PublishedContent.Id); } else { nodeId = -1; // trigger message below } } } if (nodeId == -1) { _logger.Debug <ContentFinderByIdPath>("Not a node id"); } return(node != null); }
/// <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(PublishedRequest 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 internalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId)?.ToString(); if (internalRedirectId == null) { // no value stored, just return, no need to log return(false); } if (int.TryParse(internalRedirectId, out var internalRedirectIdAsInt) && internalRedirectIdAsInt == request.PublishedContent.Id) { // redirect to self _logger.Debug <PublishedRouter>("FollowInternalRedirects: Redirecting to self, ignore"); return(false); } IPublishedContent internalRedirectNode = null; if (internalRedirectIdAsInt > 0) { // try and get the redirect node from a legacy integer ID internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectIdAsInt); } else if (GuidUdi.TryParse(internalRedirectId, out var internalRedirectIdAsUdi)) { // try and get the redirect node from a UDI Guid internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectIdAsUdi.Guid); } if (internalRedirectNode == null) { _logger.Debug <PublishedRouter, object>("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue()); return(false); } request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here _logger.Debug <PublishedRouter, int>("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectIdAsInt); return(true); }
/// <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(PublishedRequest request) { if (request.PublishedContent == null) { throw new InvalidOperationException("There is no PublishedContent."); } var path = request.PublishedContent.Path; var publicAccessAttempt = _services.PublicAccessService.IsProtected(path); if (publicAccessAttempt) { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Page is protected, check for access"); var membershipHelper = Current.Factory.GetInstance <MembershipHelper>(); if (membershipHelper.IsLoggedIn() == false) { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Not logged in, redirect to login page"); var loginPageId = publicAccessAttempt.Result.LoginNodeId; if (loginPageId != request.PublishedContent.Id) { request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(loginPageId); } } else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, GetRolesForLogin(membershipHelper.CurrentUserName)) == false) { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; if (errorPageId != request.PublishedContent.Id) { request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); } } else { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Current member has access"); } } else { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Page is not protected"); } }
/// <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 bool TryFindContent(PublishedRequest frequest) { var route = frequest.HasDomain ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) : frequest.Uri.GetAbsolutePathDecoded(); var redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture.Name); if (redirectUrl == null) { _logger.Debug <ContentFinderByRedirectUrl, string>("No match for route: {Route}", route); return(false); } var content = frequest.UmbracoContext.Content.GetById(redirectUrl.ContentId); var url = content == null ? "#" : content.Url(redirectUrl.Culture); if (url.StartsWith("#")) { _logger.Debug <ContentFinderByRedirectUrl, string, int>("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; _logger.Debug <ContentFinderByRedirectUrl, string, int, string>("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 frequest.Cacheability = HttpCacheability.NoCache; frequest.CacheExtensions = new List <string> { "no-store, must-revalidate" }; frequest.Headers = new Dictionary <string, string> { { "Pragma", "no-cache" }, { "Expires", "0" } }; return(true); }
/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedContentRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedContentRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> public virtual bool TryFindContent(PublishedRequest frequest) { string route; if (frequest.HasDomain) { route = frequest.Domain.ContentId + DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); } else { route = frequest.Uri.GetAbsolutePathDecoded(); } var node = FindContent(frequest, route); return(node != null); }
/// <summary> /// Updates the request when there is no template to render the content. /// </summary> /// <remarks>This is called from Mvc when there's a document to render but no template.</remarks> public void UpdateRequestOnMissingTemplate(PublishedRequest 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; } // see note in PrepareRequest() // assign the legacy page back to the docrequest // handlers like default.aspx will want it and most macros currently need it request.UmbracoPage = new page(request); // this is used by many legacy objects request.UmbracoContext.HttpContext.Items["pageElements"] = request.UmbracoPage.Elements; }
/// <summary> /// Tries to find and assign an Umbraco document to a <c>PublishedContentRequest</c>. /// </summary> /// <param name="frequest">The <c>PublishedContentRequest</c>.</param> /// <returns>A value indicating whether an Umbraco document was found and assigned.</returns> public bool TryFindContent(PublishedRequest frequest) { IPublishedContent node = null; if (frequest.Uri.AbsolutePath != "/") // no alias if "/" { node = FindContentByAlias(frequest.UmbracoContext.ContentCache, frequest.HasDomain ? frequest.Domain.ContentId : 0, frequest.Culture.Name, frequest.Uri.GetAbsolutePathDecoded()); if (node != null) { frequest.PublishedContent = node; Logger.Debug <ContentFinderByUrlAlias>("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, frequest.PublishedContent.Id); } } return(node != null); }
/// <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> public bool ConfigureRequest(PublishedRequest frequest) { if (frequest.HasPublishedContent == false) { return(false); } // set the culture on the thread -- again, 'cos it might have changed in the event handler Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture; SetVariationContext(frequest.Culture.Name); // if request has been flagged to redirect, or has no content to display, // then return - whoever called us is in charge of actually redirecting if (frequest.IsRedirect || frequest.HasPublishedContent == false) { return(false); } // we may be 404 _and_ have a content // can't go beyond that point without a PublishedContent to render // it's ok not to have a template, in order to give MVC a chance to hijack routes // note - the page() ctor below will cause the "page" to get the value of all its // "elements" ie of all the IPublishedContent property. If we use the object value, // that will trigger macro execution - which can't happen because macro execution // requires that _pcr.UmbracoPage is already initialized = catch-22. The "legacy" // pipeline did _not_ evaluate the macros, ie it is using the data value, and we // have to keep doing it because of that catch-22. // assign the legacy page back to the request // handlers like default.aspx will want it and most macros currently need it frequest.UmbracoPage = new page(frequest); // used by many legacy objects frequest.UmbracoContext.HttpContext.Items["pageElements"] = frequest.UmbracoPage.Elements; return(true); }
/// <summary> /// Tries to find an Umbraco document for a <c>PublishedContentRequest</c> and a route. /// </summary> /// <param name="docreq">The document request.</param> /// <param name="route">The route.</param> /// <returns>The document node, or null.</returns> protected IPublishedContent FindContent(PublishedRequest docreq, string route) { if (docreq == null) { throw new System.ArgumentNullException(nameof(docreq)); } Logger.Debug <ContentFinderByUrl>("Test route {Route}", route); var node = docreq.UmbracoContext.ContentCache.GetByRoute(docreq.UmbracoContext.InPreviewMode, route, culture: docreq.Culture?.Name); if (node != null) { docreq.PublishedContent = node; Logger.Debug <ContentFinderByUrl>("Got content, id={NodeId}", node.Id); } else { Logger.Debug <ContentFinderByUrl>("No match."); } return(node); }
/// <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(PublishedRequest 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, {(request.HasPublishedContent ? "a document was found" : "no document was found")}")) { //iterate but return on first one that finds it var found = _contentFinders.Any(finder => { _logger.Debug <PublishedRouter>("Finder {ContentFinderType}", finder.GetType().FullName); return(finder.TryFindContent(request)); }); } // indicate that the published content (if any) we have at the moment is the // one that was found by the standard finders before anything else took place. request.SetIsInitialPublishedContent(); }
/// <summary> /// Finds a template for the current node, if any. /// </summary> private void FindTemplate(PublishedRequest request) { // 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.TemplateModel = 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 = request.IsInitialPublishedContent || (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); var altTemplate = useAltTemplate ? request.UmbracoContext.HttpContext.Request[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) { _logger.Debug <PublishedRequest>("FindTemplate: Has a template already, and no alternate template."); return; } // TODO: When we remove the need for a database for templates, then this id should be irrelevant, // not sure how were going to do this nicely. // TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type! // if the template isn't assigned to the document type we should log a warning and return 404 var templateId = request.PublishedContent.TemplateId; request.TemplateModel = GetTemplateModel(templateId); } else { // we have an alternate template specified. lookup the template with that alias // this means the we override any template that a content lookup might have set // so /path/to/page/template1?altTemplate=template2 will use template2 // ignore if the alias does not match - just trace if (request.HasTemplate) { _logger.Debug <PublishedRouter>("FindTemplate: Has a template already, but also an alternative template."); } _logger.Debug <PublishedRouter>("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings if (request.PublishedContent.IsAllowedTemplate(altTemplate)) { // allowed, use var template = _services.FileService.GetTemplate(altTemplate); if (template != null) { request.TemplateModel = template; _logger.Debug <PublishedRouter>("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } else { _logger.Debug <PublishedRouter>("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate); } } else { _logger.Warn <PublishedRouter>("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id); // no allowed, back to default var templateId = request.PublishedContent.TemplateId; request.TemplateModel = GetTemplateModel(templateId); } } if (request.HasTemplate == false) { _logger.Debug <PublishedRouter>("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 } else { _logger.Debug <PublishedRouter>("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias); } }
/// <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(PublishedRequest request) { if (request.PublishedContent == null) { throw new InvalidOperationException("There is no PublishedContent."); } var path = request.PublishedContent.Path; var publicAccessAttempt = _services.PublicAccessService.IsProtected(path); if (publicAccessAttempt) { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Page is protected, check for access"); var membershipHelper = Current.Factory.GetInstance <MembershipHelper>(); if (membershipHelper.IsLoggedIn() == false) { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Not logged in, redirect to login page"); var loginPageId = publicAccessAttempt.Result.LoginNodeId; if (loginPageId != request.PublishedContent.Id) { request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(loginPageId); } } else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, membershipHelper.GetCurrentUserRoles()) == false) { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; if (errorPageId != request.PublishedContent.Id) { request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); } } else { if (membershipHelper.IsUmbracoMembershipProviderActive()) { // grab the current member var member = membershipHelper.GetCurrentMember(); // if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access var memberIsActive = true; if (member != null) { if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false) { memberIsActive = member.Value <bool>(Constants.Conventions.Member.IsApproved); } if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false) { memberIsActive = member.Value <bool>(Constants.Conventions.Member.IsLockedOut) == false; } } if (memberIsActive == false) { _logger.Debug <PublishedRouter>( "Current member is either unapproved or locked out, redirect to error page"); var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; if (errorPageId != request.PublishedContent.Id) { request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); } } else { _logger.Debug <PublishedRouter>("Current member has access"); } } else { _logger.Debug <PublishedRouter>("Current custom MembershipProvider member has access"); } } } else { _logger.Debug <PublishedRouter>("EnsurePublishedContentAccess: Page is not protected"); } }
/// <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(PublishedRequest 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(Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); if (internalRedirectId > 0) { // try and get the redirect node from a legacy integer ID valid = true; internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId); } else { var udiInternalRedirectId = request.PublishedContent.Value <GuidUdi>(Constants.Conventions.Content.InternalRedirectId); if (udiInternalRedirectId != null) { // try and get the redirect node from a UDI Guid valid = true; internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid); } } if (valid == false) { // bad redirect - log and display the current page (legacy behavior) _logger.Debug <PublishedRouter>("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) { _logger.Debug <PublishedRouter>("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 _logger.Debug <PublishedRouter>("FollowInternalRedirects: Redirecting to self, ignore"); } else { request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here redirect = true; _logger.Debug <PublishedRouter>("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(PublishedRequest request) { const string tracePrefix = "FindDomain: "; // note - we are not handling schemes nor ports here. _logger.Debug <PublishedRouter>("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains; var domains = domainsCache.GetAll(includeWildcards: 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 var domainDocument = request.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(domainDocument.Cultures.ContainsKey(domain.Culture.Name)); } domains = domains.Where(IsPublishedContentDomain).ToList(); var defaultCulture = domainsCache.DefaultCulture; // try to find a domain matching the current request var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); // handle domain - always has a contentId and a culture if (domainAndUri != null) { // matching an existing domain _logger.Debug <PublishedRouter>("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); request.Domain = domainAndUri; request.Culture = domainAndUri.Culture; // canonical? not implemented at the moment // if (...) // { // _pcr.RedirectUrl = "..."; // return true; // } } else { // not matching any existing domain _logger.Debug <PublishedRouter>("{TracePrefix}Matches no domain", tracePrefix); request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture); } _logger.Debug <PublishedRouter>("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name); return(request.Domain != 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> public bool TryFindContent(PublishedRequest frequest) { _logger.Debug <ContentFinderByConfigured404>("Looking for a page to handle 404."); // try to find a culture as best as we can var errorCulture = CultureInfo.CurrentUICulture; if (frequest.HasDomain) { errorCulture = frequest.Domain.Culture; } else { var route = frequest.Uri.GetAbsolutePathDecoded(); var pos = route.LastIndexOf('/'); IPublishedContent node = null; while (pos > 1) { route = route.Substring(0, pos); node = frequest.UmbracoContext.ContentCache.GetByRoute(route, culture: frequest?.Culture?.Name); if (node != null) { break; } pos = route.LastIndexOf('/'); } if (node != null) { var d = DomainHelper.FindWildcardDomainInPath(frequest.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), node.Path, null); if (d != null) { errorCulture = d.Culture; } } } var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( _contentConfigSection.Error404Collection.ToArray(), _entityService, new PublishedContentQuery(frequest.UmbracoContext.PublishedSnapshot, frequest.UmbracoContext.VariationContextAccessor), errorCulture); IPublishedContent content = null; if (error404.HasValue) { _logger.Debug <ContentFinderByConfigured404>("Got id={ErrorNodeId}.", error404.Value); content = frequest.UmbracoContext.ContentCache.GetById(error404.Value); _logger.Debug <ContentFinderByConfigured404>(content == null ? "Could not find content with that id." : "Found corresponding content."); } else { _logger.Debug <ContentFinderByConfigured404>("Got nothing."); } frequest.PublishedContent = content; frequest.Is404 = true; return(content != null); }