Fixes Publishing/Unpublishing variants and it's nuances

This commit is contained in:
Shannon
2018-05-07 23:22:52 +10:00
parent 7a92691bf5
commit 5a991c9424
21 changed files with 281 additions and 128 deletions

View File

@@ -194,15 +194,23 @@ namespace Umbraco.Core.Models
Name = null;
return;
}
if (!ContentTypeBase.Variations.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment))
throw new NotSupportedException("Content type does not support varying name by culture.");
if (_names == null) return;
if (_names == null) return;
if (!_names.ContainsKey(culture))
throw new InvalidOperationException($"Cannot unpublish culture {culture}, the document contains only cultures {string.Join(", ", _names.Keys)}");
_names.Remove(culture);
if (_names.Count == 0)
_names = null;
}
protected virtual void ClearNames()
{
{
if (!ContentTypeBase.Variations.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment))
throw new NotSupportedException("Content type does not support varying name by culture.");
_names = null;
OnPropertyChanged(Ps.Value.NamesSelector);
}

View File

@@ -1,14 +1,24 @@
namespace Umbraco.Core.Models.Entities
using System.Collections.Generic;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Implements <see cref="IDocumentEntitySlim"/>.
/// </summary>
public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim
{
private static readonly IReadOnlyDictionary<string, string> Empty = new Dictionary<string, string>();
private IReadOnlyDictionary<string, string> _cultureNames;
public IReadOnlyDictionary<string, string> CultureNames
{
get => _cultureNames ?? Empty;
set => _cultureNames = value;
}
/// <inheritdoc />
public bool Published { get; set; }
/// <inheritdoc />
public bool Edited { get; set; }
}
}
}

View File

@@ -1,18 +1,30 @@
namespace Umbraco.Core.Models.Entities
using System.Collections.Generic;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Represents a lightweight document entity, managed by the entity service.
/// </summary>
public interface IDocumentEntitySlim : IContentEntitySlim
{
/// <summary>
/// Gets a value indicating whether the document is published.
/// </summary>
bool Published { get; }
//fixme we need to supply more information than this and change this property name. This will need to include Published/Editor per variation since we need this information for the tree
IReadOnlyDictionary<string, string> CultureNames { get; }
/// <summary>
/// Gets a value indicating whether the document has edited properties.
/// At least one variation is published
/// </summary>
bool Edited { get; }
/// <remarks>
/// If the document is invariant, this simply means there is a published version
/// </remarks>
bool Published { get; set; }
/// <summary>
/// At least one variation has pending changes
/// </summary>
/// <remarks>
/// If the document is invariant, this simply means there is pending changes
/// </remarks>
bool Edited { get; set; }
}
}
}

View File

@@ -1,7 +1,8 @@
using System;
namespace Umbraco.Core.Models
{
{
/// <summary>
/// The states of a content item.
/// </summary>

View File

@@ -441,6 +441,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
[ResultColumn, Reference(ReferenceType.Many)]
public List<ContentEntityVariationInfoDto> VariationInfo { get; set; }
public bool Published { get; set; }
public bool Edited { get; set; }
}
/// <summary>
@@ -477,8 +480,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public DateTime CreateDate { get; set; }
public int Children { get; set; }
public int VersionId { get; set; }
public bool Published { get; set; }
public bool Edited { get; set; }
public string Alias { get; set; }
public string Icon { get; set; }
public string Thumbnail { get; set; }
@@ -826,7 +827,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Factory
private static EntitySlim BuildEntity(bool isContent, bool isMedia, BaseDto dto)
private EntitySlim BuildEntity(bool isContent, bool isMedia, BaseDto dto)
{
if (isContent)
return BuildDocumentEntity(dto);
@@ -867,8 +868,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private static void BuildDocumentEntity(DocumentEntitySlim entity, BaseDto dto)
{
BuildContentEntity(entity, dto);
entity.Published = dto.Published;
entity.Edited = dto.Edited;
}
private static EntitySlim BuildContentEntity(BaseDto dto)
@@ -879,8 +878,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return entity;
}
private static EntitySlim BuildDocumentEntity(BaseDto dto)
private DocumentEntitySlim BuildDocumentEntity(BaseDto dto)
{
if (dto is ContentEntityDto contentDto)
{
return BuildDocumentEntity(contentDto);
}
// EntitySlim does not track changes
var entity = new DocumentEntitySlim();
BuildDocumentEntity(entity, dto);
@@ -892,11 +896,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
private EntitySlim BuildDocumentEntity(ContentEntityDto dto)
private DocumentEntitySlim BuildDocumentEntity(ContentEntityDto dto)
{
// EntitySlim does not track changes
var entity = new DocumentEntitySlim();
BuildDocumentEntity(entity, dto);
//fixme we need to set these statuses for each variant, see notes in IDocumentEntitySlim
entity.Edited = dto.Edited;
entity.Published = dto.Published;
var variantInfo = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
if (dto.VariationInfo != null)
{
@@ -906,7 +915,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isoCode != null)
variantInfo[isoCode] = info.Name;
}
entity.AdditionalData["CultureNames"] = variantInfo;
entity.CultureNames = variantInfo;
}
return entity;
}

View File

@@ -14,11 +14,13 @@ namespace Umbraco.Core.Publishing
{
private readonly IContentService _contentService;
private readonly ILogger _logger;
private readonly IUserService _userService;
public ScheduledPublisher(IContentService contentService, ILogger logger)
public ScheduledPublisher(IContentService contentService, ILogger logger, IUserService userService)
{
_contentService = contentService;
_logger = logger;
_userService = userService;
}
/// <summary>
@@ -40,7 +42,7 @@ namespace Umbraco.Core.Publishing
{
d.ReleaseDate = null;
d.TryPublishValues(); // fixme variants?
var result = _contentService.SaveAndPublish(d, d.GetWriterProfile().Id);
var result = _contentService.SaveAndPublish(d, _userService.GetProfileById(d.WriterId).Id);
_logger.Debug<ContentService>($"Result of publish attempt: {result.Result}");
if (result.Success == false)
{
@@ -66,7 +68,7 @@ namespace Umbraco.Core.Publishing
try
{
d.ExpireDate = null;
var result = _contentService.Unpublish(d, d.GetWriterProfile().Id);
var result = _contentService.Unpublish(d, userId: _userService.GetProfileById(d.WriterId).Id);
if (result.Success)
{
counter++;

View File

@@ -362,9 +362,9 @@ namespace Umbraco.Core.Services
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, Func<IContent, bool> editing, Func<IContent, bool> publishValues, int userId = 0);
/// <summary>
/// Unpublishes a document.
/// Unpublishes a document or optionally unpublishes a culture and/or segment for the document.
/// </summary>
PublishResult Unpublish(IContent content, int userId = 0);
PublishResult 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

@@ -859,7 +859,7 @@ namespace Umbraco.Core.Services.Implement
/// <inheritdoc />
public OperationResult Save(IContent content, int userId = 0, bool raiseEvents = true)
{
var publishedState = ((Content) content).PublishedState;
var publishedState = content.PublishedState;
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
throw new InvalidOperationException("Cannot save a (un)publishing, use the dedicated (un)publish method.");
@@ -1025,7 +1025,7 @@ namespace Umbraco.Core.Services.Implement
}
/// <inheritdoc />
public PublishResult Unpublish(IContent content, int userId = 0)
public PublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0)
{
var evtMsgs = EventMessagesFactory.Get();
@@ -1033,32 +1033,96 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
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
content = newest;
if (content.Published == false)
var tryUnpublishVariation = TryUnpublishVariationInternal(scope, content, evtMsgs, culture, segment, userId);
if (tryUnpublishVariation) return tryUnpublishVariation.Result;
//continue the normal Unpublish operation to unpublish the entire content item
return UnpublishInternal(scope, content, evtMsgs, userId);
}
}
/// <summary>
/// Used to unpublish the requested variant if possible
/// </summary>
/// <param name="scope"></param>
/// <param name="content"></param>
/// <param name="evtMsgs"></param>
/// <param name="culture"></param>
/// <param name="segment"></param>
/// <param name="userId"></param>
/// <returns>
/// 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)
{
if (!culture.IsNullOrWhiteSpace() || !segment.IsNullOrWhiteSpace())
{
//now we need to check if this is not the last culture/segment that is published
if (!culture.IsNullOrWhiteSpace())
{
scope.Complete();
return new PublishResult(PublishResultType.SuccessAlready, evtMsgs, content); // already unpublished
//capture before we clear the values
var publishedCultureCount = content.PublishedCultures.Count();
//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)
{
scope.Complete();
return Attempt.Succeed(new PublishResult(PublishResultType.SuccessVariant, evtMsgs, content));
}
}
// strategy
// fixme should we still complete the uow? don't want to rollback here!
var attempt = StrategyCanUnpublish(scope, content, userId, evtMsgs);
if (attempt.Success == false) return attempt; // causes rollback
attempt = StrategyUnpublish(scope, content, true, userId, evtMsgs);
if (attempt.Success == false) return attempt; // causes rollback
content.WriterId = userId;
_documentRepository.Save(content);
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), "UnPublished");
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id);
scope.Complete();
if (!segment.IsNullOrWhiteSpace())
{
//TODO: When segments are supported, implement this, a discussion needs to happen about what how a segment is 'published' or not
// since currently this is very different from a culture.
throw new NotImplementedException("Segments are currently not supported in Umbraco");
}
}
//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>();
}
/// <summary>
/// Performs the logic to unpublish an entire document
/// </summary>
/// <param name="scope"></param>
/// <param name="content"></param>
/// <param name="evtMsgs"></param>
/// <param name="userId"></param>
/// <returns></returns>
private PublishResult 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
content = newest;
if (content.Published == false)
{
scope.Complete();
return new PublishResult(PublishResultType.SuccessAlready, evtMsgs, content); // already unpublished
}
// strategy
// fixme should we still complete the uow? don't want to rollback here!
var attempt = StrategyCanUnpublish(scope, content, userId, evtMsgs);
if (attempt.Success == false) return attempt; // causes rollback
attempt = StrategyUnpublish(scope, content, true, userId, evtMsgs);
if (attempt.Success == false) return attempt; // causes rollback
content.WriterId = userId;
_documentRepository.Save(content);
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), "UnPublished");
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id);
scope.Complete();
return new PublishResult(PublishResultType.Success, evtMsgs, content);
}
@@ -1092,7 +1156,7 @@ namespace Umbraco.Core.Services.Implement
try
{
d.ExpireDate = null;
var result = Unpublish(d, d.WriterId);
var result = Unpublish(d, userId: d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>($"Failed to unpublish document id={d.Id}, reason={result.Result}.");
}

View File

@@ -18,6 +18,11 @@
/// </summary>
SuccessAlready = 1,
/// <summary>
/// The specified variant was unpublished, the content item itself remains published.
/// </summary>
SuccessVariant = 2,
/// <summary>
/// The operation failed.
/// </summary>