Files
Umbraco-CMS/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
Mole 1258962429 V15: Remove Nucache (#17166)
* Remove nucache reference from Web.Common

* Get tests building-ish

* Move ReservedFieldNamesService to the right project

* Remove IPublishedSnapshotStatus

* Added functionality to the INavigationQueryService to get root keys

* Fixed issue with navigation

* Remove IPublishedSnapshot from UmbracoContext

* Begin removing usage of IPublishedSnapshot from PublishedContentExtensions

* Fix PublishedContentExtensions.cs

* Don't use snapshots in delivery media api

* Use IPublishedMediaCache in QueryMediaApiController

* Remove more usages of IPublishedSnapshotAccessor

* Comment out tests

* Remove more usages of PublishedSnapshotAccessor

* Remove PublishedSnapshot from property

* Fixed test build

* Fix errors

* Fix some tests

* Delete NuCache 🎉

* Implement DatabaseCacheRebuilder

* Remove usage of IPublishedSnapshotService

* Remove IPublishedSnapshotService

* Remove TestPublishedSnapshotAccessor and make tests build

* Don't test Snapshot cachelevel

It's no longer supported

* Fix BlockEditorConverter

Element != Element document type

* Remember to set cachemanager

* Fix RichTextParserTests

* Implement TryGetLevel on INavigationQueryService

* Fake level and obsolete it in PublishedContent

* Remove ChildrenForAllCultures

* Hack Path property on PublishedContent

* Remove usages of IPublishedSnapshot in tests

* More ConvertersTests

* Add hybrid cache to integration tests

We can actually do this now because we no longer save files on disk

* Rename IPublishedSnapshotRebuilder to ICacheRebuilder

* Comment out tests

* V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125)

* Fix .Parent references in PublishedContentExtensions

* Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params)

* Fix references from the extension methods

* Fix dependencies in tests

* Replace IPublishedSnapshotAccessor with the content cache in tests

* Resolving more .Parent references

* Fix unit tests

* Obsolete and use extension methods

* Remove private method and use extension instead

* Moving code around

* Fix tests

* Fix more references

* Cleanup

* Fix more usages

* Resolve merge conflict

* Fix tests

* Cleanup

* Fix more tests

* Fixed unit tests

* Cleanup

* Replace last usages

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>

* Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider

* Post merge fixup

* Remo IPublishedSnapshot

* Add HasAny to IDocumentUrlService

* Fix TextBuilder

* Fix modelsbuilder tests

* Use explicit types

* Implement GetByContentType

* Support element types in PublishedContentTypeCache

* Run enlistments before publishing notifications

* Fix elements cache refreshing

* Implement GetByUdi

* Implement GetAtRoot

* Implement GetByRoute

* Reimplement GetRouteById

* Fix blocks unit tests

* Initialize domain cache on boot

* Only return routes with domains on non default lanauges

* V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159)

* Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media

* Introduce GetParent() which uses the right services

* Fix obsolete message on .Parent

* Obsolete .Children

* Fix usages of Children for ApiMediaQueryService

* Fix usage in internal

* Fix usages in views

* Fix indentation

* Fix issue with delete language

* Update nuget pacakges

* Clear elements cache when content is deleted

instead of trying to update it

* Reset publishedModelFactory

* Fixed publishing

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>
Co-authored-by: kjac <kja@umbraco.dk>
2024-10-01 15:03:02 +02:00

274 lines
9.6 KiB
C#

using System.Globalization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Routing;
/// <summary>
/// Provides urls.
/// </summary>
public class DefaultUrlProvider : IUrlProvider
{
private readonly ILocalizationService _localizationService;
private readonly ILocalizedTextService? _localizedTextService;
private readonly ILogger<DefaultUrlProvider> _logger;
private readonly ISiteDomainMapper _siteDomainMapper;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IPublishedContentCache _contentCache;
private readonly IDocumentNavigationQueryService _navigationQueryService;
private readonly UriUtility _uriUtility;
private RequestHandlerSettings _requestSettings;
public DefaultUrlProvider(
IOptionsMonitor<RequestHandlerSettings> requestSettings,
ILogger<DefaultUrlProvider> logger,
ISiteDomainMapper siteDomainMapper,
IUmbracoContextAccessor umbracoContextAccessor,
UriUtility uriUtility,
ILocalizationService localizationService,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService)
{
_requestSettings = requestSettings.CurrentValue;
_logger = logger;
_siteDomainMapper = siteDomainMapper;
_umbracoContextAccessor = umbracoContextAccessor;
_uriUtility = uriUtility;
_localizationService = localizationService;
_contentCache = contentCache;
_navigationQueryService = navigationQueryService;
requestSettings.OnChange(x => _requestSettings = x);
}
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
public DefaultUrlProvider(
IOptionsMonitor<RequestHandlerSettings> requestSettings,
ILogger<DefaultUrlProvider> logger,
ISiteDomainMapper siteDomainMapper,
IUmbracoContextAccessor umbracoContextAccessor,
UriUtility uriUtility,
ILocalizationService localizationService)
: this(
requestSettings,
logger,
siteDomainMapper,
umbracoContextAccessor,
uriUtility,
localizationService,
StaticServiceProvider.Instance.GetRequiredService<IPublishedContentCache>(),
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>())
{
}
#region GetOtherUrls
/// <summary>
/// Gets the other URLs of a published content.
/// </summary>
/// <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.Domains, _siteDomainMapper, n.Id, current, false);
// n is null at root
while (domainUris == null && n != null)
{
n = n.Parent<IPublishedContent>(_contentCache, _navigationQueryService); // move to parent node
domainUris = n == null
? null
: DomainUtilities.DomainsForNode(umbracoContext.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[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 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));
}
IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
// 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))
{
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
{
_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[pos..];
DomainAndUri? domainUri = pos == 0
? null
: DomainUtilities.DomainForNode(
umbracoContext.Domains,
_siteDomainMapper,
int.Parse(route[..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;
}
#endregion
#region Utilities
private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMode mode)
{
Uri uri;
// ignore vdir at that point, UriFromUmbraco will do it
// no domain was found
if (domainUri == null)
{
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));
}
}
// a domain was found
else
{
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);
}
private string CombinePaths(string path1, string path2)
{
var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash);
}
#endregion
}