Content Url and Audit (in progress)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user