Redirect tracking: Ensure redirects with domains are stored with the domain node id prefix (closes #20894) (#20900)
* Ensure redirects with domains are stored with the domain node id prefix. * Handle removal of self-referencing redirect when domains are used. * Use entity path to save further queries for retrieving ancestor IDs. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Applied refactoring suggested in code review. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
@@ -25,6 +26,7 @@ internal sealed class RedirectTracker : IRedirectTracker
|
||||
private readonly ILogger<RedirectTracker> _logger;
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
private readonly IPublishedContentStatusFilteringService _publishedContentStatusFilteringService;
|
||||
private readonly IDomainCache _domainCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RedirectTracker"/> class.
|
||||
@@ -36,7 +38,8 @@ internal sealed class RedirectTracker : IRedirectTracker
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
ILogger<RedirectTracker> logger,
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IPublishedContentStatusFilteringService publishedContentStatusFilteringService)
|
||||
IPublishedContentStatusFilteringService publishedContentStatusFilteringService,
|
||||
IDomainCache domainCache)
|
||||
{
|
||||
_languageService = languageService;
|
||||
_redirectUrlService = redirectUrlService;
|
||||
@@ -45,6 +48,7 @@ internal sealed class RedirectTracker : IRedirectTracker
|
||||
_logger = logger;
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
_publishedContentStatusFilteringService = publishedContentStatusFilteringService;
|
||||
_domainCache = domainCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -56,11 +60,15 @@ internal sealed class RedirectTracker : IRedirectTracker
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures)
|
||||
var defaultCultures = new Lazy<string[]>(() => entityContent.AncestorsOrSelf(_navigationQueryService, _publishedContentStatusFilteringService).FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() ?? Array.Empty<string>());
|
||||
// Get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures).
|
||||
var defaultCultures = new Lazy<string[]>(() => entityContent.AncestorsOrSelf(_navigationQueryService, _publishedContentStatusFilteringService)
|
||||
.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() ?? []);
|
||||
|
||||
// Get the domains assigned to the content item again by looking up the tree (default to 0).
|
||||
var domainRootId = new Lazy<int>(() => GetNodeIdWithAssignedDomain(entityContent));
|
||||
|
||||
// Get all language ISO codes (in case we're dealing with invariant content with variant ancestors)
|
||||
var languageIsoCodes = new Lazy<string[]>(() => _languageService.GetAllIsoCodesAsync().GetAwaiter().GetResult().ToArray());
|
||||
var languageIsoCodes = new Lazy<string[]>(() => [.. _languageService.GetAllIsoCodesAsync().GetAwaiter().GetResult()]);
|
||||
|
||||
foreach (IPublishedContent publishedContent in entityContent.DescendantsOrSelf(_navigationQueryService, _publishedContentStatusFilteringService))
|
||||
{
|
||||
@@ -71,21 +79,20 @@ internal sealed class RedirectTracker : IRedirectTracker
|
||||
{
|
||||
try
|
||||
{
|
||||
var route = _publishedUrlProvider.GetUrl(publishedContent.Id, UrlMode.Relative, culture).TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
|
||||
var route = _publishedUrlProvider.GetUrl(publishedContent.Key, UrlMode.Relative, culture).TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
if (IsValidRoute(route))
|
||||
{
|
||||
oldRoutes[(publishedContent.Id, culture)] = (publishedContent.Key, route);
|
||||
StoreRoute(oldRoutes, publishedContent, culture, route, domainRootId.Value);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
// Retry using all languages, if this is invariant but has a variant ancestor.
|
||||
foreach (string languageIsoCode in languageIsoCodes.Value)
|
||||
{
|
||||
route = _publishedUrlProvider.GetUrl(publishedContent.Id, UrlMode.Relative, languageIsoCode).TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
route = GetUrl(publishedContent.Key, languageIsoCode);
|
||||
if (IsValidRoute(route))
|
||||
{
|
||||
oldRoutes[(publishedContent.Id, languageIsoCode)] = (publishedContent.Key, route);
|
||||
StoreRoute(oldRoutes, publishedContent, languageIsoCode, route, domainRootId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +105,35 @@ internal sealed class RedirectTracker : IRedirectTracker
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetNodeIdWithAssignedDomain(IPublishedContent entityContent, out int domainRootId)
|
||||
{
|
||||
domainRootId = GetNodeIdWithAssignedDomain(entityContent);
|
||||
return domainRootId > 0;
|
||||
}
|
||||
|
||||
private int GetNodeIdWithAssignedDomain(IPublishedContent entityContent) =>
|
||||
entityContent.Path.Split(',').Select(int.Parse).Reverse()
|
||||
.FirstOrDefault(x => _domainCache.HasAssigned(x, includeWildcards: true));
|
||||
|
||||
private string GetUrl(Guid contentKey, string languageIsoCode) =>
|
||||
_publishedUrlProvider.GetUrl(contentKey, UrlMode.Relative, languageIsoCode).TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
|
||||
private static void StoreRoute(
|
||||
Dictionary<(int ContentId, string Culture), (Guid ContentKey, string OldRoute)> oldRoutes,
|
||||
IPublishedContent publishedContent,
|
||||
string culture,
|
||||
string route,
|
||||
int domainRootId)
|
||||
{
|
||||
// Prepend the Id of the node with the associated domain to the route if there is one assigned.
|
||||
if (domainRootId > 0)
|
||||
{
|
||||
route = domainRootId + route;
|
||||
}
|
||||
|
||||
oldRoutes[(publishedContent.Id, culture)] = (publishedContent.Key, route);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CreateRedirects(IDictionary<(int ContentId, string Culture), (Guid ContentKey, string OldRoute)> oldRoutes)
|
||||
{
|
||||
@@ -108,9 +144,21 @@ internal sealed class RedirectTracker : IRedirectTracker
|
||||
|
||||
foreach (((int contentId, string culture), (Guid contentKey, string oldRoute)) in oldRoutes)
|
||||
{
|
||||
IPublishedContent? entityContent = _contentCache.GetById(contentKey);
|
||||
if (entityContent is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var newRoute = _publishedUrlProvider.GetUrl(contentKey, UrlMode.Relative, culture).TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
var newRoute = GetUrl(contentKey, culture);
|
||||
|
||||
// Prepend the Id of the node with the associated domain to the route if there is one assigned.
|
||||
if (TryGetNodeIdWithAssignedDomain(entityContent, out int domainRootId))
|
||||
{
|
||||
newRoute = domainRootId + newRoute;
|
||||
}
|
||||
|
||||
if (!IsValidRoute(newRoute) || oldRoute == newRoute)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user