From c19dbeda2320532be3ecaed1849228e6a84dbc0b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 27 Apr 2018 13:27:15 +1000 Subject: [PATCH] Adds a couple of tests which yielded some other issues with getting urls by culture, those are now fixed --- .../Published/ConvertersTests.cs | 7 +- .../Published/PublishedSnapshotTestObjects.cs | 64 +-------- src/Umbraco.Tests/Routing/UrlProviderTests.cs | 126 ++++++++++++++++-- .../TestHelpers/Stubs/TestPublishedContent.cs | 66 +++++++++ .../TestHelpers/TestWithDatabaseBase.cs | 3 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Mapping/RedirectUrlMapperProfile.cs | 10 +- .../PublishedCache/NuCache/ContentCache.cs | 4 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 13 +- src/Umbraco.Web/Routing/DomainHelper.cs | 24 ++-- src/Umbraco.Web/Routing/UrlProvider.cs | 4 +- 11 files changed, 226 insertions(+), 96 deletions(-) create mode 100644 src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 0c47a551bc..25933fdd9d 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache; @@ -99,7 +100,7 @@ namespace Umbraco.Tests.Published var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", Array.Empty()); - var cnt1 = new PublishedSnapshotTestObjects.TestPublishedContent(cntType1, 1234, Guid.NewGuid(), new Dictionary(), false); + var cnt1 = new TestPublishedContent(cntType1, 1234, Guid.NewGuid(), new Dictionary(), false); cacheContent[cnt1.Id] = cnt1; Assert.AreSame(cnt1, element1.Value("prop1")); @@ -200,8 +201,8 @@ namespace Umbraco.Tests.Published var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); var element2 = new PublishedElement(elementType2, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); - var cnt1 = new PublishedSnapshotTestObjects.TestPublishedContent(contentType1, 1003, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); - var cnt2 = new PublishedSnapshotTestObjects.TestPublishedContent(contentType2, 1004, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); + var cnt1 = new TestPublishedContent(contentType1, 1003, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); + var cnt2 = new TestPublishedContent(contentType2, 1004, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); cacheContent[cnt1.Id] = cnt1.CreateModel(); cacheContent[cnt2.Id] = cnt2.CreateModel(); diff --git a/src/Umbraco.Tests/Published/PublishedSnapshotTestObjects.cs b/src/Umbraco.Tests/Published/PublishedSnapshotTestObjects.cs index acfc12d408..fcb462e5c5 100644 --- a/src/Umbraco.Tests/Published/PublishedSnapshotTestObjects.cs +++ b/src/Umbraco.Tests/Published/PublishedSnapshotTestObjects.cs @@ -8,8 +8,6 @@ namespace Umbraco.Tests.Published { public class PublishedSnapshotTestObjects { - #region Published models - [PublishedModel("element1")] public class TestElementModel1 : PublishedElementModel { @@ -49,66 +47,6 @@ namespace Umbraco.Tests.Published public IEnumerable Prop2 => this.Value>("prop2"); } - - #endregion - - #region Support classes - - internal class TestPublishedContent : PublishedElement, IPublishedContent - { - public TestPublishedContent(PublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing) - : base(contentType, key, values, previewing) - { - Id = id; - } - - public int Id { get; } - public int TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public IReadOnlyDictionary CultureNames => throw new NotSupportedException(); - public string UrlName { get; set; } - public string DocumentTypeAlias => ContentType.Alias; - public int DocumentTypeId { get; set; } - public string WriterName { get; set; } - public string CreatorName { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public string Url { get; set; } - public PublishedItemType ItemType => ContentType.ItemType; - public bool IsDraft { get; set; } - public IPublishedContent Parent { get; set; } - public IEnumerable Children { get; set; } - - // copied from PublishedContentBase - public IPublishedProperty GetProperty(string alias, bool recurse) - { - var property = GetProperty(alias); - if (recurse == false) return property; - - IPublishedContent content = this; - var firstNonNullProperty = property; - while (content != null && (property == null || property.HasValue() == false)) - { - content = content.Parent; - property = content?.GetProperty(alias); - if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; - } - - // if we find a content with the property with a value, return that property - // if we find no content with the property, return null - // if we find a content with the property without a value, return that property - // have to save that first property while we look further up, hence firstNonNullProperty - - return property != null && property.HasValue() ? property : firstNonNullProperty; - } - } - - #endregion + } } diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 818e1f6817..070c68468a 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; @@ -44,11 +48,11 @@ namespace Umbraco.Tests.Routing globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); SettingsForTests.ConfigureSettings(globalSettings.Object); - var umbracoContext = GetUmbracoContext("/test", 1111, urlProviders: new [] + var umbracoContext = GetUmbracoContext("/test", 1111, urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) - }, globalSettings:globalSettings.Object); - + }, globalSettings: globalSettings.Object); + var requestHandlerMock = Mock.Get(_umbracoSettings.RequestHandler); requestHandlerMock.Setup(x => x.AddTrailingSlash).Returns(false);// (cached routes have none) @@ -110,7 +114,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/test", 1111, urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) - }, globalSettings:globalSettings.Object); + }, globalSettings: globalSettings.Object); var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); @@ -140,7 +144,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/test", 1111, urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) - }, globalSettings:globalSettings.Object); + }, globalSettings: globalSettings.Object); var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); @@ -149,14 +153,118 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(niceUrlMatch, result); } + /// + /// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is the culture specific domain + /// [Test] - public void Get_Nice_Url_Relative_Or_Absolute() + public void Get_Url_For_Culture_Variant_With_Current_Url() + { + const string currentUri = "http://example.com/fr/test"; + + var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); + globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); + SettingsForTests.ConfigureSettings(globalSettings.Object); + + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); + + var publishedContentCache = new Mock(); + publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) + .Returns("9876/home/test-fr"); //prefix with the root id node with the domain assigned as per the umbraco standard + + var domainCache = new Mock(); + domainCache.Setup(x => x.GetAssigned(It.IsAny(), false)) + .Returns((int contentId, bool includeWildcards) => + { + if (contentId != 9876) return Enumerable.Empty(); + return new[] + { + new Domain(2, "example.com/en", 9876, CultureInfo.GetCultureInfo("en-US"), false, true), //default + new Domain(3, "example.com/fr", 9876, CultureInfo.GetCultureInfo("fr-FR"), false, true) + }; + }); + + var snapshot = Mock.Of(x => x.Content == publishedContentCache.Object && x.Domains == domainCache.Object); + + var snapshotService = new Mock(); + snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) + .Returns(snapshot); + + var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: _umbracoSettings, + urlProviders: new[] { + new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + }, + globalSettings: globalSettings.Object, + snapshotService: snapshotService.Object); + + + var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + + //the current uri is the culture specific domain we want, so the result is a relative path since we are on the culture specific domain + Assert.AreEqual("/fr/home/test-fr/", url); + } + + /// + /// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is not the culture specific domain + /// + [Test] + public void Get_Url_For_Culture_Variant_Non_Current_Url() + { + const string currentUri = "http://example.com/en/test"; + + var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container + globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); + globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); + SettingsForTests.ConfigureSettings(globalSettings.Object); + + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); + + var publishedContentCache = new Mock(); + publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) + .Returns("9876/home/test-fr"); //prefix with the root id node with the domain assigned as per the umbraco standard + + var domainCache = new Mock(); + domainCache.Setup(x => x.GetAssigned(It.IsAny(), false)) + .Returns((int contentId, bool includeWildcards) => + { + if (contentId != 9876) return Enumerable.Empty(); + return new[] + { + new Domain(2, "example.com/en", 9876, CultureInfo.GetCultureInfo("en-US"), false, true), //default + new Domain(3, "example.com/fr", 9876, CultureInfo.GetCultureInfo("fr-FR"), false, true) + }; + }); + + var snapshot = Mock.Of(x => x.Content == publishedContentCache.Object && x.Domains == domainCache.Object); + + var snapshotService = new Mock(); + snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())) + .Returns(snapshot); + + var umbracoContext = GetUmbracoContext(currentUri, umbracoSettings: _umbracoSettings, + urlProviders: new[] { + new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) + }, + globalSettings: globalSettings.Object, + snapshotService: snapshotService.Object); + + + var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + + //the current uri is not the culture specific domain we want, so the result is an absolute path to the culture specific domain + Assert.AreEqual("http://example.com/fr/home/test-fr/", url); + } + + [Test] + public void Get_Url_Relative_Or_Absolute() { var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.UseDirectoryUrls).Returns(true); globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); SettingsForTests.ConfigureSettings(globalSettings.Object); - + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); @@ -164,7 +272,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("http://example.com/test", 1111, umbracoSettings: _umbracoSettings, urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) - }, globalSettings:globalSettings.Object); + }, globalSettings: globalSettings.Object); Assert.AreEqual("/home/sub1/custom-sub-1/", umbracoContext.UrlProvider.GetUrl(1177)); @@ -187,7 +295,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("http://example.com/test", 1111, urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) - }, globalSettings:globalSettings.Object); + }, globalSettings: globalSettings.Object); //mock the Umbraco settings that we need var requestMock = Mock.Get(_umbracoSettings.RequestHandler); diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs new file mode 100644 index 0000000000..7d9bbec855 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.TestHelpers.Stubs +{ + internal class TestPublishedContent : PublishedElement, IPublishedContent + { + public TestPublishedContent(PublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultureNames = null) + : base(contentType, key, values, previewing) + { + Id = id; + CultureNames = cultureNames; + } + + public int Id { get; } + public int TemplateId { get; set; } + public int SortOrder { get; set; } + public string Name { get; set; } + public IReadOnlyDictionary CultureNames { get; set; } + public string UrlName { get; set; } + public string DocumentTypeAlias => ContentType.Alias; + public int DocumentTypeId { get; set; } + public string WriterName { get; set; } + public string CreatorName { get; set; } + public int WriterId { get; set; } + public int CreatorId { get; set; } + public string Path { get; set; } + public DateTime CreateDate { get; set; } + public DateTime UpdateDate { get; set; } + public Guid Version { get; set; } + public int Level { get; set; } + public string Url { get; set; } + public PublishedItemType ItemType => ContentType.ItemType; + public bool IsDraft { get; set; } + public IPublishedContent Parent { get; set; } + public IEnumerable Children { get; set; } + + // copied from PublishedContentBase + public IPublishedProperty GetProperty(string alias, bool recurse) + { + var property = GetProperty(alias); + if (recurse == false) return property; + + IPublishedContent content = this; + var firstNonNullProperty = property; + while (content != null && (property == null || property.HasValue() == false)) + { + content = content.Parent; + property = content?.GetProperty(alias); + if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; + } + + // if we find a content with the property with a value, return that property + // if we find no content with the property, return null + // if we find a content with the property without a value, return that property + // have to save that first property while we look further up, hence firstNonNullProperty + + return property != null && property.HasValue() ? property : firstNonNullProperty; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 80a4da3fb5..070166eaff 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -366,7 +366,8 @@ namespace Umbraco.Tests.TestHelpers var umbracoContext = new UmbracoContext( httpContext, service, - new WebSecurity(httpContext, Container.GetInstance(), Container.GetInstance()), + new WebSecurity(httpContext, Container.GetInstance(), + Container.GetInstance()), umbracoSettings ?? Container.GetInstance(), urlProviders ?? Enumerable.Empty(), globalSettings ?? Container.GetInstance(), diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 655dac8796..eaec8a24ff 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -178,6 +178,7 @@ + diff --git a/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs index e92e72db77..33e2164a21 100644 --- a/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs @@ -2,15 +2,23 @@ using Umbraco.Core.Models; using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Routing; namespace Umbraco.Web.Models.Mapping { internal class RedirectUrlMapperProfile : Profile { + private readonly UrlProvider _urlProvider; + + public RedirectUrlMapperProfile(UrlProvider urlProvider) + { + _urlProvider = urlProvider; + } + public RedirectUrlMapperProfile() { CreateMap() - .ForMember(x => x.OriginalUrl, expression => expression.MapFrom(item => Current.UmbracoContext.UrlProvider.GetUrlFromRoute(item.ContentId, item.Url))) + .ForMember(x => x.OriginalUrl, expression => expression.MapFrom(item => _urlProvider.GetUrlFromRoute(item.ContentId, item.Url, null))) .ForMember(x => x.DestinationUrl, expression => expression.Ignore()) .ForMember(x => x.RedirectId, expression => expression.MapFrom(item => item.Key)); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index fc95557dd1..37b8e97a28 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -163,7 +163,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // assemble the route pathParts.Reverse(); var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc - var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path; + //prefix the root node id containing the domain if it exists (this is a standard way of creating route paths) + //and is done so that we know the ID of the domain node for the path + var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path; return route; } diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 788a7a809b..0ea0c950da 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - /// Gets the nice url of a published content. + /// Gets the url of a published content. /// /// The Umbraco context. /// The published content id. @@ -51,10 +51,10 @@ namespace Umbraco.Web.Routing // will not use cache if previewing var route = umbracoContext.ContentCache.GetRouteById(id, culture); - return GetUrlFromRoute(route, umbracoContext, id, current, mode); + return GetUrlFromRoute(route, umbracoContext, id, current, mode, culture); } - internal string GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode) + internal string GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) { if (string.IsNullOrWhiteSpace(route)) { @@ -71,7 +71,7 @@ namespace Umbraco.Web.Routing var path = pos == 0 ? route : route.Substring(pos); var domainUri = pos == 0 ? null - : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current); + : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current, culture); // assemble the url from domainUri (maybe null) and path return AssembleUrl(domainUri, path, current, mode).ToString(); @@ -119,8 +119,7 @@ namespace Umbraco.Web.Routing var route = umbracoContext.ContentCache.GetRouteById(id, d?.Culture?.Name); if (route == null) continue; - //need to strip off the leading ID for the route - //TODO: Is there a nicer way to deal with this? + //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); @@ -170,7 +169,7 @@ namespace Umbraco.Web.Routing { if (mode == UrlProviderMode.Auto) { - if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) + if (current != null && current.GetLeftPart(UriPartial.Path).InvariantStartsWith(domainUri.Uri.GetLeftPart(UriPartial.Path))) mode = UrlProviderMode.Relative; else mode = UrlProviderMode.Absolute; diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index ba7b59323b..db051c20c5 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Routing /// The domain and its uri, if any, that best matches the specified uri, else null. /// If at least a domain is set on the node then the method returns the domain that /// best matches the specified uri, else it returns null. - internal DomainAndUri DomainForNode(int nodeId, Uri current) + internal DomainAndUri DomainForNode(int nodeId, Uri current, string culture = null) { // be safe if (nodeId <= 0) @@ -45,7 +45,7 @@ namespace Umbraco.Web.Routing return null; // else filter - var domainAndUri = DomainForUri(domains, current, domainAndUris => _siteDomainHelper.MapDomain(current, domainAndUris)); + var domainAndUri = DomainForUri(domains, current, culture, domainAndUris => _siteDomainHelper.MapDomain(current, domainAndUris)); if (domainAndUri == null) throw new Exception("DomainForUri returned null."); @@ -108,14 +108,13 @@ namespace Umbraco.Web.Routing /// the right one, unless it is null, in which case the method returns null. /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. /// - internal static DomainAndUri DomainForUri(IEnumerable domains, Uri current, Func filter = null) + internal static DomainAndUri DomainForUri(IEnumerable domains, Uri current, string culture = null, Func filter = null) { // sanitize the list to have proper uris for comparison (scheme, path end with /) // we need to end with / because example.com/foo cannot match example.com/foobar // we need to order so example.com/foo matches before example.com/ var domainsAndUris = domains .Where(d => d.IsWildcard == false) - //.Select(SanitizeForBackwardCompatibility) .Select(d => new DomainAndUri(d, current)) .OrderByDescending(d => d.Uri.ToString()) .ToArray(); @@ -126,8 +125,12 @@ namespace Umbraco.Web.Routing DomainAndUri domainAndUri; if (current == null) { - //get the default domain (there should be one) - domainAndUri = domainsAndUris.FirstOrDefault(x => x.IsDefault); + //get the default domain or the one matching the culture if specified + domainAndUri = domainsAndUris.FirstOrDefault(x => culture.IsNullOrWhiteSpace() ? x.IsDefault : x.Culture.Name.InvariantEquals(culture)); + + if (domainAndUri == null && !culture.IsNullOrWhiteSpace()) + throw new InvalidOperationException($"No domain was found by the specified culture '{culture}'"); + if (domainAndUri == null) domainAndUri = domainsAndUris.First(); // take the first one by default (what else can we do?) } @@ -138,13 +141,17 @@ namespace Umbraco.Web.Routing var currentWithSlash = current.EndPathWithSlash(); domainAndUri = domainsAndUris .FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(currentWithSlash)); - if (domainAndUri != null) return domainAndUri; + //is culture specified? if so this will need to match too + if (domainAndUri != null && (culture.IsNullOrWhiteSpace() || domainAndUri.Culture.Name.InvariantEquals(culture))) + return domainAndUri; // if none matches, try again without the port // ie current is www.example.com:1234/foo/bar, look for domain www.example.com domainAndUri = domainsAndUris .FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(currentWithSlash.WithoutPort())); - if (domainAndUri != null) return domainAndUri; + //is culture specified? if so this will need to match too + if (domainAndUri != null && (culture.IsNullOrWhiteSpace() || domainAndUri.Culture.Name.InvariantEquals(culture))) + return domainAndUri; // if none matches, then try to run the filter to pick a domain if (filter != null) @@ -170,7 +177,6 @@ namespace Umbraco.Web.Routing { return domains .Where(d => d.IsWildcard == false) - //.Select(SanitizeForBackwardCompatibility) .Select(d => new DomainAndUri(d, current)) .OrderByDescending(d => d.Uri.ToString()); } diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 422fb4d4e7..98bd1b182d 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -216,12 +216,12 @@ namespace Umbraco.Web.Routing return url ?? "#"; // legacy wants this } - internal string GetUrlFromRoute(int id, string route) + internal string GetUrlFromRoute(int id, string route, string culture) { var provider = _urlProviders.OfType().FirstOrDefault(); var url = provider == null ? route // what else? - : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode); + : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode, culture); return url ?? "#"; }