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); }
/// <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> /// 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); }
public async Task RenderAsync(int pageId, int?altTemplateId, StringWriter writer) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); // instantiate a request and process // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL, though this isn't going to matter // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. IPublishedRequestBuilder requestBuilder = await _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); IPublishedContent?doc = umbracoContext.Content?.GetById(pageId); if (doc == null) { writer.Write("<!-- Could not render template for Id {0}, the document was not found -->", pageId); return; } // in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the // execution of a front-end rendered page. In this case set the culture to the default. // set the culture to the same as is currently rendering if (umbracoContext.PublishedRequest == null) { ILanguage?defaultLanguage = _languageService.GetAllLanguages().FirstOrDefault(); requestBuilder.SetCulture(defaultLanguage == null ? CultureInfo.CurrentUICulture.Name : defaultLanguage.IsoCode); } else { requestBuilder.SetCulture(umbracoContext.PublishedRequest.Culture); } // set the doc that was found by id requestBuilder.SetPublishedContent(doc); // set the template, either based on the AltTemplate found or the standard template of the doc var templateId = _webRoutingSettings.DisableAlternativeTemplates || !altTemplateId.HasValue ? doc.TemplateId : altTemplateId.Value; if (templateId.HasValue) { requestBuilder.SetTemplate(_fileService.GetTemplate(templateId.Value)); } // if there is not template then exit if (requestBuilder.HasTemplate() == false) { if (altTemplateId.HasValue == false) { writer.Write( "<!-- Could not render template for Id {0}, the document's template was not found with id {0}-->", doc.TemplateId); } else { writer.Write( "<!-- Could not render template for Id {0}, the altTemplate was not found with id {0}-->", altTemplateId); } return; } // First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these // after this page has rendered. SaveExistingItems(out IPublishedRequest? oldPublishedRequest); IPublishedRequest contentRequest = requestBuilder.Build(); try { // set the new items on context objects for this templates execution SetNewItemsOnContextObjects(contentRequest); // Render the template ExecuteTemplateRendering(writer, contentRequest); } finally { // restore items on context objects to continuing rendering the parent template RestoreItems(oldPublishedRequest); } }