diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index 7fe5017948..0f8296c919 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -1,6 +1,10 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services.Navigation; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; @@ -27,7 +31,49 @@ namespace Umbraco.Cms.Core.Routing /// one document per culture), and domains, withing the context of a current Uri, assign /// a culture to that document. /// - public static string? GetCultureFromDomains(int contentId, string contentPath, Uri? current, IUmbracoContext umbracoContext, ISiteDomainMapper siteDomainMapper) + [Obsolete("Please use the method taking all parameters. This overload will be removed in V17.")] + public static string? GetCultureFromDomains( + int contentId, + string contentPath, + Uri? current, + IUmbracoContext umbracoContext, + ISiteDomainMapper siteDomainMapper) + => GetCultureFromDomains( + contentId, + contentPath, + current, + umbracoContext, + siteDomainMapper, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()); + + /// + /// Gets the culture assigned to a document by domains, in the context of a current Uri. + /// + /// The document identifier. + /// The document path. + /// An optional current Uri. + /// An Umbraco context. + /// The site domain helper. + /// The domain cache. + /// The published content cache. + /// The navigation query service. + /// The culture assigned to the document by domains. + /// + /// In 1:1 multilingual setup, a document contains several cultures (there is not + /// one document per culture), and domains, withing the context of a current Uri, assign + /// a culture to that document. + /// + public static string? GetCultureFromDomains( + int contentId, + string contentPath, + Uri? current, + IUmbracoContext umbracoContext, + ISiteDomainMapper siteDomainMapper, + IDomainCache domainCache, + IPublishedCache publishedCache, + INavigationQueryService navigationQueryService) { if (umbracoContext == null) { @@ -39,20 +85,11 @@ namespace Umbraco.Cms.Core.Routing current = umbracoContext.CleanedUmbracoUrl; } - // get the published route, else the preview route - // if both are null then the content does not exist - var route = umbracoContext.Content?.GetRouteById(contentId) ?? - umbracoContext.Content?.GetRouteById(true, contentId); + var domainNodeId = GetAncestorNodeWithDomainsAssigned(contentId, umbracoContext, domainCache, publishedCache, navigationQueryService); - if (route == null) - { - return null; - } - - var pos = route.IndexOf('/'); - DomainAndUri? domain = pos == 0 - ? null - : DomainForNode(umbracoContext.Domains, siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current); + DomainAndUri? domain = domainNodeId.HasValue + ? DomainForNode(umbracoContext.Domains, siteDomainMapper, domainNodeId.Value, current) + : null; var rootContentId = domain?.ContentId ?? -1; Domain? wcDomain = FindWildcardDomainInPath(umbracoContext.Domains?.GetAll(true), contentPath, rootContentId); @@ -70,6 +107,22 @@ namespace Umbraco.Cms.Core.Routing return umbracoContext.Domains?.DefaultCulture; } + private static int? GetAncestorNodeWithDomainsAssigned(int contentId, IUmbracoContext umbracoContext, IDomainCache domainCache, IPublishedCache publishedCache, INavigationQueryService navigationQueryService) + { + IPublishedContent? content = umbracoContext.Content.GetById(contentId); + var hasDomains = ContentHasAssignedDomains(content, domainCache); + while (content is not null && !hasDomains) + { + content = content.Parent(publishedCache, navigationQueryService); + hasDomains = content is not null && domainCache.HasAssigned(content.Id, true); + } + + return content?.Id; + } + + private static bool ContentHasAssignedDomains(IPublishedContent? content, IDomainCache domainCache) + => content is not null && domainCache.HasAssigned(content.Id, true); + #endregion #region Domain for Document diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs index c4c839f695..38ba31245b 100644 --- a/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs @@ -19,6 +19,9 @@ public static class FriendlyPublishedContentExtensions private static IVariationContextAccessor VariationContextAccessor { get; } = StaticServiceProvider.Instance.GetRequiredService(); + private static IDomainCache DomainCache { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + private static IPublishedContentCache PublishedContentCache { get; } = StaticServiceProvider.Instance.GetRequiredService(); @@ -731,7 +734,7 @@ public static class FriendlyPublishedContentExtensions public static string? GetCultureFromDomains( this IPublishedContent content, Uri? current = null) - => content.GetCultureFromDomains(UmbracoContextAccessor, SiteDomainHelper, current); + => content.GetCultureFromDomains(UmbracoContextAccessor, SiteDomainHelper, DomainCache, PublishedContentCache, DocumentNavigationQueryService, current); public static IEnumerable SearchDescendants( this IPublishedContent content, diff --git a/src/Umbraco.Web.Common/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Web.Common/Extensions/PublishedContentExtensions.cs index 8e5efa079c..d9ca747ba3 100644 --- a/src/Umbraco.Web.Common/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/PublishedContentExtensions.cs @@ -4,8 +4,10 @@ using Examine.Search; using Microsoft.AspNetCore.Html; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.Navigation; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.Examine; @@ -20,7 +22,36 @@ public static class PublishedContentExtensions /// /// The document. /// - /// + /// The site domain helper. + /// An optional current Uri. + /// The culture assigned to the document by domains. + /// + /// + /// In 1:1 multilingual setup, a document contains several cultures (there is not + /// one document per culture), and domains, withing the context of a current Uri, assign + /// a culture to that document. + /// + /// + [Obsolete("Please use the method taking all parameters. This overload will be removed in V17.")] + public static string? GetCultureFromDomains( + this IPublishedContent content, + IUmbracoContextAccessor umbracoContextAccessor, + ISiteDomainMapper siteDomainHelper, + Uri? current = null) + { + IUmbracoContext umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + return DomainUtilities.GetCultureFromDomains(content.Id, content.Path, current, umbracoContext, siteDomainHelper); + } + + /// + /// Gets the culture assigned to a document by domains, in the context of a current Uri. + /// + /// The document. + /// + /// The site domain helper. + /// The domain cache. + /// The published content cache. + /// The navigation query service. /// An optional current Uri. /// The culture assigned to the document by domains. /// @@ -34,10 +65,13 @@ public static class PublishedContentExtensions this IPublishedContent content, IUmbracoContextAccessor umbracoContextAccessor, ISiteDomainMapper siteDomainHelper, + IDomainCache domainCache, + IPublishedCache publishedCache, + INavigationQueryService navigationQueryService, Uri? current = null) { IUmbracoContext umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); - return DomainUtilities.GetCultureFromDomains(content.Id, content.Path, current, umbracoContext, siteDomainHelper); + return DomainUtilities.GetCultureFromDomains(content.Id, content.Path, current, umbracoContext, siteDomainHelper, domainCache, publishedCache, navigationQueryService); } #endregion