Ensures the whole doc gets unpublished if a mandatory culture is unpublished, updates content service Unpublish to have it's own status result enum since that is needed.

This commit is contained in:
Shannon
2018-05-08 00:37:41 +10:00
parent 5a991c9424
commit 97cb752cda
11 changed files with 141 additions and 44 deletions

View File

@@ -364,7 +364,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Unpublishes a document or optionally unpublishes a culture and/or segment for the document.
/// </summary>
PublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0);
UnpublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0);
/// <summary>
/// Gets a value indicating whether a document is path-publishable.

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Core.Services.Implement
private readonly IAuditRepository _auditRepository;
private readonly IContentTypeRepository _contentTypeRepository;
private readonly IDocumentBlueprintRepository _documentBlueprintRepository;
private readonly ILanguageRepository _languageRepository;
private readonly MediaFileSystem _mediaFileSystem;
private IQuery<IContent> _queryNotTrashed;
@@ -35,7 +35,7 @@ namespace Umbraco.Core.Services.Implement
public ContentService(IScopeProvider provider, ILogger logger,
IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem,
IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository,
IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository)
IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository)
: base(provider, logger, eventMessagesFactory)
{
_mediaFileSystem = mediaFileSystem;
@@ -44,6 +44,7 @@ namespace Umbraco.Core.Services.Implement
_auditRepository = auditRepository;
_contentTypeRepository = contentTypeRepository;
_documentBlueprintRepository = documentBlueprintRepository;
_languageRepository = languageRepository;
}
#endregion
@@ -1025,7 +1026,7 @@ namespace Umbraco.Core.Services.Implement
}
/// <inheritdoc />
public PublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0)
public UnpublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0)
{
var evtMsgs = EventMessagesFactory.Get();
@@ -1038,7 +1039,13 @@ namespace Umbraco.Core.Services.Implement
if (tryUnpublishVariation) return tryUnpublishVariation.Result;
//continue the normal Unpublish operation to unpublish the entire content item
return UnpublishInternal(scope, content, evtMsgs, userId);
var result = UnpublishInternal(scope, content, evtMsgs, userId);
//not succesful, so return it
if (!result.Success) return result;
//else check if there was a status returned from TryUnpublishVariationInternal and if so use that
return tryUnpublishVariation.Result ?? result;
}
}
@@ -1055,7 +1062,7 @@ namespace Umbraco.Core.Services.Implement
/// A successful attempt if a variant was unpublished and it wasn't the last, else a failed result if it's an invariant document or if it's the last.
/// A failed result indicates that a normal unpublish operation will need to be performed.
/// </returns>
private Attempt<PublishResult> TryUnpublishVariationInternal(IScope scope, IContent content, EventMessages evtMsgs, string culture, string segment, int userId)
private Attempt<UnpublishResult> TryUnpublishVariationInternal(IScope scope, IContent content, EventMessages evtMsgs, string culture, string segment, int userId)
{
if (!culture.IsNullOrWhiteSpace() || !segment.IsNullOrWhiteSpace())
{
@@ -1064,16 +1071,27 @@ namespace Umbraco.Core.Services.Implement
{
//capture before we clear the values
var publishedCultureCount = content.PublishedCultures.Count();
//we need to unpublish everything if this is a mandatory language
var unpublishAll = _languageRepository.GetMany().Where(x => x.Mandatory).Select(x => x.IsoCode).Contains(culture, StringComparer.InvariantCultureIgnoreCase);
//fixme - this needs to be changed when 11227 is merged!
content.ClearPublishedValues(culture, segment);
//now we just publish with the name cleared
SaveAndPublish(content, userId);
Audit(AuditType.UnPublish, $"UnPublish variation culture: {culture ?? string.Empty}, segment: {segment ?? string.Empty} performed by user", userId, content.Id);
//This is not the last one, so complete the scope and return
if (publishedCultureCount > 1)
//We don't need to unpublish all and this is not the last one, so complete the scope and return
if (!unpublishAll && publishedCultureCount > 1)
{
scope.Complete();
return Attempt.Succeed(new PublishResult(PublishResultType.SuccessVariant, evtMsgs, content));
return Attempt.Succeed(new UnpublishResult(UnpublishResultType.SuccessVariant, evtMsgs, content));
}
if (unpublishAll)
{
//return a fail with a custom status here so the normal unpublish operation takes place but the result will be this flag
return Attempt.Fail(new UnpublishResult(UnpublishResultType.SuccessMandatoryCulture, evtMsgs, content));
}
}
@@ -1085,8 +1103,8 @@ namespace Umbraco.Core.Services.Implement
}
}
//This is either a non variant document or this is the last one, so return a failed result which indicates that a normal unpublish operation needs to also take place
return Attempt.Fail<PublishResult>();
//This is either a non variant document or this is the last one or this was a mandatory variant, so return a failed result which indicates that a normal unpublish operation needs to also take place
return Attempt.Fail<UnpublishResult>();
}
/// <summary>
@@ -1097,7 +1115,7 @@ namespace Umbraco.Core.Services.Implement
/// <param name="evtMsgs"></param>
/// <param name="userId"></param>
/// <returns></returns>
private PublishResult UnpublishInternal(IScope scope, IContent content, EventMessages evtMsgs, int userId)
private UnpublishResult UnpublishInternal(IScope scope, IContent content, EventMessages evtMsgs, int userId)
{
var newest = GetById(content.Id); // ensure we have the newest version
if (content.VersionId != newest.VersionId) // but use the original object if it's already the newest version
@@ -1105,7 +1123,7 @@ namespace Umbraco.Core.Services.Implement
if (content.Published == false)
{
scope.Complete();
return new PublishResult(PublishResultType.SuccessAlready, evtMsgs, content); // already unpublished
return new UnpublishResult(UnpublishResultType.SuccessAlready, evtMsgs, content); // already unpublished
}
// strategy
@@ -1123,7 +1141,7 @@ namespace Umbraco.Core.Services.Implement
Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id);
scope.Complete();
return new PublishResult(PublishResultType.Success, evtMsgs, content);
return new UnpublishResult(evtMsgs, content);
}
/// <inheritdoc />
@@ -2142,23 +2160,23 @@ namespace Umbraco.Core.Services.Implement
}
// ensures that a document can be unpublished
internal PublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs)
internal UnpublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs)
{
// raise UnPublishing event
if (scope.Events.DispatchCancelable(UnPublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
{
Logger.Info<ContentService>($"Document \"{content.Name}\" (id={content.Id}) cannot be unpublished: unpublishing was cancelled.");
return new PublishResult(PublishResultType.FailedCancelledByEvent, evtMsgs, content);
return new UnpublishResult(UnpublishResultType.FailedCancelledByEvent, evtMsgs, content);
}
return new PublishResult(evtMsgs, content);
return new UnpublishResult(evtMsgs, content);
}
// unpublishes a document
internal PublishResult StrategyUnpublish(IScope scope, IContent content, bool canUnpublish, int userId, EventMessages evtMsgs)
internal UnpublishResult StrategyUnpublish(IScope scope, IContent content, bool canUnpublish, int userId, EventMessages evtMsgs)
{
var attempt = canUnpublish
? new PublishResult(evtMsgs, content) // already know we can
? new UnpublishResult(evtMsgs, content) // already know we can
: StrategyCanUnpublish(scope, content, userId, evtMsgs); // else check
if (attempt.Success == false)

View File

@@ -4,6 +4,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
/// <summary>
/// Represents the result of publishing a document.
/// </summary>

View File

@@ -1,7 +1,8 @@
namespace Umbraco.Core.Services
{
/// <summary>
/// A value indicating the result of (un)publishing a content item.
/// A value indicating the result of publishing a content item.
/// </summary>
public enum PublishResultType : byte
{
@@ -9,20 +10,15 @@
// every failure codes as >128 - see OperationResult and OperationResultType for details.
/// <summary>
/// The (un)publishing was successful.
/// The publishing was successful.
/// </summary>
Success = 0,
/// <summary>
/// The item was already (un)published.
/// The item was already published.
/// </summary>
SuccessAlready = 1,
/// <summary>
/// The specified variant was unpublished, the content item itself remains published.
/// </summary>
SuccessVariant = 2,
/// <summary>
/// The operation failed.
/// </summary>

View File

@@ -0,0 +1,29 @@
using Umbraco.Core.Events;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
/// <summary>
/// Represents the result of unpublishing a document.
/// </summary>
public class UnpublishResult : OperationResult<UnpublishResultType, IContent>
{
/// <summary>
/// Creates a successful result
/// </summary>
/// <param name="eventMessages"></param>
/// <param name="entity"></param>
public UnpublishResult(EventMessages eventMessages, IContent entity) : base(UnpublishResultType.Success, eventMessages, entity)
{
}
public UnpublishResult(UnpublishResultType result, EventMessages eventMessages, IContent entity) : base(result, eventMessages, entity)
{
}
/// <summary>
/// Gets the document.
/// </summary>
public IContent Content => Entity;
}
}

View File

@@ -0,0 +1,39 @@
namespace Umbraco.Core.Services
{
/// <summary>
/// A value indicating the result of unpublishing a content item.
/// </summary>
public enum UnpublishResultType : byte
{
/// <summary>
/// The unpublishing was successful.
/// </summary>
Success = 0,
/// <summary>
/// The item was already unpublished.
/// </summary>
SuccessAlready = 1,
/// <summary>
/// The specified variant was unpublished, the content item itself remains published.
/// </summary>
SuccessVariant = 2,
/// <summary>
/// The specified variant was a mandatory culture therefore it was unpublished and the content item itself is unpublished
/// </summary>
SuccessMandatoryCulture = 3,
/// <summary>
/// The operation failed.
/// </summary>
/// <remarks>All values above this value indicate a failure.</remarks>
Failed = 128,
/// <summary>
/// The publish action has been cancelled by an event handler.
/// </summary>
FailedCancelledByEvent = Failed | 5,
}
}

View File

@@ -1420,6 +1420,8 @@
<Compile Include="Services\Implement\TagService.cs" />
<Compile Include="Services\Implement\TaskService.cs" />
<Compile Include="Services\Implement\UserService.cs" />
<Compile Include="Services\UnpublishResult.cs" />
<Compile Include="Services\UnpublishResultType.cs" />
<Compile Include="Services\UserServiceExtensions.cs" />
<Compile Include="Settable.cs" />
<Compile Include="Strategies\ManifestWatcherComponent.cs" />

View File

@@ -161,7 +161,7 @@ namespace Umbraco.Tests.TestHelpers
var userService = GetLazyService<IUserService>(container, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo<IUserRepository>(c), GetRepo<IUserGroupRepository>(c),globalSettings));
var dataTypeService = GetLazyService<IDataTypeService>(container, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo<IDataTypeRepository>(c), GetRepo<IDataTypeContainerRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IEntityRepository>(c), GetRepo<IContentTypeRepository>(c)));
var contentService = GetLazyService<IContentService>(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo<IDocumentRepository>(c), GetRepo<IEntityRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IContentTypeRepository>(c), GetRepo<IDocumentBlueprintRepository>(c)));
var contentService = GetLazyService<IContentService>(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo<IDocumentRepository>(c), GetRepo<IEntityRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IContentTypeRepository>(c), GetRepo<IDocumentBlueprintRepository>(c), GetRepo<ILanguageRepository>(c)));
var notificationService = GetLazyService<INotificationService>(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, logger, GetRepo<INotificationsRepository>(c),globalSettings));
var serverRegistrationService = GetLazyService<IServerRegistrationService>(container, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo<IServerRegistrationRepository>(c)));
var memberGroupService = GetLazyService<IMemberGroupService>(container, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo<IMemberGroupRepository>(c)));

View File

@@ -1385,7 +1385,8 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="templateErrorText">Please make sure that you do not have 2 templates with the same alias</key>
<key alias="templateSavedHeader">Template saved</key>
<key alias="templateSavedText">Template saved without any errors!</key>
<key alias="contentUnpublished">Content unpublished</key>
<key alias="contentUnpublished">Content unpublished</key>
<key alias="contentVariationUnpublished">Content variation %0% unpublished</key>
<key alias="partialViewSavedHeader">Partial view saved</key>
<key alias="partialViewSavedText">Partial view saved without any errors!</key>
<key alias="partialViewErrorHeader">Partial view not saved</key>

View File

@@ -243,10 +243,10 @@ namespace Umbraco.Web.Editors
//set a custom path since the tree that renders this has the content type id as the parent
content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id);
content.AllowedActions = new[] {"A"};
content.AllowedActions = new[] { "A" };
content.IsBlueprint = true;
var excludeProps = new[] {"_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template"};
var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" };
var propsTab = content.Tabs.Last();
propsTab.Properties = propsTab.Properties
.Where(p => excludeProps.Contains(p.Alias) == false);
@@ -300,7 +300,7 @@ namespace Umbraco.Web.Editors
//Remove all variants except for the default since currently the default must be saved before other variants can be edited
//TODO: Allow for editing all variants at once ... this will be a future task
mapped.Variants = new[] {mapped.Variants.First(x => x.IsCurrent)};
mapped.Variants = new[] { mapped.Variants.First(x => x.IsCurrent) };
return mapped;
}
@@ -515,7 +515,7 @@ namespace Umbraco.Web.Editors
var notificationModel = new SimpleNotificationModel();
notificationModel.AddSuccessNotification(
Services.TextService.Localize("blueprints/createdBlueprintHeading"),
Services.TextService.Localize("blueprints/createdBlueprintMessage", new[]{ content.Name})
Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name })
);
return notificationModel;
@@ -944,23 +944,30 @@ namespace Umbraco.Web.Editors
if (foundContent == null)
HandleContentNotFound(id);
var unpublishResult = Services.ContentService.Unpublish(foundContent, culture:culture, userId: Security.CurrentUser.Id);
var unpublishResult = Services.ContentService.Unpublish(foundContent, culture: culture, userId: Security.CurrentUser.Id);
var content = MapToDisplay(foundContent, culture);
if (unpublishResult.Success == false)
if (!unpublishResult.Success)
{
AddCancelMessage(content);
throw new HttpResponseException(Request.CreateValidationErrorResponse(content));
}
else
{
content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished"));
{
//fixme should have a better localized method for when we have the UnpublishResultType.SuccessMandatoryCulture status
content.AddSuccessNotification(
Services.TextService.Localize("content/unPublish"),
unpublishResult.Result == UnpublishResultType.SuccessVariant
? Services.TextService.Localize("speechBubbles/contentVariationUnpublished", new[] { culture })
: Services.TextService.Localize("speechBubbles/contentUnpublished"));
return content;
}
}
}
/// <summary>
/// Maps the dto property values to the persisted model
/// </summary>
@@ -1215,7 +1222,7 @@ namespace Umbraco.Web.Editors
new Dictionary<string, object> { { ContextMapper.CultureKey, culture } });
return display;
}
}
}
}

View File

@@ -35,7 +35,11 @@ namespace Umbraco.Web.Models.Mapping
Mandatory = x.Mandatory,
Name = source.GetName(x.IsoCode),
Exists = source.IsCultureAvailable(x.IsoCode), // segments ??
PublishedState = (source.IsCulturePublished(x.IsoCode) ? PublishedState.Published : PublishedState.Unpublished).ToString(),
PublishedState = (source.PublishedState == PublishedState.Unpublished //if the entire document is unpublished, then flag every variant as unpublished
? PublishedState.Unpublished
: source.IsCulturePublished(x.IsoCode)
? PublishedState.Published
: PublishedState.Unpublished).ToString(),
IsEdited = source.IsCultureEdited(x.IsoCode)
//Segment = ?? We'll need to populate this one day when we support segments
}).ToList();