Files
Umbraco-CMS/src/Umbraco.Core/Routing/UrlProviderExtensions.cs

215 lines
10 KiB
C#
Raw Normal View History

2018-06-29 19:52:40 +02:00
using System;
using System.Collections.Generic;
2018-07-05 17:14:11 +02:00
using System.Linq;
2018-06-29 19:52:40 +02:00
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
2018-06-29 19:52:40 +02:00
namespace Umbraco.Web.Routing
{
internal static class UrlProviderExtensions
{
/// <summary>
2018-06-12 17:05:37 +02:00
/// Gets the Urls of the content item.
2018-06-29 19:52:40 +02:00
/// </summary>
/// <remarks>
2018-06-12 17:05:37 +02:00
/// <para>Use when displaying Urls. If errors occur when generating the Urls, they will show in the list.</para>
/// <para>Contains all the Urls that we can figure out (based upon domains, etc).</para>
2018-06-29 19:52:40 +02:00
/// </remarks>
public static IEnumerable<UrlInfo> GetContentUrls(this IContent content,
2019-01-31 09:08:51 +01:00
IPublishedRouter publishedRouter,
2020-02-09 18:53:37 +01:00
IUmbracoContext umbracoContext,
ILocalizationService localizationService,
ILocalizedTextService textService,
IContentService contentService,
IVariationContextAccessor variationContextAccessor,
ILogger logger,
Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/move-files-after-umbraco-context-abstractions # Conflicts: # src/Umbraco.Abstractions/PublishedContentExtensions.cs # src/Umbraco.Abstractions/Routing/AliasUrlProvider.cs # src/Umbraco.Abstractions/Routing/DefaultMediaUrlProvider.cs # src/Umbraco.Abstractions/Routing/DefaultUrlProvider.cs # src/Umbraco.Abstractions/Routing/UrlProvider.cs # src/Umbraco.Abstractions/Routing/UrlProviderExtensions.cs # src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs # src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs # src/Umbraco.Tests/Routing/GetContentUrlsTests.cs # src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs # src/Umbraco.Tests/Routing/UmbracoModuleTests.cs # src/Umbraco.Tests/Routing/UrlProviderTests.cs # src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs # src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs # src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs # src/Umbraco.Tests/TestHelpers/TestHelper.cs # src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs # src/Umbraco.Tests/Testing/Objects/TestUmbracoContextFactory.cs # src/Umbraco.Tests/Testing/UmbracoTestBase.cs # src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs # src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs # src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs # src/Umbraco.Web/PublishedContentExtensions.cs # src/Umbraco.Web/UmbracoContext.cs # src/Umbraco.Web/UmbracoContextFactory.cs # src/Umbraco.Web/UmbracoInjectedModule.cs
2020-02-17 12:45:37 +01:00
UriUtility uriUtility,
IPublishedUrlProvider publishedUrlProvider)
2018-06-29 19:52:40 +02:00
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (publishedRouter == null) throw new ArgumentNullException(nameof(publishedRouter));
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
if (localizationService == null) throw new ArgumentNullException(nameof(localizationService));
if (textService == null) throw new ArgumentNullException(nameof(textService));
if (contentService == null) throw new ArgumentNullException(nameof(contentService));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (publishedUrlProvider == null) throw new ArgumentNullException(nameof(publishedUrlProvider));
if (uriUtility == null) throw new ArgumentNullException(nameof(uriUtility));
if (variationContextAccessor == null) throw new ArgumentNullException(nameof(variationContextAccessor));
2018-06-29 19:52:40 +02:00
if (content.Published == false)
{
2018-12-18 22:02:39 +11:00
yield return UrlInfo.Message(textService.Localize("content/itemNotPublished"));
yield break;
2018-12-20 14:31:46 +01:00
}
2018-12-18 22:02:39 +11:00
2018-07-05 17:14:11 +02:00
// build a list of urls, for the back-office
// which will contain
// - the 'main' urls, which is what .Url would return, for each culture
// - the 'other' urls we know (based upon domains, etc)
//
2018-12-20 14:31:46 +01:00
// need to work through each installed culture:
// on invariant nodes, each culture returns the same url segment but,
// we don't know if the branch to this content is invariant, so we need to ask
// for URLs for all cultures.
2018-12-20 14:31:46 +01:00
// and, not only for those assigned to domains in the branch, because we want
// to show what GetUrl() would return, for every culture.
2018-06-12 17:05:37 +02:00
2018-12-20 14:31:46 +01:00
var urls = new HashSet<UrlInfo>();
2018-07-05 17:14:11 +02:00
var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList();
//get all URLs for all cultures
2018-12-20 14:31:46 +01:00
//in a HashSet, so de-duplicates too
Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/move-files-after-umbraco-context-abstractions # Conflicts: # src/Umbraco.Abstractions/PublishedContentExtensions.cs # src/Umbraco.Abstractions/Routing/AliasUrlProvider.cs # src/Umbraco.Abstractions/Routing/DefaultMediaUrlProvider.cs # src/Umbraco.Abstractions/Routing/DefaultUrlProvider.cs # src/Umbraco.Abstractions/Routing/UrlProvider.cs # src/Umbraco.Abstractions/Routing/UrlProviderExtensions.cs # src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs # src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs # src/Umbraco.Tests/Routing/GetContentUrlsTests.cs # src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs # src/Umbraco.Tests/Routing/UmbracoModuleTests.cs # src/Umbraco.Tests/Routing/UrlProviderTests.cs # src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs # src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs # src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs # src/Umbraco.Tests/TestHelpers/TestHelper.cs # src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs # src/Umbraco.Tests/Testing/Objects/TestUmbracoContextFactory.cs # src/Umbraco.Tests/Testing/UmbracoTestBase.cs # src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs # src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs # src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs # src/Umbraco.Web/PublishedContentExtensions.cs # src/Umbraco.Web/UmbracoContext.cs # src/Umbraco.Web/UmbracoContextFactory.cs # src/Umbraco.Web/UmbracoInjectedModule.cs
2020-02-17 12:45:37 +01:00
foreach (var cultureUrl in GetContentUrlsByCulture(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider))
{
urls.Add(cultureUrl);
}
//return the real urls first, then the messages
foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key))
{
//in some cases there will be the same URL for multiple cultures:
// * The entire branch is invariant
// * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed
foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture))
yield return dUrl;
}
// get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless.
// for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them.
foreach (var otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture))
if (urls.Add(otherUrl)) //avoid duplicates
yield return otherUrl;
}
/// <summary>
/// Tries to return a <see cref="UrlInfo"/> for each culture for the content while detecting collisions/errors
/// </summary>
/// <param name="content"></param>
/// <param name="cultures"></param>
/// <param name="publishedRouter"></param>
/// <param name="umbracoContext"></param>
/// <param name="contentService"></param>
/// <param name="textService"></param>
/// <param name="logger"></param>
/// <returns></returns>
private static IEnumerable<UrlInfo> GetContentUrlsByCulture(IContent content,
IEnumerable<string> cultures,
2019-01-31 09:08:51 +01:00
IPublishedRouter publishedRouter,
2020-02-09 18:53:37 +01:00
IUmbracoContext umbracoContext,
IContentService contentService,
ILocalizedTextService textService,
IVariationContextAccessor variationContextAccessor,
ILogger logger,
Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/move-files-after-umbraco-context-abstractions # Conflicts: # src/Umbraco.Abstractions/PublishedContentExtensions.cs # src/Umbraco.Abstractions/Routing/AliasUrlProvider.cs # src/Umbraco.Abstractions/Routing/DefaultMediaUrlProvider.cs # src/Umbraco.Abstractions/Routing/DefaultUrlProvider.cs # src/Umbraco.Abstractions/Routing/UrlProvider.cs # src/Umbraco.Abstractions/Routing/UrlProviderExtensions.cs # src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs # src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs # src/Umbraco.Tests/Routing/GetContentUrlsTests.cs # src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs # src/Umbraco.Tests/Routing/UmbracoModuleTests.cs # src/Umbraco.Tests/Routing/UrlProviderTests.cs # src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs # src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs # src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs # src/Umbraco.Tests/TestHelpers/TestHelper.cs # src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs # src/Umbraco.Tests/Testing/Objects/TestUmbracoContextFactory.cs # src/Umbraco.Tests/Testing/UmbracoTestBase.cs # src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs # src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs # src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs # src/Umbraco.Web/PublishedContentExtensions.cs # src/Umbraco.Web/UmbracoContext.cs # src/Umbraco.Web/UmbracoContextFactory.cs # src/Umbraco.Web/UmbracoInjectedModule.cs
2020-02-17 12:45:37 +01:00
UriUtility uriUtility,
IPublishedUrlProvider publishedUrlProvider)
{
2018-07-05 17:14:11 +02:00
foreach (var culture in cultures)
2018-06-29 19:52:40 +02:00
{
2018-07-05 17:14:11 +02:00
// if content is variant, and culture is not published, skip
if (content.ContentType.VariesByCulture() && !content.IsCulturePublished(culture))
continue;
// if it's variant and culture is published, or if it's invariant, proceed
string url;
2018-06-12 17:05:37 +02:00
try
{
url = publishedUrlProvider.GetUrl(content.Id, culture: culture);
2018-06-12 17:05:37 +02:00
}
catch (Exception ex)
2018-06-12 17:05:37 +02:00
{
logger.Error<UrlProvider>(ex, "GetUrl exception.");
2018-06-12 17:05:37 +02:00
url = "#ex";
}
2018-07-05 17:14:11 +02:00
switch (url)
2018-06-29 19:52:40 +02:00
{
2018-07-05 17:14:11 +02:00
// deal with 'could not get the url'
case "#":
yield return HandleCouldNotGetUrl(content, culture, contentService, textService);
2018-07-05 17:14:11 +02:00
break;
// deal with exceptions
case "#ex":
yield return UrlInfo.Message(textService.Localize("content/getUrlException"), culture);
2018-07-05 17:14:11 +02:00
break;
// got a url, deal with collisions, add url
default:
if (DetectCollision(content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility, out var urlInfo)) // detect collisions, etc
yield return urlInfo;
else
yield return UrlInfo.Url(url, culture);
2018-07-05 17:14:11 +02:00
break;
2018-06-29 19:52:40 +02:00
}
2018-07-05 17:14:11 +02:00
}
}
private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService)
2018-07-05 17:14:11 +02:00
{
// document has a published version yet its url is "#" => a parent must be
// unpublished, walk up the tree until we find it, and report.
var parent = content;
do
2018-06-29 19:52:40 +02:00
{
parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null;
2018-06-29 19:52:40 +02:00
}
2018-07-05 17:14:11 +02:00
while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture)));
if (parent == null) // oops, internal error
return UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture);
2018-07-05 17:14:11 +02:00
else if (!parent.Published) // totally not published
return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] {parent.Name}), culture);
2018-07-05 17:14:11 +02:00
else // culture not published
return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture);
2018-07-05 17:14:11 +02:00
}
private static bool DetectCollision(IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility, out UrlInfo urlInfo)
2018-07-05 17:14:11 +02:00
{
// test for collisions on the 'main' url
var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute);
if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl);
uri = uriUtility.UriToUmbraco(uri);
var pcr = publishedRouter.CreateRequest(umbracoContext, uri);
2018-07-05 17:14:11 +02:00
publishedRouter.TryRouteRequest(pcr);
urlInfo = null;
2018-07-05 17:14:11 +02:00
if (pcr.HasPublishedContent == false)
2018-06-12 17:05:37 +02:00
{
urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture);
2018-07-05 17:14:11 +02:00
return true;
2018-06-12 17:05:37 +02:00
}
2018-07-05 17:14:11 +02:00
if (pcr.IgnorePublishedContentCollisions)
return false;
2018-07-05 17:14:11 +02:00
if (pcr.PublishedContent.Id != content.Id)
2018-06-29 19:52:40 +02:00
{
2018-07-05 17:14:11 +02:00
var o = pcr.PublishedContent;
var l = new List<string>();
while (o != null)
2018-06-29 19:52:40 +02:00
{
l.Add(o.Name(variationContextAccessor));
o = o.Parent;
2018-06-29 19:52:40 +02:00
}
2018-07-05 17:14:11 +02:00
l.Reverse();
var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")";
2018-06-12 17:05:37 +02:00
urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture);
2018-07-05 17:14:11 +02:00
return true;
2018-06-29 19:52:40 +02:00
}
2018-07-05 17:14:11 +02:00
// no collision
return false;
2018-06-29 19:52:40 +02:00
}
}
}