internal UrlInfo GetUrlFromRoute( string route, IUmbracoContext umbracoContext, int id, Uri current, UrlMode mode, string culture) { if (string.IsNullOrWhiteSpace(route)) { _logger.LogDebug( "Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", id); return(null); } // extract domainUri and path // route is /<path> or <domainRootId>/<path> var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); DomainAndUri domainUri = pos == 0 ? null : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); if (domainUri is not null || string.IsNullOrEmpty(culture) || culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase)) { var url = AssembleUrl(domainUri, path, current, mode).ToString(); return(UrlInfo.Url(url, culture)); } return(null); }
/// <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> /// <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); }
/// <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> /// Initializes a new instance of the <see cref="DomainAndUri"/> class. /// </summary> /// <param name="domain">The original domain.</param> /// <param name="currentUri">The context current Uri.</param> public DomainAndUri(Domain domain, Uri currentUri) : base(domain) { try { Uri = DomainUtilities.ParseUriFromDomainName(Name, currentUri); } catch (UriFormatException) { throw new ArgumentException($"Failed to parse invalid domain: node id={domain.ContentId}, hostname=\"{Name.ToCSharpString()}\"." + " Hostname should be a valid uri.", nameof(domain)); } }
/// <summary> /// Gets the other URLs of a published content. /// </summary> /// <param name="umbracoContextAccessor">The Umbraco context.</param> /// <param name="id">The published content id.</param> /// <param name="current">The current absolute URL.</param> /// <returns>The other URLs for the published content.</returns> /// <remarks> /// <para> /// Other URLs are those that <c>GetUrl</c> would not return in the current context, but would be valid /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// </para> /// </remarks> public virtual IEnumerable <UrlInfo> GetOtherUrls(int id, Uri current) { IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); IPublishedContent node = umbracoContext.Content.GetById(id); if (node == null) { yield break; } // look for domains, walking up the tree IPublishedContent n = node; IEnumerable <DomainAndUri> domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); while (domainUris == null && n != null) // n is null at root { n = n.Parent; // move to parent node domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current); } // no domains = exit if (domainUris == null) { yield break; } foreach (DomainAndUri d in domainUris) { var culture = d?.Culture; // although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok var route = umbracoContext.Content.GetRouteById(id, culture); if (route == null) { continue; } // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); uri = _uriUtility.UriFromUmbraco(uri, _requestSettings); yield return(UrlInfo.Url(uri.ToString(), culture)); } }
internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current, UrlMode mode, string culture) { if (string.IsNullOrWhiteSpace(route)) { _logger.LogDebug("Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", id); return(null); } // extract domainUri and path // route is /<path> or <domainRootId>/<path> var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); var domainUri = pos == 0 ? null : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); // assemble the URL from domainUri (maybe null) and path var url = AssembleUrl(domainUri, path, current, mode).ToString(); return(UrlInfo.Url(url, culture)); }
/// <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 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 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(Microsoft.Extensions.Logging.LogLevel.Debug)) { _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); } var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); IDomainCache?domainsCache = 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 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(Microsoft.Extensions.Logging.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(Microsoft.Extensions.Logging.LogLevel.Debug)) { _logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix); } request.SetCulture(defaultCulture ?? CultureInfo.CurrentUICulture.Name); } if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture); } return(request.Domain != null); }
/// <summary> /// Gets the other URLs of a published content. /// </summary> /// <param name="umbracoContext">The Umbraco context.</param> /// <param name="id">The published content id.</param> /// <param name="current">The current absolute URL.</param> /// <returns>The other URLs for the published content.</returns> /// <remarks> /// <para>Other URLs are those that <c>GetUrl</c> would not return in the current context, but would be valid /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...).</para> /// </remarks> public IEnumerable <UrlInfo> GetOtherUrls(int id, Uri current) { var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); var node = umbracoContext.Content.GetById(id); if (node == null) { yield break; } if (!node.HasProperty(Constants.Conventions.Content.UrlAlias)) { yield break; } // look for domains, walking up the tree var n = node; var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); while (domainUris == null && n != null) // n is null at root { // move to parent node n = n.Parent; domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, excludeDefault: false); } // determine whether the alias property varies var varies = node.GetProperty(Constants.Conventions.Content.UrlAlias).PropertyType.VariesByCulture(); if (domainUris == null) { // no domain // if the property is invariant, then URL "/<alias>" is ok // if the property varies, then what are we supposed to do? // the content finder may work, depending on the 'current' culture, // but there's no way we can return something meaningful here if (varies) { yield break; } var umbracoUrlName = node.Value <string>(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (aliases == null || aliases.Any() == false) { yield break; } foreach (var alias in aliases.Distinct()) { var path = "/" + alias; var uri = new Uri(path, UriKind.Relative); yield return(UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString())); } } else { // some domains: one URL per domain, which is "<domain>/<alias>" foreach (var domainUri in domainUris) { // if the property is invariant, get the invariant value, URL is "<domain>/<invariant-alias>" // if the property varies, get the variant value, URL is "<domain>/<variant-alias>" // but! only if the culture is published, else ignore if (varies && !node.HasCulture(domainUri.Culture)) { continue; } var umbracoUrlName = varies ? node.Value <string>(_publishedValueFallback, Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture) : node.Value <string>(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (aliases == null || aliases.Any() == false) { continue; } foreach (var alias in aliases.Distinct()) { var path = "/" + alias; var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); yield return(UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), domainUri.Culture)); } } } }