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

205 lines
9.2 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;
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,
PublishedRouter publishedRouter,
UmbracoContext umbracoContext,
ILocalizationService localizationService,
ILocalizedTextService textService,
IContentService contentService,
ILogger logger)
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));
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
foreach (var cultureUrl in GetContentUrlsByCulture(content, cultures, publishedRouter, umbracoContext, contentService, textService, logger))
{
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 umbracoContext.UrlProvider.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,
PublishedRouter publishedRouter,
UmbracoContext umbracoContext,
IContentService contentService,
ILocalizedTextService textService,
ILogger logger)
{
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 = umbracoContext.UrlProvider.GetUrl(content.Id, 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, 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, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService, 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);
2018-07-05 17:14:11 +02:00
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
{
2018-07-05 17:14:11 +02:00
l.Add(o.Name);
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
}
}
}