Skip to content

Commit 4078e83

Browse files
authored
Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined (#18763)
* Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined. * Added tests to verify functionality. * Added reference to previous PR. * Referenced second PR. * Assemble URLs for all cultures, not just the default. * Revert previous update. * Display an original URL if we have one.
1 parent 1f4c19d commit 4078e83

File tree

4 files changed

+195
-9
lines changed

4 files changed

+195
-9
lines changed

src/Umbraco.Cms.Api.Management/Factories/RedirectUrlPresentationFactory.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Umbraco.Cms.Api.Management.ViewModels;
1+
using Umbraco.Cms.Api.Management.ViewModels;
22
using Umbraco.Cms.Api.Management.ViewModels.RedirectUrlManagement;
33
using Umbraco.Cms.Core.Models;
44
using Umbraco.Cms.Core.Routing;
@@ -22,6 +22,12 @@ public RedirectUrlResponseModel Create(IRedirectUrl source)
2222

2323
var originalUrl = _publishedUrlProvider.GetUrlFromRoute(source.ContentId, source.Url, source.Culture);
2424

25+
// Even if the URL could not be extracted from the route, if we have a path as a the route for the original URL, we should display it.
26+
if (originalUrl == "#" && source.Url.StartsWith('/'))
27+
{
28+
originalUrl = source.Url;
29+
}
30+
2531
return new RedirectUrlResponseModel
2632
{
2733
OriginalUrl = originalUrl,

src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,38 @@ public async Task<bool> TryFindContent(IPublishedRequestBuilder frequest)
5353
return false;
5454
}
5555

56-
var route = frequest.Domain != null
57-
? frequest.Domain.ContentId +
58-
DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded)
59-
: frequest.AbsolutePathDecoded;
60-
56+
var route = frequest.AbsolutePathDecoded;
6157
IRedirectUrl? redirectUrl = await _redirectUrlService.GetMostRecentRedirectUrlAsync(route, frequest.Culture);
6258

63-
if (redirectUrl == null)
59+
if (redirectUrl is null)
6460
{
6561
if (_logger.IsEnabled(LogLevel.Debug))
6662
{
6763
_logger.LogDebug("No match for route: {Route}", route);
6864
}
6965

70-
return false;
66+
// Routes under domains can be stored with the integer ID of the content where the domains were defined as the first part of the route,
67+
// so if we haven't found a redirect, try using that format too.
68+
// See: https://github.com/umbraco/Umbraco-CMS/pull/18160 and https://github.com/umbraco/Umbraco-CMS/pull/18763
69+
if (frequest.Domain is not null)
70+
{
71+
route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded);
72+
redirectUrl = await _redirectUrlService.GetMostRecentRedirectUrlAsync(route, frequest.Culture);
73+
74+
if (redirectUrl is null)
75+
{
76+
if (_logger.IsEnabled(LogLevel.Debug))
77+
{
78+
_logger.LogDebug("No match for route with domain: {Route}", route);
79+
}
80+
81+
return false;
82+
}
83+
}
84+
else
85+
{
86+
return false;
87+
}
7188
}
7289

7390
IPublishedContent? content = umbracoContext.Content?.GetById(redirectUrl.ContentId);

src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ private string GetLegacyRouteFormatById(Guid key, string? culture)
249249
culture);
250250

251251
var defaultCulture = _localizationService.GetDefaultLanguageIsoCode();
252-
if (domainUri is not null || string.IsNullOrEmpty(culture) ||
252+
if (domainUri is not null ||
253+
string.IsNullOrEmpty(culture) ||
253254
culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
254255
{
255256
var url = AssembleUrl(domainUri, path, current, mode).ToString();
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
using System.Net;
2+
using Microsoft.Extensions.Logging.Abstractions;
3+
using Moq;
4+
using NUnit.Framework;
5+
using Umbraco.Cms.Core.Models;
6+
using Umbraco.Cms.Core.Models.PublishedContent;
7+
using Umbraco.Cms.Core.Routing;
8+
using Umbraco.Cms.Core.Services;
9+
using Umbraco.Cms.Core.Web;
10+
11+
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing;
12+
13+
[TestFixture]
14+
public class ContentFinderByRedirectUrlTests
15+
{
16+
private const int DomainContentId = 1233;
17+
private const int ContentId = 1234;
18+
19+
[Test]
20+
public async Task Can_Find_Invariant_Content()
21+
{
22+
const string OldPath = "/old-page-path";
23+
const string NewPath = "/new-page-path";
24+
25+
var mockRedirectUrlService = CreateMockRedirectUrlService(OldPath);
26+
27+
var mockContent = CreateMockPublishedContent();
28+
29+
var mockUmbracoContextAccessor = CreateMockUmbracoContextAccessor(mockContent);
30+
31+
var mockPublishedUrlProvider = CreateMockPublishedUrlProvider(NewPath);
32+
33+
var sut = CreateContentFinder(mockRedirectUrlService, mockUmbracoContextAccessor, mockPublishedUrlProvider);
34+
35+
var publishedRequestBuilder = CreatePublishedRequestBuilder(OldPath);
36+
37+
var result = await sut.TryFindContent(publishedRequestBuilder);
38+
39+
AssertRedirectResult(publishedRequestBuilder, result);
40+
}
41+
42+
[Test]
43+
public async Task Can_Find_Variant_Content_With_Path_Root()
44+
{
45+
const string OldPath = "/en/old-page-path";
46+
const string NewPath = "/en/new-page-path";
47+
48+
var mockRedirectUrlService = CreateMockRedirectUrlService(OldPath);
49+
50+
var mockContent = CreateMockPublishedContent();
51+
52+
var mockUmbracoContextAccessor = CreateMockUmbracoContextAccessor(mockContent);
53+
54+
var mockPublishedUrlProvider = CreateMockPublishedUrlProvider(NewPath);
55+
56+
var sut = CreateContentFinder(mockRedirectUrlService, mockUmbracoContextAccessor, mockPublishedUrlProvider);
57+
58+
var publishedRequestBuilder = CreatePublishedRequestBuilder(OldPath, withDomain: true);
59+
60+
var result = await sut.TryFindContent(publishedRequestBuilder);
61+
62+
AssertRedirectResult(publishedRequestBuilder, result);
63+
}
64+
65+
[Test]
66+
public async Task Can_Find_Variant_Content_With_Domain_Node_Id_Prefixed_Path()
67+
{
68+
const string OldPath = "/en/old-page-path";
69+
var domainPrefixedOldPath = $"{DomainContentId}/old-page-path";
70+
const string NewPath = "/en/new-page-path";
71+
72+
var mockRedirectUrlService = CreateMockRedirectUrlService(domainPrefixedOldPath);
73+
74+
var mockContent = CreateMockPublishedContent();
75+
76+
var mockUmbracoContextAccessor = CreateMockUmbracoContextAccessor(mockContent);
77+
78+
var mockPublishedUrlProvider = CreateMockPublishedUrlProvider(NewPath);
79+
80+
var sut = CreateContentFinder(mockRedirectUrlService, mockUmbracoContextAccessor, mockPublishedUrlProvider);
81+
82+
var publishedRequestBuilder = CreatePublishedRequestBuilder(OldPath, withDomain: true);
83+
84+
var result = await sut.TryFindContent(publishedRequestBuilder);
85+
86+
AssertRedirectResult(publishedRequestBuilder, result);
87+
}
88+
89+
private static Mock<IRedirectUrlService> CreateMockRedirectUrlService(string oldPath)
90+
{
91+
var mockRedirectUrlService = new Mock<IRedirectUrlService>();
92+
mockRedirectUrlService
93+
.Setup(x => x.GetMostRecentRedirectUrlAsync(It.Is<string>(y => y == oldPath), It.IsAny<string>()))
94+
.ReturnsAsync(new RedirectUrl
95+
{
96+
ContentId = ContentId,
97+
});
98+
return mockRedirectUrlService;
99+
}
100+
101+
private static Mock<IPublishedUrlProvider> CreateMockPublishedUrlProvider(string newPath)
102+
{
103+
var mockPublishedUrlProvider = new Mock<IPublishedUrlProvider>();
104+
mockPublishedUrlProvider
105+
.Setup(x => x.GetUrl(It.Is<IPublishedContent>(y => y.Id == ContentId), It.IsAny<UrlMode>(), It.IsAny<string?>(), It.IsAny<Uri?>()))
106+
.Returns(newPath);
107+
return mockPublishedUrlProvider;
108+
}
109+
110+
private static Mock<IPublishedContent> CreateMockPublishedContent()
111+
{
112+
var mockContent = new Mock<IPublishedContent>();
113+
mockContent
114+
.SetupGet(x => x.Id)
115+
.Returns(ContentId);
116+
mockContent
117+
.SetupGet(x => x.ContentType.ItemType)
118+
.Returns(PublishedItemType.Content);
119+
return mockContent;
120+
}
121+
122+
private static Mock<IUmbracoContextAccessor> CreateMockUmbracoContextAccessor(Mock<IPublishedContent> mockContent)
123+
{
124+
var mockUmbracoContext = new Mock<IUmbracoContext>();
125+
mockUmbracoContext
126+
.Setup(x => x.Content.GetById(It.Is<int>(y => y == ContentId)))
127+
.Returns(mockContent.Object);
128+
var mockUmbracoContextAccessor = new Mock<IUmbracoContextAccessor>();
129+
var umbracoContext = mockUmbracoContext.Object;
130+
mockUmbracoContextAccessor
131+
.Setup(x => x.TryGetUmbracoContext(out umbracoContext))
132+
.Returns(true);
133+
return mockUmbracoContextAccessor;
134+
}
135+
136+
private static ContentFinderByRedirectUrl CreateContentFinder(
137+
Mock<IRedirectUrlService> mockRedirectUrlService,
138+
Mock<IUmbracoContextAccessor> mockUmbracoContextAccessor,
139+
Mock<IPublishedUrlProvider> mockPublishedUrlProvider)
140+
=> new ContentFinderByRedirectUrl(
141+
mockRedirectUrlService.Object,
142+
new NullLogger<ContentFinderByRedirectUrl>(),
143+
mockPublishedUrlProvider.Object,
144+
mockUmbracoContextAccessor.Object);
145+
146+
private static PublishedRequestBuilder CreatePublishedRequestBuilder(string path, bool withDomain = false)
147+
{
148+
var publishedRequestBuilder = new PublishedRequestBuilder(new Uri($"https://example.com{path}"), Mock.Of<IFileService>());
149+
if (withDomain)
150+
{
151+
publishedRequestBuilder.SetDomain(new DomainAndUri(new Domain(1, "/en", DomainContentId, "en-US", false, 0), new Uri($"https://example.com{path}")));
152+
}
153+
154+
return publishedRequestBuilder;
155+
}
156+
157+
private static void AssertRedirectResult(PublishedRequestBuilder publishedRequestBuilder, bool result)
158+
{
159+
Assert.AreEqual(true, result);
160+
Assert.AreEqual(HttpStatusCode.Moved, (HttpStatusCode)publishedRequestBuilder.ResponseStatusCode);
161+
}
162+
}

0 commit comments

Comments
 (0)