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

189 lines
7.5 KiB
C#

using System;
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides urls.
/// </summary>
public class DefaultUrlProvider : IUrlProvider
{
private readonly RequestHandlerSettings _requestSettings;
private readonly ILogger<DefaultUrlProvider> _logger;
private readonly ISiteDomainHelper _siteDomainHelper;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly UriUtility _uriUtility;
public DefaultUrlProvider(IOptions<RequestHandlerSettings> requestSettings, ILogger<DefaultUrlProvider> logger, ISiteDomainHelper siteDomainHelper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility)
{
_requestSettings = requestSettings.Value;
_logger = logger;
_siteDomainHelper = siteDomainHelper;
_uriUtility = uriUtility;
_umbracoContextAccessor = umbracoContextAccessor;
}
#region GetUrl
/// <inheritdoc />
public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string culture, Uri current)
{
if (!current.IsAbsoluteUri) throw new ArgumentException("Current URL must be absolute.", nameof(current));
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
// will not use cache if previewing
var route = umbracoContext.Content.GetRouteById(content.Id, culture);
return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, 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, _siteDomainHelper, int.Parse(route.Substring(0, pos)), current, culture);
// assemble the URL from domainUri (maybe null) and path
var url = AssembleUrl(domainUri, path, current, mode).ToString();
return UrlInfo.Url(url, culture);
}
#endregion
#region GetOtherUrls
/// <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)
{
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
var node = umbracoContext.Content.GetById(id);
if (node == null)
{
yield break;
}
// look for domains, walking up the tree
var n = node;
var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, 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, _siteDomainHelper, n.Id, current, excludeDefault: true);
}
// no domains = exit
if (domainUris ==null)
{
yield break;
}
foreach (var 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);
}
}
#endregion
#region Utilities
Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
{
Uri uri;
// ignore vdir at that point, UriFromUmbraco will do it
if (domainUri == null) // no domain was found
{
if (current == null)
mode = UrlMode.Relative; // best we can do
switch (mode)
{
case UrlMode.Absolute:
uri = new Uri(current.GetLeftPart(UriPartial.Authority) + path);
break;
case UrlMode.Relative:
case UrlMode.Auto:
uri = new Uri(path, UriKind.Relative);
break;
default:
throw new ArgumentOutOfRangeException(nameof(mode));
}
}
else // a domain was found
{
if (mode == UrlMode.Auto)
{
//this check is a little tricky, we can't just compare domains
if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
mode = UrlMode.Relative;
else
mode = UrlMode.Absolute;
}
switch (mode)
{
case UrlMode.Absolute:
uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path));
break;
case UrlMode.Relative:
uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative);
break;
default:
throw new ArgumentOutOfRangeException(nameof(mode));
}
}
// UriFromUmbraco will handle vdir
// meaning it will add vdir into domain URLs too!
return _uriUtility.UriFromUmbraco(uri, _requestSettings);
}
string CombinePaths(string path1, string path2)
{
string path = path1.TrimEnd('/') + path2;
return path == "/" ? path : path.TrimEnd('/');
}
#endregion
}
}