Fixes Publishing/Unpublishing variants and it's nuances
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The states of a content item.
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}.");
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user