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:
Kenn Jacobsen
2023-11-22 12:52:08 +01:00
committed by GitHub
parent 42dd2da579
commit 012b43a1c2
41 changed files with 2017 additions and 567 deletions

View File

@@ -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);