Merge remote-tracking branch 'origin/temp8-3336-publish-branch' into temp8-224-db-updates-sched-publishing-with-variants
# Conflicts: # src/Umbraco.Core/Services/Implement/ContentService.cs
This commit is contained in:
@@ -953,6 +953,17 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
/// <inheritdoc />
|
||||
public PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
var result = SavePublishingInternal(scope, content, userId, raiseEvents);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private PublishResult SavePublishingInternal(IScope scope, IContent content, int userId = 0, bool raiseEvents = true)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
PublishResult publishResult = null;
|
||||
@@ -960,7 +971,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// nothing set = republish it all
|
||||
if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing)
|
||||
((Content) content).PublishedState = PublishedState.Publishing;
|
||||
((Content)content).PublishedState = PublishedState.Publishing;
|
||||
|
||||
// state here is either Publishing or Unpublishing
|
||||
var publishing = content.PublishedState == PublishedState.Publishing;
|
||||
@@ -978,27 +989,22 @@ namespace Umbraco.Core.Services.Implement
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
|
||||
var isNew = !content.HasIdentity;
|
||||
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
|
||||
var previouslyPublished = content.HasIdentity && content.Published;
|
||||
var isNew = !content.HasIdentity;
|
||||
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
|
||||
var previouslyPublished = content.HasIdentity && content.Published;
|
||||
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
// always save
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(content, evtMsgs);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
|
||||
{
|
||||
scope.Complete();
|
||||
// always save
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(content, evtMsgs);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
}
|
||||
|
||||
if (publishing)
|
||||
{
|
||||
if (publishing)
|
||||
{
|
||||
culturesUnpublishing = content.GetCulturesUnpublishing();
|
||||
|
||||
// ensure that the document can be published, and publish handling events, business rules, etc
|
||||
publishResult = StrategyCanPublish(scope, content, userId, /*checkPath:*/ true, evtMsgs);
|
||||
if (publishResult.Success)
|
||||
publishResult = StrategyCanPublish(scope, content, userId, /*checkPath:*/ true, evtMsgs);
|
||||
if (publishResult.Success)
|
||||
{
|
||||
culturesPublishing = variesByCulture
|
||||
? content.PublishCultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key).ToList()
|
||||
@@ -1025,20 +1031,20 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
if (unpublishing)
|
||||
{
|
||||
var newest = GetById(content.Id); // ensure we have the newest version - in scope
|
||||
if (content.VersionId != newest.VersionId) // but use the original object if it's already the newest version
|
||||
content = newest;
|
||||
if (unpublishing)
|
||||
{
|
||||
var newest = GetById(content.Id); // ensure we have the newest version - in scope
|
||||
if (content.VersionId != newest.VersionId) // but use the original object if it's already the newest version
|
||||
content = newest;
|
||||
|
||||
if (content.Published)
|
||||
{
|
||||
// ensure that the document can be unpublished, and unpublish
|
||||
// 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, userId, evtMsgs);
|
||||
if (unpublishResult.Success)
|
||||
if (content.Published)
|
||||
{
|
||||
// ensure that the document can be unpublished, and unpublish
|
||||
// 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, userId, evtMsgs);
|
||||
if (unpublishResult.Success)
|
||||
unpublishResult = StrategyUnpublish(scope, content, userId, evtMsgs);
|
||||
else
|
||||
{
|
||||
@@ -1046,37 +1052,37 @@ namespace Umbraco.Core.Services.Implement
|
||||
((Content)content).Published = content.Published; // reset published state = save unchanged
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// already unpublished - optimistic concurrency collision, really,
|
||||
// and I am not sure at all what we should do, better die fast, else
|
||||
// we may end up corrupting the db
|
||||
throw new InvalidOperationException("Concurrency collision.");
|
||||
}
|
||||
}
|
||||
|
||||
// save, always
|
||||
if (content.HasIdentity == false)
|
||||
content.CreatorId = userId;
|
||||
content.WriterId = userId;
|
||||
|
||||
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
|
||||
_documentRepository.Save(content);
|
||||
|
||||
// raise the Saved event, always
|
||||
if (raiseEvents)
|
||||
else
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
// already unpublished - optimistic concurrency collision, really,
|
||||
// and I am not sure at all what we should do, better die fast, else
|
||||
// we may end up corrupting the db
|
||||
throw new InvalidOperationException("Concurrency collision.");
|
||||
}
|
||||
}
|
||||
|
||||
if (unpublishing) // we have tried to unpublish
|
||||
// save, always
|
||||
if (content.HasIdentity == false)
|
||||
content.CreatorId = userId;
|
||||
content.WriterId = userId;
|
||||
|
||||
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
|
||||
_documentRepository.Save(content);
|
||||
|
||||
// raise the Saved event, always
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
}
|
||||
|
||||
if (unpublishing) // we have tried to unpublish
|
||||
{
|
||||
if (unpublishResult.Success) // and succeeded, trigger events
|
||||
{
|
||||
if (unpublishResult.Success) // and succeeded, trigger events
|
||||
{
|
||||
// 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());
|
||||
// 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());
|
||||
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
@@ -1091,35 +1097,33 @@ namespace Umbraco.Core.Services.Implement
|
||||
else
|
||||
Audit(AuditType.Unpublish, userId, content.Id);
|
||||
|
||||
scope.Complete();
|
||||
return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
|
||||
}
|
||||
|
||||
// or, failed
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
scope.Complete(); // compete the save
|
||||
return new PublishResult(PublishResultType.FailedUnpublish, evtMsgs, content); // bah
|
||||
}
|
||||
|
||||
if (publishing) // we have tried to publish
|
||||
// or, failed
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
return new PublishResult(PublishResultType.FailedUnpublish, evtMsgs, content); // bah
|
||||
}
|
||||
|
||||
if (publishing) // we have tried to publish
|
||||
{
|
||||
if (publishResult.Success) // and succeeded, trigger events
|
||||
{
|
||||
if (publishResult.Success) // and succeeded, trigger events
|
||||
if (isNew == false && previouslyPublished == false)
|
||||
changeType = TreeChangeTypes.RefreshBranch; // whole branch
|
||||
|
||||
// invalidate the node/branch
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(content, false, false), "Published");
|
||||
|
||||
// if was not published and now is... descendants that were 'published' (but
|
||||
// had an unpublished ancestor) are 're-published' ie not explicitely published
|
||||
// but back as 'published' nevertheless
|
||||
if (isNew == false && previouslyPublished == false && HasChildren(content.Id))
|
||||
{
|
||||
if (isNew == false && previouslyPublished == false)
|
||||
changeType = TreeChangeTypes.RefreshBranch; // whole branch
|
||||
|
||||
// invalidate the node/branch
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(content, false, false), "Published");
|
||||
|
||||
// if was not published and now is... descendants that were 'published' (but
|
||||
// had an unpublished ancestor) are 're-published' ie not explicitely published
|
||||
// but back as 'published' nevertheless
|
||||
if (isNew == false && previouslyPublished == false && HasChildren(content.Id))
|
||||
{
|
||||
var descendants = GetPublishedDescendantsLocked(content).ToArray();
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(descendants, false, false), "Published");
|
||||
}
|
||||
var descendants = GetPublishedDescendantsLocked(content).ToArray();
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(descendants, false, false), "Published");
|
||||
}
|
||||
|
||||
switch(publishResult.Result)
|
||||
{
|
||||
@@ -1146,9 +1150,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
break;
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
return publishResult;
|
||||
}
|
||||
return publishResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1167,10 +1170,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
// or, failed
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
scope.Complete(); // compete the save
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
return publishResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1272,16 +1273,44 @@ namespace Umbraco.Core.Services.Implement
|
||||
// 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
|
||||
|
||||
bool IsEditing(IContent c, string l)
|
||||
=> c.PublishName != c.Name ||
|
||||
c.PublishedCultures.Where(x => x.InvariantEquals(l)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
|
||||
c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture.InvariantEquals(l)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
|
||||
// determines whether the document is edited, and thus needs to be published,
|
||||
// for the specified culture (it may be edited for other cultures and that
|
||||
// should not trigger a publish).
|
||||
HashSet<string> ShouldPublish(IContent c)
|
||||
{
|
||||
if (c.ContentType.VariesByCulture())
|
||||
{
|
||||
// variant content type
|
||||
// add culture if edited, and already published or forced
|
||||
if (c.IsCultureEdited(culture) && (c.IsCulturePublished(culture) || force))
|
||||
return new HashSet<string> { culture.ToLowerInvariant() };
|
||||
}
|
||||
else
|
||||
{
|
||||
// invariant content type
|
||||
// add "*" if edited, and already published or forced
|
||||
if (c.Edited && (c.Published || force))
|
||||
return new HashSet<string> { "*" };
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, document => IsEditing(document, culture), document => document.PublishCulture(culture), userId);
|
||||
return new HashSet<string>();
|
||||
}
|
||||
|
||||
// publish the specified cultures
|
||||
bool PublishCultures(IContent c, HashSet<string> culturesToPublish)
|
||||
{
|
||||
// variant content type - publish specified cultures
|
||||
// invariant content type - publish only the invariant culture
|
||||
return c.ContentType.VariesByCulture()
|
||||
? culturesToPublish.All(c.PublishCulture)
|
||||
: c.PublishCulture();
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, ShouldPublish, PublishCultures, userId);
|
||||
}
|
||||
|
||||
// fixme - make this public once we know it works + document
|
||||
private IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = 0)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = 0)
|
||||
{
|
||||
// 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
|
||||
@@ -1291,35 +1320,47 @@ namespace Umbraco.Core.Services.Implement
|
||||
// determines whether the document is edited, and thus needs to be published,
|
||||
// for the specified cultures (it may be edited for other cultures and that
|
||||
// should not trigger a publish).
|
||||
bool IsEdited(IContent c)
|
||||
HashSet<string> ShouldPublish(IContent c)
|
||||
{
|
||||
if (cultures.Length == 0)
|
||||
var culturesToPublish = new HashSet<string>();
|
||||
|
||||
if (c.ContentType.VariesByCulture())
|
||||
{
|
||||
// nothing = everything
|
||||
return c.PublishName != c.Name ||
|
||||
c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
|
||||
c.Properties.Any(x => x.Values.Any(y => !y.EditedValue.Equals(y.PublishedValue)));
|
||||
// variant content type
|
||||
// add cultures which are edited, and already published or forced
|
||||
foreach (var culture in cultures)
|
||||
{
|
||||
if (c.IsCultureEdited(culture) && (c.IsCulturePublished(culture) || force))
|
||||
culturesToPublish.Add(culture.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// invariant content type
|
||||
// add "*" if edited, and already published or forced
|
||||
if (c.Edited && (c.Published || force))
|
||||
culturesToPublish.Add("*");
|
||||
}
|
||||
|
||||
return c.PublishName != c.Name ||
|
||||
c.PublishedCultures.Where(x => cultures.Contains(x, StringComparer.InvariantCultureIgnoreCase)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
|
||||
c.Properties.Any(x => x.Values.Where(y => cultures.Contains(y.Culture, StringComparer.InvariantCultureIgnoreCase)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
|
||||
return culturesToPublish;
|
||||
}
|
||||
|
||||
// publish the specified cultures
|
||||
bool PublishCultures(IContent c)
|
||||
bool PublishCultures(IContent c, HashSet<string> culturesToPublish)
|
||||
{
|
||||
return cultures.Length == 0
|
||||
? c.PublishCulture() // nothing = everything
|
||||
: cultures.All(c.PublishCulture);
|
||||
// variant content type - publish specified cultures
|
||||
// invariant content type - publish only the invariant culture
|
||||
return c.ContentType.VariesByCulture()
|
||||
? culturesToPublish.All(c.PublishCulture)
|
||||
: c.PublishCulture();
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, IsEdited, PublishCultures, userId);
|
||||
return SaveAndPublishBranch(content, force, ShouldPublish, PublishCultures, userId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
|
||||
Func<IContent, bool> editing, Func<IContent, bool> publishCultures, int userId = 0)
|
||||
Func<IContent, HashSet<string>> shouldPublish, Func<IContent, HashSet<string>, bool> publishCultures, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
var results = new List<PublishResult>();
|
||||
@@ -1332,14 +1373,14 @@ namespace Umbraco.Core.Services.Implement
|
||||
// fixme events?!
|
||||
|
||||
if (!document.HasIdentity)
|
||||
throw new InvalidOperationException("Do not branch-publish a new document.");
|
||||
throw new InvalidOperationException("Cannot not branch-publish a new document.");
|
||||
|
||||
var publishedState = ((Content) document).PublishedState;
|
||||
if (publishedState == PublishedState.Publishing)
|
||||
throw new InvalidOperationException("Do not publish values when publishing branches.");
|
||||
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
|
||||
|
||||
// deal with the branch root - if it fails, abort
|
||||
var result = SaveAndPublishBranchOne(scope, document, editing, publishCultures, true, publishedDocuments, evtMsgs, userId);
|
||||
var result = SaveAndPublishBranchOne(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
|
||||
results.Add(result);
|
||||
if (!result.Success) return results;
|
||||
|
||||
@@ -1347,35 +1388,36 @@ namespace Umbraco.Core.Services.Implement
|
||||
// if one fails, abort its branch
|
||||
var exclude = new HashSet<int>();
|
||||
|
||||
const int pageSize = 500;
|
||||
int count;
|
||||
var page = 0;
|
||||
var total = long.MaxValue;
|
||||
while (page * pageSize < total)
|
||||
const int pageSize = 100;
|
||||
do
|
||||
{
|
||||
var descendants = GetPagedDescendants(document.Id, page++, pageSize, out total);
|
||||
|
||||
foreach (var d in descendants)
|
||||
count = 0;
|
||||
// important to order by Path ASC so make it explicit in case defaults change
|
||||
// ReSharper disable once RedundantArgumentDefaultValue
|
||||
foreach (var d in GetPagedDescendants(document.Id, page, pageSize, out _, ordering: Ordering.By("Path", Direction.Ascending)))
|
||||
{
|
||||
// if parent is excluded, exclude document and ignore
|
||||
// if not forcing, and not publishing, exclude document and ignore
|
||||
if (exclude.Contains(d.ParentId) || !force && !d.Published)
|
||||
count++;
|
||||
|
||||
// if parent is excluded, exclude child too
|
||||
if (exclude.Contains(d.ParentId))
|
||||
{
|
||||
exclude.Add(d.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// no need to check path here,
|
||||
// 1. because we know the parent is path-published (we just published it)
|
||||
// 2. because it would not work as nothing's been written out to the db until the uow completes
|
||||
result = SaveAndPublishBranchOne(scope, d, editing, publishCultures, false, publishedDocuments, evtMsgs, userId);
|
||||
// no need to check path here, parent has to be published here
|
||||
result = SaveAndPublishBranchOne(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
|
||||
results.Add(result);
|
||||
if (result.Success) continue;
|
||||
|
||||
// abort branch
|
||||
// if we could not publish the document, cut its branch
|
||||
exclude.Add(d.Id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
page++;
|
||||
} while (count > 0);
|
||||
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(document, TreeChangeTypes.RefreshBranch).ToEventArgs());
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(publishedDocuments, false, false), "Published");
|
||||
@@ -1387,21 +1429,31 @@ namespace Umbraco.Core.Services.Implement
|
||||
return results;
|
||||
}
|
||||
|
||||
// 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 SaveAndPublishBranchOne(IScope scope, IContent document,
|
||||
Func<IContent, bool> editing, Func<IContent, bool> publishValues,
|
||||
Func<IContent, HashSet<string>> shouldPublish, Func<IContent, HashSet<string>, bool> publishCultures,
|
||||
bool checkPath,
|
||||
List<IContent> publishedDocuments,
|
||||
ICollection<IContent> publishedDocuments,
|
||||
EventMessages evtMsgs, int userId)
|
||||
{
|
||||
// if already published, and values haven't changed - i.e. not changing anything
|
||||
// nothing to do - fixme - unless we *want* to bump dates?
|
||||
if (document.Published && (editing == null || !editing(document)))
|
||||
// use 'shouldPublish' to determine whether there are changes to be published
|
||||
// if the document has no changes to be published - nothing to
|
||||
// for an invariant content, shouldPublish may contain "*" to indicate changes
|
||||
var culturesToPublish = shouldPublish?.Invoke(document);
|
||||
if (culturesToPublish == null || culturesToPublish.Count == 0)
|
||||
return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
|
||||
|
||||
// there are changes to be published - publish them with 'publishCultures'
|
||||
|
||||
// publish & check if values are valid
|
||||
if (publishValues != null && !publishValues(document))
|
||||
if (publishCultures != null && !publishCultures(document, culturesToPublish))
|
||||
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
|
||||
|
||||
return SavePublishingInternal(scope, document, userId);
|
||||
|
||||
// fixme deal with the rest of this code!
|
||||
// check if we can publish
|
||||
var result = StrategyCanPublish(scope, document, userId, checkPath, evtMsgs);
|
||||
if (!result.Success)
|
||||
|
||||
Reference in New Issue
Block a user