Content Url and Audit (in progress)

This commit is contained in:
Stephan
2018-07-05 17:14:11 +02:00
parent e9a31437e1
commit fdfdd541e3
12 changed files with 250 additions and 160 deletions

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core;
@@ -18,7 +18,7 @@ namespace Umbraco.Web.Routing
/// <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>
/// </remarks>
public static IEnumerable<string> GetContentUrls(this IContent content, UrlProvider urlProvider, ILocalizedTextService textService, IContentService contentService, ILogger logger)
public static IEnumerable<UrlInfo> GetContentUrls(this IContent content, UrlProvider urlProvider, ILocalizedTextService textService, IContentService contentService, ILogger logger)
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (urlProvider == null) throw new ArgumentNullException(nameof(urlProvider));
@@ -26,50 +26,39 @@ namespace Umbraco.Web.Routing
if (contentService == null) throw new ArgumentNullException(nameof(contentService));
if (logger == null) throw new ArgumentNullException(nameof(logger));
var urls = new HashSet<string>();
// going to build a list of urls (essentially for the back-office)
// which will contain
// - the 'main' url, which is what .Url would return, in the current culture
// - the 'other' urls we know (based upon domains, etc)
//
// this essentially happens when producing the urls for the back-office, and then we don't have
// a meaningful 'current culture' - so we need to explicitely pass some culture where required,
// and deal with whatever might happen
//
// if content is variant, go with the current culture - and that is NOT safe, there may be
// no 'main' url for that culture, deal with it later - otherwise, go with the invariant
// culture, and that is safe.
var varies = content.ContentType.VariesByCulture();
var culture = varies ? Thread.CurrentThread.CurrentUICulture.Name : "";
var urls = new List<UrlInfo>();
if (content.Published == false)
{
urls.Add(textService.Localize("content/itemNotPublished"));
urls.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished")));
return urls;
}
string url = null;
// fixme inject
// fixme PublishedRouter is stateless and should be a singleton!
var localizationService = Core.Composing.Current.Services.LocalizationService;
var publishedRouter = Core.Composing.Current.Container.GetInstance<PublishedRouter>();
if (varies)
{
if (!content.IsCulturePublished(culture))
{
urls.Add(textService.Localize("content/itemCultureNotPublished", culture));
// but keep going, we want to add the 'other' urls
url = "#no";
}
else if (!content.IsCultureAvailable(culture))
{
urls.Add(textService.Localize("content/itemCultureNotAvailable", culture));
// but keep going, we want to add the 'other' urls
url = "#no";
}
}
// 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)
//
// need to work on each culture.
// on invariant trees, each culture return the same thing
// but, we don't know if the tree to this content is invariant
// get the 'main' url
if (url == null)
var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList();
foreach (var culture in cultures)
{
// 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;
try
{
url = urlProvider.GetUrl(content.Id, culture);
@@ -79,79 +68,108 @@ namespace Umbraco.Web.Routing
logger.Error<UrlProvider>("GetUrl exception.", e);
url = "#ex";
}
}
if (url == "#") // deal with 'could not get the url'
{
// document as 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
switch (url)
{
parent = parent.ParentId > 0 ? parent.Parent(contentService) : null;
}
while (parent != null && parent.Published && (!varies || parent.IsCulturePublished(culture)));
// deal with 'could not get the url'
case "#":
HandleCouldNotGetUrl(content, culture, urls, contentService, textService);
break;
urls.Add(parent == null
? textService.Localize("content/parentNotPublishedAnomaly") // oops - internal error
: textService.Localize("content/parentNotPublished", new[] { parent.Name }));
}
else if (url == "#ex") // deal with exceptions
{
urls.Add(textService.Localize("content/getUrlException"));
}
else if (url == "#no") // deal with 'there is no main url'
{
// get the 'other' urls
foreach(var otherUrl in urlProvider.GetOtherUrls(content.Id))
urls.Add(otherUrl);
}
else // detect collisions, etc
{
// test for collisions on the 'main' url
var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute);
if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(UmbracoContext.Current.CleanedUmbracoUrl);
uri = UriUtility.UriToUmbraco(uri);
var r = Core.Composing.Current.Container.GetInstance<PublishedRouter>(); // fixme inject or ?
var pcr = r.CreateRequest(UmbracoContext.Current, uri);
r.TryRouteRequest(pcr);
// deal with exceptions
case "#ex":
urls.Add(UrlInfo.Message(textService.Localize("content/getUrlException"), culture));
break;
if (pcr.HasPublishedContent == false)
{
urls.Add(textService.Localize("content/routeError", new[] { "(error)" }));
}
else if (pcr.PublishedContent.Id != content.Id)
{
var o = pcr.PublishedContent;
string s;
if (o == null)
{
s = "(unknown)";
}
else
{
var l = new List<string>();
while (o != null)
{
l.Add(o.Name);
o = o.Parent;
}
l.Reverse();
s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")";
}
urls.Add(textService.Localize("content/routeError", s));
}
else
{
urls.Add(url);
// get the 'other' urls
foreach(var otherUrl in urlProvider.GetOtherUrls(content.Id))
urls.Add(otherUrl);
// got a url, deal with collisions, add url
default:
if (!DetectCollision(content, url, urls, culture, publishedRouter, textService)) // detect collisions, etc
urls.Add(UrlInfo.Url(url, culture));
break;
}
}
return urls;
// prepare for de-duplication
var durl = new Dictionary<string, List<UrlInfo>>();
var dmsg = new Dictionary<string, List<UrlInfo>>();
foreach (var url in urls)
{
var d = url.IsUrl ? durl : dmsg;
if (!d.TryGetValue(url.Text, out var l))
d[url.Text] = l = new List<UrlInfo>();
l.Add(url);
}
// deduplicate, order urls first then messages, concatenate cultures (hide if 'all')
var ret = new List<UrlInfo>();
foreach (var (text, infos) in durl)
ret.Add(UrlInfo.Url(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture))));
foreach (var (text, infos) in dmsg)
ret.Add(UrlInfo.Message(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture))));
// fixme - need to add 'others' urls
// but, when?
//// get the 'other' urls
//foreach(var otherUrl in urlProvider.GetOtherUrls(content.Id))
// urls.Add(otherUrl);
return ret;
}
private static void HandleCouldNotGetUrl(IContent content, string culture, List<UrlInfo> urls, IContentService contentService, ILocalizedTextService textService)
{
// 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
{
parent = parent.ParentId > 0 ? parent.Parent(contentService) : null;
}
while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture)));
if (parent == null) // oops, internal error
urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture));
else if (!parent.Published) // totally not published
urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] { parent.Name }), culture));
else // culture not published
urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture));
}
private static bool DetectCollision(IContent content, string url, List<UrlInfo> urls, string culture, PublishedRouter publishedRouter, ILocalizedTextService textService)
{
// test for collisions on the 'main' url
var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute);
if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(UmbracoContext.Current.CleanedUmbracoUrl);
uri = UriUtility.UriToUmbraco(uri);
var pcr = publishedRouter.CreateRequest(UmbracoContext.Current, uri);
publishedRouter.TryRouteRequest(pcr);
if (pcr.HasPublishedContent == false)
{
urls.Add(UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture));
return true;
}
if (pcr.PublishedContent.Id != content.Id)
{
var o = pcr.PublishedContent;
var l = new List<string>();
while (o != null)
{
l.Add(o.Name);
o = o.Parent;
}
l.Reverse();
var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")";
urls.Add(UrlInfo.Message(textService.Localize("content/routeError", s), culture));
return true;
}
// no collision
return false;
}
}
}