Publishing in the Management API (#14774)
* make CoreScopeProvider available for derived classes * Create publish controller * Add publish functionality * Remove unneeded using * Implement publish for multiple cultures * support multiple cultures in controler * Dont validate properties * Refactor to use PublishingOperationStatus * refactor to use proper publish async methods * Refactor publish logic into own service * Commit some demo code * Add notes about what errors can happen when publishing * Rework ContentPublishingService and introduce explicit Publish and PublishBranch methods in ContentService * Fix merge * Allow the publishing strategy to do its job * Improved check for unsaved changes * Make the old content controller work (as best possible) * Remove SaveAndPublish (SaveAndPublishBranch) from all tests * Proper guards for invalid cultures when publishing * Fix edge cases for property validation and content unpublishing + add unpublishing to ContentPublishingService * Clear out a few TODOs - we'll accept the behavior for now * Unpublish controller * Fix merge * Fix branch publish notifications * Added extra test for publishing unpublished cultures and added FIXME comments for when we fix the state of published cultures in content --------- Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Co-authored-by: Zeegaan <nge@umbraco.dk>
This commit is contained in:
@@ -1110,10 +1110,41 @@ public class ContentService : RepositoryService, IContentService
|
||||
return OperationResult.Succeed(eventMessages);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"This method no longer saves content, only publishes it. Please use {nameof(Publish)} instead. Will be removed in V16")]
|
||||
public PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId)
|
||||
=> Publish(content, new[] { culture }, userId);
|
||||
|
||||
[Obsolete($"This method no longer saves content, only publishes it. Please use {nameof(Publish)} instead. Will be removed in V16")]
|
||||
public PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
=> Publish(content, cultures, userId);
|
||||
|
||||
public PublishResult Publish(IContent content, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
EventMessages evtMsgs = EventMessagesFactory.Get();
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
if (cultures is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cultures));
|
||||
}
|
||||
|
||||
if (cultures.Any(c => c.IsNullOrWhiteSpace()) || cultures.Distinct().Count() != cultures.Length)
|
||||
{
|
||||
throw new ArgumentException("Cultures cannot be null or whitespace", nameof(cultures));
|
||||
}
|
||||
|
||||
// we need to guard against unsaved changes before proceeding; the content will be saved, but we're not firing any saved notifications
|
||||
if (HasUnsavedChanges(content))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, EventMessagesFactory.Get(), content);
|
||||
}
|
||||
|
||||
if (content.Name != null && content.Name.Length > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
|
||||
}
|
||||
|
||||
PublishedState publishedState = content.PublishedState;
|
||||
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
|
||||
@@ -1126,71 +1157,22 @@ public class ContentService : RepositoryService, IContentService
|
||||
// cannot accept a specific culture for invariant content type (but '*' is ok)
|
||||
if (content.ContentType.VariesByCulture())
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
if (cultures.Length > 1 && cultures.Contains("*"))
|
||||
{
|
||||
throw new NotSupportedException("Invariant culture is not supported by variant content types.");
|
||||
throw new ArgumentException("Cannot combine wildcard and specific cultures when publishing variant content types.", nameof(cultures));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!culture.IsNullOrWhiteSpace() && culture != "*")
|
||||
if (cultures.Length == 0)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"Culture \"{culture}\" is not supported by invariant content types.");
|
||||
}
|
||||
}
|
||||
|
||||
if (content.Name != null && content.Name.Length > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
|
||||
}
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
// Change state to publishing
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
var savingNotification = new ContentSavingNotification(content, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
cultures = new[] { "*" };
|
||||
}
|
||||
|
||||
// if culture is specific, first publish the invariant values, then publish the culture itself.
|
||||
// if culture is '*', then publish them all (including variants)
|
||||
|
||||
// this will create the correct culture impact even if culture is * or null
|
||||
var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
|
||||
// publish the culture(s)
|
||||
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
|
||||
content.PublishCulture(impact);
|
||||
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
if (cultures == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cultures));
|
||||
}
|
||||
|
||||
if (content.Name != null && content.Name.Length > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
|
||||
if (cultures[0] != "*" || cultures.Length > 1)
|
||||
{
|
||||
throw new ArgumentException($"Only wildcard culture is supported when publishing invariant content types.", nameof(cultures));
|
||||
}
|
||||
}
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
@@ -1201,37 +1183,18 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
EventMessages evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
var savingNotification = new ContentSavingNotification(content, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
}
|
||||
|
||||
var varies = content.ContentType.VariesByCulture();
|
||||
|
||||
if (cultures.Length == 0 && !varies)
|
||||
{
|
||||
// No cultures specified and doesn't vary, so publish it, else nothing to publish
|
||||
return SaveAndPublish(content, userId: userId);
|
||||
}
|
||||
|
||||
if (cultures.Any(x => x == null || x == "*"))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
|
||||
}
|
||||
|
||||
IEnumerable<CultureImpact> impacts =
|
||||
cultures.Select(x => _cultureImpactFactory.ImpactExplicit(x, IsDefaultCulture(allLangs, x)));
|
||||
// this will create the correct culture impact even if culture is * or null
|
||||
IEnumerable<CultureImpact?> impacts =
|
||||
cultures.Select(culture => _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content));
|
||||
|
||||
// publish the culture(s)
|
||||
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
|
||||
foreach (CultureImpact impact in impacts)
|
||||
foreach (CultureImpact? impact in impacts)
|
||||
{
|
||||
content.PublishCulture(impact);
|
||||
}
|
||||
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
@@ -1286,12 +1249,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
var savingNotification = new ContentSavingNotification(content, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
}
|
||||
|
||||
// all cultures = unpublish whole
|
||||
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
|
||||
{
|
||||
@@ -1300,7 +1257,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// because we don't want to actually unpublish every culture and then the document, we just want everything
|
||||
// to be non-routable so that when it's re-published all variants were as they were.
|
||||
content.PublishedState = PublishedState.Unpublishing;
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
@@ -1314,7 +1271,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
var removed = content.UnpublishCulture(culture);
|
||||
|
||||
// Save and publish any changes
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
@@ -1332,7 +1289,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a document and publishes/unpublishes any pending publishing changes made to the document.
|
||||
/// Publishes/unpublishes any pending publishing changes made to the document.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -1365,15 +1322,9 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var savingNotification = new ContentSavingNotification(content, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
}
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
@@ -1385,7 +1336,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
/// <param name="scope"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="allLangs"></param>
|
||||
/// <param name="notificationState"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="branchOne"></param>
|
||||
/// <param name="branchRoot"></param>
|
||||
@@ -1404,8 +1354,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
IContent content,
|
||||
EventMessages eventMessages,
|
||||
IReadOnlyCollection<ILanguage> allLangs,
|
||||
IDictionary<string, object?>? notificationState,
|
||||
int userId = Constants.Security.SuperUserId,
|
||||
int userId,
|
||||
out IDictionary<string, object?>? initialNotificationState,
|
||||
bool branchOne = false,
|
||||
bool branchRoot = false)
|
||||
{
|
||||
@@ -1467,6 +1417,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
_documentRepository.Save(c);
|
||||
}
|
||||
|
||||
initialNotificationState = null;
|
||||
if (publishing)
|
||||
{
|
||||
// Determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
|
||||
@@ -1484,7 +1435,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
culturesUnpublishing,
|
||||
eventMessages,
|
||||
allLangs,
|
||||
notificationState);
|
||||
out initialNotificationState);
|
||||
if (publishResult.Success)
|
||||
{
|
||||
// note: StrategyPublish flips the PublishedState to Publishing!
|
||||
@@ -1547,7 +1498,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// handling events, business rules, etc
|
||||
// note: StrategyUnpublish flips the PublishedState to Unpublishing!
|
||||
// note: This unpublishes the entire document (not different variants)
|
||||
unpublishResult = StrategyCanUnpublish(scope, content, eventMessages, notificationState);
|
||||
unpublishResult = StrategyCanUnpublish(scope, content, eventMessages, out initialNotificationState);
|
||||
if (unpublishResult.Success)
|
||||
{
|
||||
unpublishResult = StrategyUnpublish(content, eventMessages);
|
||||
@@ -1560,6 +1511,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// PublishState to anything other than Publishing or Unpublishing - which is precisely
|
||||
// what we want to do here - throws
|
||||
content.Published = content.Published;
|
||||
return unpublishResult;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1574,10 +1526,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
// Persist the document
|
||||
SaveDocument(content);
|
||||
|
||||
// raise the Saved event, always
|
||||
scope.Notifications.Publish(
|
||||
new ContentSavedNotification(content, eventMessages).WithState(notificationState));
|
||||
|
||||
// we have tried to unpublish - won't happen in a branch
|
||||
if (unpublishing)
|
||||
{
|
||||
@@ -1586,7 +1534,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
{
|
||||
// events and audit
|
||||
scope.Notifications.Publish(
|
||||
new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState));
|
||||
new ContentUnpublishedNotification(content, eventMessages).WithState(initialNotificationState));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
|
||||
|
||||
if (culturesUnpublishing != null)
|
||||
@@ -1648,7 +1596,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
scope.Notifications.Publish(
|
||||
new ContentTreeChangeNotification(content, changeType, eventMessages));
|
||||
scope.Notifications.Publish(
|
||||
new ContentPublishedNotification(content, eventMessages).WithState(notificationState));
|
||||
new ContentPublishedNotification(content, eventMessages).WithState(initialNotificationState));
|
||||
}
|
||||
|
||||
// it was not published and now is... descendants that were 'published' (but
|
||||
@@ -1658,7 +1606,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
{
|
||||
IContent[] descendants = GetPublishedDescendantsLocked(content).ToArray();
|
||||
scope.Notifications.Publish(
|
||||
new ContentPublishedNotification(descendants, eventMessages).WithState(notificationState));
|
||||
new ContentPublishedNotification(descendants, eventMessages).WithState(initialNotificationState));
|
||||
}
|
||||
|
||||
switch (publishResult.Result)
|
||||
@@ -1758,13 +1706,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
continue; // shouldn't happen but no point in processing this document if there's nothing there
|
||||
}
|
||||
|
||||
var savingNotification = new ContentSavingNotification(d, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var c in pendingCultures)
|
||||
{
|
||||
// Clear this schedule for this culture
|
||||
@@ -1775,7 +1716,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
_documentRepository.PersistContentSchedule(d, contentSchedule);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId, out _);
|
||||
if (result.Success == false)
|
||||
{
|
||||
_logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
|
||||
@@ -1830,13 +1771,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
continue; // shouldn't happen but no point in processing this document if there's nothing there
|
||||
}
|
||||
|
||||
var savingNotification = new ContentSavingNotification(d, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
|
||||
continue;
|
||||
}
|
||||
|
||||
var publishing = true;
|
||||
foreach (var culture in pendingCultures)
|
||||
{
|
||||
@@ -1881,7 +1815,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
else
|
||||
{
|
||||
_documentRepository.PersistContentSchedule(d, contentSchedule);
|
||||
result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId);
|
||||
result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId, out _);
|
||||
}
|
||||
|
||||
if (result.Success == false)
|
||||
@@ -1905,7 +1839,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
else
|
||||
{
|
||||
_documentRepository.PersistContentSchedule(d, contentSchedule);
|
||||
result = SaveAndPublish(d, userId: d.WriterId);
|
||||
result = Publish(d, d.AvailableCultures.ToArray(), userId: d.WriterId);
|
||||
}
|
||||
|
||||
if (result.Success == false)
|
||||
@@ -1924,10 +1858,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// utility 'PublishCultures' func used by SaveAndPublishBranch
|
||||
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish, IReadOnlyCollection<ILanguage> allLangs)
|
||||
private bool PublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish, IReadOnlyCollection<ILanguage> allLangs)
|
||||
{
|
||||
// TODO: Th is does not support being able to return invalid property details to bubble up to the UI
|
||||
|
||||
// variant content type - publish specified cultures
|
||||
// invariant content type - publish only the invariant culture
|
||||
if (content.ContentType.VariesByCulture())
|
||||
@@ -1945,7 +1877,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// utility 'ShouldPublish' func used by SaveAndPublishBranch
|
||||
private HashSet<string>? SaveAndPublishBranch_ShouldPublish(ref HashSet<string>? cultures, string c, bool published, bool edited, bool isRoot, bool force)
|
||||
private HashSet<string>? PublishBranch_ShouldPublish(ref HashSet<string>? cultures, string c, bool published, bool edited, bool isRoot, bool force)
|
||||
{
|
||||
// if published, republish
|
||||
if (published)
|
||||
@@ -1978,7 +1910,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
return cultures;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"This method no longer saves content, only publishes it. Please use {nameof(PublishBranch)} instead. Will be removed in V16")]
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
// note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
|
||||
@@ -1998,13 +1930,13 @@ public class ContentService : RepositoryService, IContentService
|
||||
// invariant content type
|
||||
if (!c.ContentType.VariesByCulture())
|
||||
{
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
|
||||
return PublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
|
||||
}
|
||||
|
||||
// variant content type, specific culture
|
||||
if (culture != "*")
|
||||
{
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
|
||||
return PublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
|
||||
}
|
||||
|
||||
// variant content type, all cultures
|
||||
@@ -2014,7 +1946,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// others will have to 'republish this culture'
|
||||
foreach (var x in c.AvailableCultures)
|
||||
{
|
||||
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
|
||||
PublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
|
||||
}
|
||||
|
||||
return culturesToPublish;
|
||||
@@ -2026,16 +1958,25 @@ public class ContentService : RepositoryService, IContentService
|
||||
: null; // null means 'nothing to do'
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
|
||||
return PublishBranch(content, force, ShouldPublish, PublishBranch_PublishCultures, userId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"This method no longer saves content, only publishes it. Please use {nameof(PublishBranch)} instead. Will be removed in V16")]
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
=> PublishBranch(content, force, cultures, userId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> PublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
// note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
|
||||
// and not to == them, else we would be comparing references, and that is a bad thing
|
||||
cultures = cultures ?? Array.Empty<string>();
|
||||
|
||||
if (content.ContentType.VariesByCulture() is false && cultures.Length == 0)
|
||||
{
|
||||
cultures = new[] { "*" };
|
||||
}
|
||||
|
||||
// determines cultures to be published
|
||||
// can be: null (content is not impacted), an empty set (content is impacted but already published), or cultures
|
||||
HashSet<string>? ShouldPublish(IContent c)
|
||||
@@ -2046,7 +1987,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// invariant content type
|
||||
if (!c.ContentType.VariesByCulture())
|
||||
{
|
||||
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
|
||||
return PublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
|
||||
}
|
||||
|
||||
// variant content type, specific cultures
|
||||
@@ -2056,7 +1997,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// others will have to 'republish this culture'
|
||||
foreach (var x in cultures)
|
||||
{
|
||||
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
|
||||
PublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
|
||||
}
|
||||
|
||||
return culturesToPublish;
|
||||
@@ -2068,10 +2009,10 @@ public class ContentService : RepositoryService, IContentService
|
||||
: null; // null means 'nothing to do'
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
|
||||
return PublishBranch(content, force, ShouldPublish, PublishBranch_PublishCultures, userId);
|
||||
}
|
||||
|
||||
internal IEnumerable<PublishResult> SaveAndPublishBranch(
|
||||
internal IEnumerable<PublishResult> PublishBranch(
|
||||
IContent document,
|
||||
bool force,
|
||||
Func<IContent, HashSet<string>?> shouldPublish,
|
||||
@@ -2092,6 +2033,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
var results = new List<PublishResult>();
|
||||
var publishedDocuments = new List<IContent>();
|
||||
|
||||
IDictionary<string, object?>? initialNotificationState = null;
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
@@ -2110,7 +2052,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// deal with the branch root - if it fails, abort
|
||||
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary<string, object?> notificationState);
|
||||
PublishResult? result = PublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out initialNotificationState);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -2145,7 +2087,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// no need to check path here, parent has to be published here
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
|
||||
result = PublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -2169,7 +2111,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// (SaveAndPublishBranchOne does *not* do it)
|
||||
scope.Notifications.Publish(
|
||||
new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages));
|
||||
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages).WithState(notificationState));
|
||||
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages, true).WithState(initialNotificationState));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -2180,7 +2122,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// shouldPublish: a function determining whether the document has changes that need to be published
|
||||
// note - 'force' is handled by 'editing'
|
||||
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
|
||||
private PublishResult? SaveAndPublishBranchItem(
|
||||
private PublishResult? PublishBranchItem(
|
||||
ICoreScope scope,
|
||||
IContent document,
|
||||
Func<IContent, HashSet<string>?> shouldPublish,
|
||||
@@ -2191,11 +2133,18 @@ public class ContentService : RepositoryService, IContentService
|
||||
EventMessages evtMsgs,
|
||||
int userId,
|
||||
IReadOnlyCollection<ILanguage> allLangs,
|
||||
out IDictionary<string, object?> notificationState)
|
||||
out IDictionary<string, object?>? initialNotificationState)
|
||||
{
|
||||
notificationState = new Dictionary<string, object?>();
|
||||
HashSet<string>? culturesToPublish = shouldPublish(document);
|
||||
|
||||
initialNotificationState = null;
|
||||
|
||||
// we need to guard against unsaved changes before proceeding; the document will be saved, but we're not firing any saved notifications
|
||||
if (HasUnsavedChanges(document))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, evtMsgs, document);
|
||||
}
|
||||
|
||||
// null = do not include
|
||||
if (culturesToPublish == null)
|
||||
{
|
||||
@@ -2208,12 +2157,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
|
||||
}
|
||||
|
||||
var savingNotification = new ContentSavingNotification(document, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
|
||||
}
|
||||
|
||||
// publish & check if values are valid
|
||||
if (!publishCultures(document, culturesToPublish, allLangs))
|
||||
{
|
||||
@@ -2221,11 +2164,10 @@ public class ContentService : RepositoryService, IContentService
|
||||
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
|
||||
}
|
||||
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, true, isRoot);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, userId, out initialNotificationState, true, isRoot);
|
||||
if (result.Success)
|
||||
{
|
||||
publishedDocuments.Add(document);
|
||||
notificationState = savingNotification.State;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -2967,6 +2909,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
return OperationResult.Succeed(eventMessages);
|
||||
}
|
||||
|
||||
private bool HasUnsavedChanges(IContent content) => content.HasIdentity is false || content.IsDirty();
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
@@ -3057,7 +3001,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
/// <param name="evtMsgs"></param>
|
||||
/// <param name="culturesPublishing"></param>
|
||||
/// <param name="allLangs"></param>
|
||||
/// <param name="notificationState"></param>
|
||||
/// <returns></returns>
|
||||
private PublishResult StrategyCanPublish(
|
||||
ICoreScope scope,
|
||||
@@ -3067,11 +3010,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
IReadOnlyCollection<string>? culturesUnpublishing,
|
||||
EventMessages evtMsgs,
|
||||
IReadOnlyCollection<ILanguage> allLangs,
|
||||
IDictionary<string, object?>? notificationState)
|
||||
out IDictionary<string, object?>? initialNotificationState)
|
||||
{
|
||||
// raise Publishing notification
|
||||
if (scope.Notifications.PublishCancelable(
|
||||
new ContentPublishingNotification(content, evtMsgs).WithState(notificationState)))
|
||||
var notification = new ContentPublishingNotification(content, evtMsgs);
|
||||
var notificationResult = scope.Notifications.PublishCancelable(notification);
|
||||
initialNotificationState = notification.State;
|
||||
|
||||
if (notificationResult)
|
||||
{
|
||||
_logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled");
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
@@ -3310,10 +3256,18 @@ public class ContentService : RepositoryService, IContentService
|
||||
/// <param name="content"></param>
|
||||
/// <param name="evtMsgs"></param>
|
||||
/// <returns></returns>
|
||||
private PublishResult StrategyCanUnpublish(ICoreScope scope, IContent content, EventMessages evtMsgs, IDictionary<string, object?>? notificationState)
|
||||
private PublishResult StrategyCanUnpublish(
|
||||
ICoreScope scope,
|
||||
IContent content,
|
||||
EventMessages evtMsgs,
|
||||
out IDictionary<string, object?>? initialNotificationState)
|
||||
{
|
||||
// raise Unpublishing notification
|
||||
if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs).WithState(notificationState)))
|
||||
var notification = new ContentUnpublishingNotification(content, evtMsgs);
|
||||
var notificationResult = scope.Notifications.PublishCancelable(notification);
|
||||
initialNotificationState = notification.State;
|
||||
|
||||
if (notificationResult)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id);
|
||||
|
||||
Reference in New Issue
Block a user