Content Url and Audit (in progress)
This commit is contained in:
@@ -891,7 +891,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
var changeType = TreeChangeTypes.RefreshNode;
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
Audit(AuditType.Save, "Save Content performed by user", userId, content.Id);
|
||||
Audit(AuditType.Save, "Saved by user", userId, content.Id);
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
@@ -931,7 +931,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
}
|
||||
scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs());
|
||||
Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root);
|
||||
Audit(AuditType.Save, "Bulk-saved by user", userId == -1 ? 0 : userId, Constants.System.Root);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -988,6 +988,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
var publishedState = content.PublishedState;
|
||||
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
|
||||
throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(SavePublishing)} method.");
|
||||
@@ -996,12 +998,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
// cannot accept a specific culture for invariant content type (but '*' is ok)
|
||||
if (content.ContentType.VariesByCulture())
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
if (culture == null)
|
||||
throw new NotSupportedException("Invariant culture is not supported by variant content types.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!culture.IsNullOrWhiteSpace() && culture != "*")
|
||||
if (culture != null && culture != "*")
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types.");
|
||||
}
|
||||
|
||||
@@ -1010,7 +1012,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return new UnpublishResult(UnpublishResultType.SuccessAlready, evtMsgs, content);
|
||||
|
||||
// all cultures = unpublish whole
|
||||
if (culture == "*")
|
||||
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
|
||||
{
|
||||
((Content) content).PublishedState = PublishedState.Unpublishing;
|
||||
}
|
||||
@@ -1031,9 +1033,21 @@ namespace Umbraco.Core.Services.Implement
|
||||
var saved = SavePublishing(content, userId);
|
||||
if (saved.Success)
|
||||
{
|
||||
Audit(AuditType.UnPublish, $"Unpublish variation culture: \"{culture ?? string.Empty}\" performed by user", userId, content.Id);
|
||||
UnpublishResultType result;
|
||||
if (culture == "*" || culture == null)
|
||||
{
|
||||
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
|
||||
result = UnpublishResultType.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
Audit(AuditType.UnPublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id);
|
||||
if (!content.Published)
|
||||
Audit(AuditType.UnPublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id);
|
||||
result = content.Published ? UnpublishResultType.SuccessCulture : UnpublishResultType.SuccessMandatoryCulture;
|
||||
}
|
||||
scope.Complete();
|
||||
return new UnpublishResult(UnpublishResultType.SuccessVariant, evtMsgs, content);
|
||||
return new UnpublishResult(result, evtMsgs, content);
|
||||
}
|
||||
|
||||
// failed - map result
|
||||
@@ -1156,7 +1170,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// events and audit
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), "UnPublished");
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
|
||||
Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id);
|
||||
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
|
||||
scope.Complete();
|
||||
return new PublishResult(PublishResultType.Success, evtMsgs, content);
|
||||
}
|
||||
@@ -1187,7 +1201,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(descendants, false, false), "Published");
|
||||
}
|
||||
|
||||
Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id);
|
||||
Audit(AuditType.Publish, "Published by user", userId, content.Id);
|
||||
scope.Complete();
|
||||
return publishResult;
|
||||
}
|
||||
@@ -1321,7 +1335,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(document, TreeChangeTypes.RefreshBranch).ToEventArgs());
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(publishedDocuments, false, false), "Published");
|
||||
Audit(AuditType.Publish, "SaveAndPublishBranch performed by user", userId, document.Id);
|
||||
Audit(AuditType.Publish, "Branch published by user", userId, document.Id);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1390,7 +1404,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
DeleteLocked(scope, content);
|
||||
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.Remove).ToEventArgs());
|
||||
Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id);
|
||||
Audit(AuditType.Delete, "Deleted by user", userId, content.Id);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1458,7 +1472,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
deleteRevisionsEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs);
|
||||
Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root);
|
||||
Audit(AuditType.Delete, "Delete (by version date) by user", userId, Constants.System.Root);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1495,7 +1509,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
_documentRepository.DeleteVersion(versionId);
|
||||
|
||||
scope.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId));
|
||||
Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root);
|
||||
Audit(AuditType.Delete, "Delete (by version) by user", userId, Constants.System.Root);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1540,7 +1554,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
moveEventArgs.CanCancel = false;
|
||||
moveEventArgs.MoveInfoCollection = moveInfo;
|
||||
scope.Events.Dispatch(Trashed, this, moveEventArgs, nameof(Trashed));
|
||||
Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
|
||||
Audit(AuditType.Move, "Moved to Recycle Bin by user", userId, content.Id);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1612,7 +1626,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
moveEventArgs.MoveInfoCollection = moveInfo;
|
||||
moveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved));
|
||||
Audit(AuditType.Move, "Move Content performed by user", userId, content.Id);
|
||||
Audit(AuditType.Move, "Moved by user", userId, content.Id);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1709,7 +1723,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?!
|
||||
scope.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs);
|
||||
scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.Remove)).ToEventArgs());
|
||||
Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent);
|
||||
Audit(AuditType.Delete, "Recycle Bin emptied by user", 0, Constants.System.RecycleBinContent);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
/// <summary>
|
||||
/// The specified variant was unpublished, the content item itself remains published.
|
||||
/// </summary>
|
||||
SuccessVariant = 2,
|
||||
SuccessCulture = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The specified variant was a mandatory culture therefore it was unpublished and the content item itself is unpublished
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
<div class="umb-package-details">
|
||||
|
||||
|
||||
<div class="umb-package-details__main-content">
|
||||
|
||||
|
||||
<umb-box data-element="node-info-urls">
|
||||
<umb-box-header title-key="general_links"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
<ul class="nav nav-stacked" style="margin-bottom: 0;">
|
||||
<li ng-repeat="url in node.urls">
|
||||
<a ng-if="node.published" href="{{url}}" target="_blank">
|
||||
<i class="icon icon-window-popin"></i>
|
||||
<span>{{url}}</span>
|
||||
</a>
|
||||
<div ng-if="!node.published">
|
||||
<i class="icon icon-window-popin"></i>
|
||||
<span>{{url}}</span>
|
||||
<span ng-if="url.isUrl">
|
||||
<a href="{{url.text}}" target="_blank">
|
||||
<i class="icon icon-window-popin"></i>
|
||||
<span>{{url.text}}</span>
|
||||
</a>
|
||||
<span ng-if="url.culture" style="font-size: 13px; color: #cccccc; margin-left: 20px;">{{url.culture}}</span>
|
||||
</span>
|
||||
<div ng-if="!url.isUrl" style="margin-top: 4px;">
|
||||
<span>{{url.text}}</span>
|
||||
<span ng-if="url.culture" style="font-size: 13px; color: #cccccc; margin-left: 20px;">{{url.culture}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -23,12 +26,12 @@
|
||||
<umb-box data-element="node-info-history">
|
||||
<umb-box-header title-key="general_history"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
|
||||
<div style="position: relative;">
|
||||
|
||||
<div ng-if="loadingAuditTrail" style="background: rgba(255, 255, 255, 0.8); position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div>
|
||||
<umb-load-indicator ng-if="loadingAuditTrail"></umb-load-indicator>
|
||||
|
||||
|
||||
<div ng-if="auditTrail.length === 0" style="padding: 10px;">
|
||||
<umb-empty-state
|
||||
position="center"
|
||||
@@ -39,10 +42,10 @@
|
||||
|
||||
<div class="history">
|
||||
|
||||
<div ng-if="auditTrail.length > 1" class="history-line"></div>
|
||||
|
||||
<div ng-if="auditTrail.length > 1" class="history-line"></div>
|
||||
|
||||
<div class="history-item" ng-repeat="item in auditTrail">
|
||||
|
||||
|
||||
<div class="history-item__break">
|
||||
<div class="history-item__avatar">
|
||||
<umb-avatar
|
||||
@@ -59,7 +62,7 @@
|
||||
<div class="history-item__date">{{item.timestampFormatted}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="history-item__break">
|
||||
<umb-badge
|
||||
style="margin-right: 5px;"
|
||||
@@ -69,7 +72,7 @@
|
||||
</umb-badge>
|
||||
<span>{{ item.comment }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,8 +87,8 @@
|
||||
</umb-pagination>
|
||||
</div>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__sidebar">
|
||||
@@ -96,15 +99,15 @@
|
||||
<div class="date-wrapper">
|
||||
|
||||
<div class="flex items-center flex-column">
|
||||
|
||||
|
||||
|
||||
<umb-date-time-picker
|
||||
data-element="node-info-publish"
|
||||
options="datePickerConfig"
|
||||
on-change="datePickerChange(event, 'publish')">
|
||||
|
||||
|
||||
<div class="date-container">
|
||||
|
||||
|
||||
<div class="date-container__title">
|
||||
<localize key="content_releaseDate"></localize>
|
||||
</div>
|
||||
@@ -115,12 +118,12 @@
|
||||
<div class="date-wrapper__date">{{node.releaseDateDay}} {{node.releaseDateTime}}</div>
|
||||
</div>
|
||||
|
||||
<a href="" ng-if="!node.releaseDate" class="bold" style="color: #00aea2; text-decoration: underline;"><localize key="content_setDate">Set date</localize></a>
|
||||
|
||||
<a href="" ng-if="!node.releaseDate" class="bold" style="color: #00aea2; text-decoration: underline;"><localize key="content_setDate">Set date</localize></a>
|
||||
|
||||
</div>
|
||||
|
||||
</umb-date-time-picker>
|
||||
|
||||
|
||||
<a ng-if="node.releaseDate" ng-click="clearPublishDate()" href="" style="text-decoration: underline;">
|
||||
<small><localize key="content_removeDate">Clear date</localize></small>
|
||||
</a>
|
||||
@@ -140,7 +143,7 @@
|
||||
<div class="date-container__title">
|
||||
<localize key="content_unpublishDate"></localize>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="date-container__date" ng-if="node.removeDate">
|
||||
<div class="date-wrapper__date">{{node.removeDateMonth}} {{node.removeDateYear}}</div>
|
||||
<div class="date-wrapper__number">{{node.removeDateDayNumber}}</div>
|
||||
@@ -159,7 +162,7 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
@@ -200,7 +203,7 @@
|
||||
<small>{{ node.key }}</small>
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-empty-state
|
||||
ng-if="!nodeUrl"
|
||||
ng-if="!nodeUrl || !nodeUrl.isUrl"
|
||||
size="small">
|
||||
<localize key="content_noMediaLink"></localize>
|
||||
</umb-empty-state>
|
||||
|
||||
<ul ng-if="nodeUrl" class="nav nav-stacked" style="margin-bottom: 0;">
|
||||
<ul ng-if="nodeUrl && nodeUrl.isUrl" class="nav nav-stacked" style="margin-bottom: 0;">
|
||||
<li>
|
||||
<a href="{{nodeUrl}}" target="_blank">
|
||||
<a href="{{nodeUrl.text}}" target="_blank">
|
||||
<i class="icon icon-window-popin"></i>
|
||||
<span>{{nodeUrl}}</span>
|
||||
<span>{{nodeUrl.text}}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -56,4 +56,4 @@
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -165,9 +165,11 @@
|
||||
<key alias="noMediaLink">This media item has no link</key>
|
||||
<key alias="otherElements">Properties</key>
|
||||
<key alias="parentNotPublished">This document is published but is not visible because the parent '%0%' is unpublished</key>
|
||||
<key alias="parentCultureNotPublished">This culture is published but is not visible because it is unpublished on parent '%0%'</key>
|
||||
<key alias="parentNotPublishedAnomaly">This document is published but is not in the cache</key>
|
||||
<key alias="getUrlException">Could not get the url</key>
|
||||
<key alias="routeError">This document is published but its url would collide with content %0%</key>
|
||||
<key alias="routeErrorCannotRoute">This document is published but its url cannot be routed</key>
|
||||
<key alias="publish">Publish</key>
|
||||
<key alias="published">Published</key>
|
||||
<key alias="publishedPendingChanges">Published (pending changes)</key>
|
||||
|
||||
@@ -199,9 +199,11 @@
|
||||
<key alias="noMediaLink">This media item has no link</key>
|
||||
<key alias="otherElements">Properties</key>
|
||||
<key alias="parentNotPublished">This document is published but is not visible because the parent '%0%' is unpublished</key>
|
||||
<key alias="parentCultureNotPublished">This culture is published but is not visible because it is unpublished on parent '%0%'</key>
|
||||
<key alias="parentNotPublishedAnomaly">This document is published but is not in the cache</key>
|
||||
<key alias="getUrlException">Could not get the url</key>
|
||||
<key alias="routeError">This document is published but its url would collide with content %0%</key>
|
||||
<key alias="routeErrorCannotRoute">This document is published but its url cannot be routed</key>
|
||||
<key alias="publish">Publish</key>
|
||||
<key alias="published">Published</key>
|
||||
<key alias="publishedPendingChanges">Published (pending changes)</key>
|
||||
|
||||
@@ -1003,7 +1003,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
if (foundContent == null)
|
||||
HandleContentNotFound(id);
|
||||
|
||||
|
||||
var unpublishResult = Services.ContentService.Unpublish(foundContent, culture: culture, userId: Security.GetUserId().ResultOr(0));
|
||||
|
||||
var content = MapToDisplay(foundContent, culture);
|
||||
@@ -1019,7 +1019,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
content.AddSuccessNotification(
|
||||
Services.TextService.Localize("content/unPublish"),
|
||||
unpublishResult.Result == UnpublishResultType.SuccessVariant
|
||||
unpublishResult.Result == UnpublishResultType.SuccessCulture
|
||||
? Services.TextService.Localize("speechBubbles/contentVariationUnpublished", new[] { culture })
|
||||
: Services.TextService.Localize("speechBubbles/contentUnpublished"));
|
||||
|
||||
@@ -1288,6 +1288,5 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -41,7 +42,7 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
public ContentTypeBasic DocumentType { get; set; }
|
||||
|
||||
[DataMember(Name = "urls")]
|
||||
public string[] Urls { get; set; }
|
||||
public UrlInfo[] Urls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether previewing is allowed for this node
|
||||
|
||||
@@ -8,7 +8,7 @@ using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class ContentUrlResolver : IValueResolver<IContent, ContentItemDisplay, string[]>
|
||||
internal class ContentUrlResolver : IValueResolver<IContent, ContentItemDisplay, UrlInfo[]>
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly IContentService _contentService;
|
||||
@@ -21,12 +21,12 @@ namespace Umbraco.Web.Models.Mapping
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string[] Resolve(IContent source, ContentItemDisplay destination, string[] destMember, ResolutionContext context)
|
||||
public UrlInfo[] Resolve(IContent source, ContentItemDisplay destination, UrlInfo[] destMember, ResolutionContext context)
|
||||
{
|
||||
var umbracoContext = context.GetUmbracoContext(throwIfMissing: false);
|
||||
|
||||
var urls = umbracoContext == null
|
||||
? new[] {"Cannot generate urls without a current Umbraco Context"}
|
||||
? new[] { UrlInfo.Message("Cannot generate urls without a current Umbraco Context") }
|
||||
: source.GetContentUrls(umbracoContext.UrlProvider, _textService, _contentService, _logger).ToArray();
|
||||
|
||||
return urls;
|
||||
|
||||
50
src/Umbraco.Web/Routing/UrlInfo.cs
Normal file
50
src/Umbraco.Web/Routing/UrlInfo.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents infos for a url.
|
||||
/// </summary>
|
||||
[DataContract(Name = "urlInfo", Namespace = "")]
|
||||
public class UrlInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlInfo"/> class.
|
||||
/// </summary>
|
||||
private UrlInfo(string text, bool isUrl, string culture)
|
||||
{
|
||||
IsUrl = isUrl;
|
||||
Text = text;
|
||||
Culture = culture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the culture.
|
||||
/// </summary>
|
||||
[DataMember(Name = "culture")]
|
||||
public string Culture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the url is a true url.
|
||||
/// </summary>
|
||||
/// <remarks>Otherwise, it is a message.</remarks>
|
||||
[DataMember(Name = "isUrl")]
|
||||
public bool IsUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text, which is either the url, or a message.
|
||||
/// </summary>
|
||||
[DataMember(Name = "text")]
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="UrlInfo"/> instance representing a true url.
|
||||
/// </summary>
|
||||
public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="UrlInfo"/> instance representing a message.
|
||||
/// </summary>
|
||||
public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +395,7 @@
|
||||
<Compile Include="Routing\ContentFinderCollectionBuilder.cs" />
|
||||
<Compile Include="Routing\Domain.cs" />
|
||||
<Compile Include="Routing\IContentLastChanceFinder.cs" />
|
||||
<Compile Include="Routing\UrlInfo.cs" />
|
||||
<Compile Include="Routing\UrlProviderCollection.cs" />
|
||||
<Compile Include="Routing\UrlProviderCollectionBuilder.cs" />
|
||||
<Compile Include="Scheduling\HealthCheckNotifier.cs" />
|
||||
|
||||
Reference in New Issue
Block a user