Fixes up dirty tracking of cultures and published cultures the same way we handle that with other entities which uses observable collection events. This simplifies the dirty tracking logic and means we dont require lookup the original object, but now we need to figure out the fixme stuff
This commit is contained in:
@@ -52,27 +52,7 @@ namespace Umbraco.Core
|
||||
return ContentStatus.Unpublished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cultures that have been flagged for unpublishing.
|
||||
/// </summary>
|
||||
/// <remarks>Gets cultures for which content.UnpublishCulture() has been invoked.</remarks>
|
||||
internal static IReadOnlyList<string> GetCulturesUnpublishing(this IContent content, IContent persisted)
|
||||
{
|
||||
//TODO: The reason we need a ref to the original persisted IContent is to check if it is published
|
||||
// however, for performance reasons we could pass in a ContentCultureInfosCollection which could be
|
||||
// resolved from the database much more quickly than resolving an entire IContent object.
|
||||
// That said, the GetById on the IContentService will return from cache so might not be something to worry about.
|
||||
|
||||
|
||||
if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos"))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var culturesChanging = content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture);
|
||||
return culturesChanging
|
||||
.Where(x => !content.IsCulturePublished(x) && // is not published anymore
|
||||
persisted != null && persisted.IsCulturePublished(x)) // but was published before
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Permissions;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public class CancellableEnumerableObjectEventArgs<T> : CancellableObjectEventArgs<IEnumerable<T>>, IEquatable<CancellableEnumerableObjectEventArgs<T>>
|
||||
{
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
: base(eventObject, canCancel, messages, additionalData)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(eventObject, canCancel, eventMessages)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, EventMessages eventMessages)
|
||||
: base(eventObject, eventMessages)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel)
|
||||
: base(eventObject, canCancel)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject)
|
||||
: base(eventObject)
|
||||
{ }
|
||||
|
||||
public bool Equals(CancellableEnumerableObjectEventArgs<T> other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
return EventObject.SequenceEqual(other.EventObject);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((CancellableEnumerableObjectEventArgs<T>)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCodeHelper.GetHashCode(EventObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Events
|
||||
public class CancellableEventArgs : EventArgs, IEquatable<CancellableEventArgs>
|
||||
{
|
||||
private bool _cancel;
|
||||
private Dictionary<string, object> _eventState;
|
||||
private IDictionary<string, object> _eventState;
|
||||
|
||||
private static readonly ReadOnlyDictionary<string, object> EmptyAdditionalData = new ReadOnlyDictionary<string, object>(new Dictionary<string, object>());
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Umbraco.Core.Events
|
||||
/// <summary>
|
||||
/// Returns the EventMessages object which is used to add messages to the message collection for this event
|
||||
/// </summary>
|
||||
public EventMessages Messages { get; private set; }
|
||||
public EventMessages Messages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// In some cases raised evens might need to contain additional arbitrary readonly data which can be read by event subscribers
|
||||
@@ -98,7 +98,7 @@ namespace Umbraco.Core.Events
|
||||
/// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility
|
||||
/// so we cannot change the strongly typed nature for some events.
|
||||
/// </remarks>
|
||||
public ReadOnlyDictionary<string, object> AdditionalData { get; private set; }
|
||||
public ReadOnlyDictionary<string, object> AdditionalData { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// This can be used by event subscribers to store state in the event args so they easily deal with custom state data between a starting ("ing")
|
||||
@@ -106,7 +106,8 @@ namespace Umbraco.Core.Events
|
||||
/// </summary>
|
||||
public IDictionary<string, object> EventState
|
||||
{
|
||||
get { return _eventState ?? (_eventState = new Dictionary<string, object>()); }
|
||||
get => _eventState ?? (_eventState = new Dictionary<string, object>());
|
||||
internal set => _eventState = value;
|
||||
}
|
||||
|
||||
public bool Equals(CancellableEventArgs other)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Permissions;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
@@ -128,49 +127,4 @@ namespace Umbraco.Core.Events
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public class CancellableEnumerableObjectEventArgs<T> : CancellableObjectEventArgs<IEnumerable<T>>, IEquatable<CancellableEnumerableObjectEventArgs<T>>
|
||||
{
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
: base(eventObject, canCancel, messages, additionalData)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(eventObject, canCancel, eventMessages)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, EventMessages eventMessages)
|
||||
: base(eventObject, eventMessages)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel)
|
||||
: base(eventObject, canCancel)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject)
|
||||
: base(eventObject)
|
||||
{ }
|
||||
|
||||
public bool Equals(CancellableEnumerableObjectEventArgs<T> other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
return EventObject.SequenceEqual(other.EventObject);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((CancellableEnumerableObjectEventArgs<T>)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCodeHelper.GetHashCode(EventObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/Umbraco.Core/Events/ContentPublishedEventArgs.cs
Normal file
29
src/Umbraco.Core/Events/ContentPublishedEventArgs.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class ContentPublishedEventArgs : PublishEventArgs<IContent>
|
||||
{
|
||||
public ContentPublishedEventArgs(IEnumerable<IContent> eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(eventObject, canCancel, eventMessages)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture has been published, during a Published event.
|
||||
/// </summary>
|
||||
public bool HasPublishedCulture(IContent content, string culture)
|
||||
=> content.WasPropertyDirty("_changedCulture_" + culture);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture has been unpublished, during a Published event.
|
||||
/// </summary>
|
||||
public bool HasUnpublishedCulture(IContent content, string culture)
|
||||
=> content.WasPropertyDirty("_unpublishedCulture_" + culture);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
33
src/Umbraco.Core/Events/ContentPublishingEventArgs.cs
Normal file
33
src/Umbraco.Core/Events/ContentPublishingEventArgs.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class ContentPublishingEventArgs : PublishEventArgs<IContent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ContentPublishingEventArgs"/>
|
||||
/// </summary>
|
||||
/// <param name="eventObject"></param>
|
||||
/// <param name="eventMessages"></param>
|
||||
public ContentPublishingEventArgs(IEnumerable<IContent> eventObject, EventMessages eventMessages)
|
||||
: base(eventObject, eventMessages)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture is being published, during a Publishing event.
|
||||
/// </summary>
|
||||
public bool IsPublishingCulture(IContent content, string culture)
|
||||
=> content.PublishCultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture is being unpublished, during a Publishing event.
|
||||
/// </summary>
|
||||
public bool IsUnpublishingCulture(IContent content, string culture)
|
||||
=> content.IsPropertyDirty("_unpublishedCulture_" + culture); //bit of a hack since we know that the content implementation tracks changes this way
|
||||
|
||||
}
|
||||
}
|
||||
30
src/Umbraco.Core/Events/ContentSavedEventArgs.cs
Normal file
30
src/Umbraco.Core/Events/ContentSavedEventArgs.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class ContentSavedEventArgs : SaveEventArgs<IContent>
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ContentSavedEventArgs"/>
|
||||
/// </summary>
|
||||
/// <param name="eventObject"></param>
|
||||
/// <param name="messages"></param>
|
||||
/// <param name="additionalData"></param>
|
||||
public ContentSavedEventArgs(IEnumerable<IContent> eventObject, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
: base(eventObject, false, messages, additionalData)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture has been saved, during a Saved event.
|
||||
/// </summary>
|
||||
public bool HasSavedCulture(IContent content, string culture)
|
||||
=> content.WasPropertyDirty("_updatedCulture_" + culture);
|
||||
}
|
||||
}
|
||||
66
src/Umbraco.Core/Events/ContentSavingEventArgs.cs
Normal file
66
src/Umbraco.Core/Events/ContentSavingEventArgs.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class ContentSavingEventArgs : SaveEventArgs<IContent>
|
||||
{
|
||||
#region Factory Methods
|
||||
/// <summary>
|
||||
/// Converts <see cref="ContentSavingEventArgs"/> to <see cref="ContentSavedEventArgs"/> while preserving all args state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ContentSavedEventArgs ToContentSavedEventArgs()
|
||||
{
|
||||
return new ContentSavedEventArgs(EventObject, Messages, AdditionalData)
|
||||
{
|
||||
EventState = EventState
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="ContentSavingEventArgs"/> to <see cref="ContentPublishedEventArgs"/> while preserving all args state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ContentPublishedEventArgs ToContentPublishedEventArgs()
|
||||
{
|
||||
return new ContentPublishedEventArgs(EventObject, false, Messages)
|
||||
{
|
||||
EventState = EventState,
|
||||
AdditionalData = AdditionalData
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="ContentSavingEventArgs"/> to <see cref="ContentPublishingEventArgs"/> while preserving all args state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ContentPublishingEventArgs ToContentPublishingEventArgs()
|
||||
{
|
||||
return new ContentPublishingEventArgs(EventObject, Messages)
|
||||
{
|
||||
EventState = EventState,
|
||||
AdditionalData = AdditionalData
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ContentSavingEventArgs(IEnumerable<IContent> eventObject, EventMessages eventMessages) : base(eventObject, eventMessages)
|
||||
{
|
||||
}
|
||||
|
||||
public ContentSavingEventArgs(IContent eventObject, EventMessages eventMessages) : base(eventObject, eventMessages)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture is being saved, during a Saving event.
|
||||
/// </summary>
|
||||
public bool IsSavingCulture(IContent content, string culture) => content.CultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty();
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,10 @@ namespace Umbraco.Core.Events
|
||||
/// </summary>
|
||||
/// <param name="eventObject"></param>
|
||||
/// <param name="canCancel"></param>
|
||||
/// <param name="isAllPublished"></param>
|
||||
/// <param name="eventMessages"></param>
|
||||
public PublishEventArgs(IEnumerable<TEntity> eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages)
|
||||
public PublishEventArgs(IEnumerable<TEntity> eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(eventObject, canCancel, eventMessages)
|
||||
{
|
||||
IsAllRepublished = isAllPublished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,12 +41,10 @@ namespace Umbraco.Core.Events
|
||||
/// </summary>
|
||||
/// <param name="eventObject"></param>
|
||||
/// <param name="canCancel"></param>
|
||||
/// <param name="isAllPublished"></param>
|
||||
/// <param name="eventMessages"></param>
|
||||
public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages)
|
||||
public PublishEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(new List<TEntity> { eventObject }, canCancel, eventMessages)
|
||||
{
|
||||
IsAllRepublished = isAllPublished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,7 +56,6 @@ namespace Umbraco.Core.Events
|
||||
public PublishEventArgs(IEnumerable<TEntity> eventObject, bool canCancel, bool isAllPublished)
|
||||
: base(eventObject, canCancel)
|
||||
{
|
||||
IsAllRepublished = isAllPublished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -90,24 +85,18 @@ namespace Umbraco.Core.Events
|
||||
public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished)
|
||||
: base(new List<TEntity> { eventObject }, canCancel)
|
||||
{
|
||||
IsAllRepublished = isAllPublished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all entities that were published during the operation
|
||||
/// </summary>
|
||||
public IEnumerable<TEntity> PublishedEntities
|
||||
{
|
||||
get { return EventObject; }
|
||||
}
|
||||
|
||||
public bool IsAllRepublished { get; private set; }
|
||||
public IEnumerable<TEntity> PublishedEntities => EventObject;
|
||||
|
||||
public bool Equals(PublishEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && IsAllRepublished == other.IsAllRepublished;
|
||||
return base.Equals(other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
@@ -122,7 +111,7 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ IsAllRepublished.GetHashCode();
|
||||
return (base.GetHashCode() * 397);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
@@ -113,9 +112,6 @@ namespace Umbraco.Core.Events
|
||||
/// <summary>
|
||||
/// Returns all entities that were saved during the operation
|
||||
/// </summary>
|
||||
public IEnumerable<TEntity> SavedEntities
|
||||
{
|
||||
get { return EventObject; }
|
||||
}
|
||||
public IEnumerable<TEntity> SavedEntities => EventObject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,14 @@ namespace Umbraco.Core.Models
|
||||
private bool _published;
|
||||
private PublishedState _publishedState;
|
||||
private HashSet<string> _editedCultures;
|
||||
private ContentCultureInfosCollection _publishInfos, _publishInfos1, _publishInfos2;
|
||||
private ContentCultureInfosCollection _publishInfos;
|
||||
|
||||
#region Used for change tracking
|
||||
|
||||
private (HashSet<string> addedCultures, HashSet<string> removedCultures, HashSet<string> updatedCultures) _currentPublishCultureChanges;
|
||||
private (HashSet<string> addedCultures, HashSet<string> removedCultures, HashSet<string> updatedCultures) _previousPublishCultureChanges;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating a Content object
|
||||
@@ -92,7 +99,7 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
set
|
||||
{
|
||||
if(_schedule != null)
|
||||
if (_schedule != null)
|
||||
_schedule.CollectionChanged -= ScheduleCollectionChanged;
|
||||
SetPropertyValueAndDetectChanges(value, ref _schedule, nameof(ContentSchedule));
|
||||
if (_schedule != null)
|
||||
@@ -204,7 +211,7 @@ namespace Umbraco.Core.Models
|
||||
// just check _publishInfos
|
||||
// a non-available culture could not become published anyways
|
||||
=> _publishInfos != null && _publishInfos.ContainsKey(culture);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCultureEdited(string culture)
|
||||
=> IsCultureAvailable(culture) && // is available, and
|
||||
@@ -246,26 +253,8 @@ namespace Umbraco.Core.Models
|
||||
if (culture.IsNullOrWhiteSpace()) return PublishDate;
|
||||
if (!ContentTypeBase.VariesByCulture()) return null;
|
||||
if (_publishInfos == null) return null;
|
||||
return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null;
|
||||
return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?)null;
|
||||
}
|
||||
|
||||
// internal for repository
|
||||
internal void AknPublishInfo()
|
||||
{
|
||||
_publishInfos1 = _publishInfos2 = new ContentCultureInfosCollection(_publishInfos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPublishingCulture(string culture) => _publishInfos.IsCultureUpdated(_publishInfos1, culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsUnpublishingCulture(string culture) => _publishInfos.IsCultureRemoved(_publishInfos1, culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPublishedCulture(string culture) => _publishInfos1.IsCultureUpdated(_publishInfos2, culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasUnpublishedCulture(string culture) => _publishInfos1.IsCultureRemoved(_publishInfos2, culture);
|
||||
|
||||
/// <summary>
|
||||
/// Handles culture infos collection changes.
|
||||
@@ -273,6 +262,40 @@ namespace Umbraco.Core.Models
|
||||
private void PublishNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(PublishCultureInfos));
|
||||
|
||||
//we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too
|
||||
//which would allows us to continue doing WasCulturePublished, but don't think we need it anymore
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
var cultureInfo = e.NewItems.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.addedCultures == null) _currentPublishCultureChanges.addedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
if (_currentPublishCultureChanges.updatedCultures == null) _currentPublishCultureChanges.updatedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
_currentPublishCultureChanges.addedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.removedCultures?.Remove(cultureInfo.Culture);
|
||||
break;
|
||||
}
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
//remove listening for changes
|
||||
var cultureInfo = e.OldItems.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.removedCultures == null) _currentPublishCultureChanges.removedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
_currentPublishCultureChanges.removedCultures.Add(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.updatedCultures?.Remove(cultureInfo.Culture);
|
||||
_currentPublishCultureChanges.addedCultures?.Remove(cultureInfo.Culture);
|
||||
break;
|
||||
}
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
{
|
||||
//replace occurs when an Update occurs
|
||||
var cultureInfo = e.NewItems.Cast<ContentCultureInfos>().First();
|
||||
if (_currentPublishCultureChanges.updatedCultures == null) _currentPublishCultureChanges.updatedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
_currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
@@ -305,7 +328,7 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
|
||||
internal void ChangeContentType(IContentType contentType, bool clearProperties)
|
||||
{
|
||||
if(clearProperties)
|
||||
if (clearProperties)
|
||||
{
|
||||
ContentTypeId = contentType.Id;
|
||||
ContentType = new SimpleContentType(contentType);
|
||||
@@ -320,21 +343,92 @@ namespace Umbraco.Core.Models
|
||||
ChangeContentType(contentType);
|
||||
}
|
||||
|
||||
public override void ResetWereDirtyProperties()
|
||||
{
|
||||
base.ResetWereDirtyProperties();
|
||||
_previousPublishCultureChanges.updatedCultures = null;
|
||||
_previousPublishCultureChanges.removedCultures = null;
|
||||
_previousPublishCultureChanges.addedCultures = null;
|
||||
}
|
||||
|
||||
public override void ResetDirtyProperties(bool rememberDirty)
|
||||
{
|
||||
base.ResetDirtyProperties(rememberDirty);
|
||||
|
||||
if (rememberDirty)
|
||||
{
|
||||
|
||||
_previousPublishCultureChanges.addedCultures = _currentPublishCultureChanges.addedCultures == null ? null : new HashSet<string>(_currentPublishCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousPublishCultureChanges.removedCultures = _currentPublishCultureChanges.removedCultures == null ? null : new HashSet<string>(_currentPublishCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousPublishCultureChanges.updatedCultures = _currentPublishCultureChanges.updatedCultures == null ? null : new HashSet<string>(_currentPublishCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
_previousPublishCultureChanges.addedCultures = null;
|
||||
_previousPublishCultureChanges.removedCultures = null;
|
||||
_previousPublishCultureChanges.updatedCultures = null;
|
||||
}
|
||||
_currentPublishCultureChanges.addedCultures?.Clear();
|
||||
_currentPublishCultureChanges.removedCultures?.Clear();
|
||||
_currentPublishCultureChanges.updatedCultures?.Clear();
|
||||
|
||||
// take care of the published state
|
||||
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
|
||||
|
||||
_publishInfos2 = _publishInfos1;
|
||||
|
||||
if (_publishInfos == null) return;
|
||||
|
||||
foreach (var infos in _publishInfos)
|
||||
infos.ResetDirtyProperties(rememberDirty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to check special keys.</remarks>
|
||||
public override bool IsPropertyDirty(string propertyName)
|
||||
{
|
||||
//Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith("_publishedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_publishedCulture_");
|
||||
return _currentPublishCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_unpublishedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_unpublishedCulture_");
|
||||
return _currentPublishCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_changedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_changedCulture_");
|
||||
return _currentPublishCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return base.IsPropertyDirty(propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to check special keys.</remarks>
|
||||
public override bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
//Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith("_publishedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_publishedCulture_");
|
||||
return _previousPublishCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_unpublishedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_unpublishedCulture_");
|
||||
return _previousPublishCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_changedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_changedCulture_");
|
||||
return _previousPublishCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return base.WasPropertyDirty(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of the current entity with its identity and it's property identities reset
|
||||
/// </summary>
|
||||
@@ -365,7 +459,7 @@ namespace Umbraco.Core.Models
|
||||
if (clonedContent._publishInfos != null)
|
||||
{
|
||||
clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any
|
||||
clonedContent._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone
|
||||
clonedContent._publishInfos = (ContentCultureInfosCollection)_publishInfos.DeepClone(); //manually deep clone
|
||||
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,14 @@ namespace Umbraco.Core.Models
|
||||
protected IContentTypeComposition ContentTypeBase;
|
||||
private int _writerId;
|
||||
private PropertyCollection _properties;
|
||||
private ContentCultureInfosCollection _cultureInfos, _cultureInfos1, _cultureInfos2;
|
||||
private ContentCultureInfosCollection _cultureInfos;
|
||||
|
||||
#region Used for change tracking
|
||||
|
||||
private (HashSet<string> addedCultures, HashSet<string> removedCultures, HashSet<string> updatedCultures) _currentCultureChanges;
|
||||
private (HashSet<string> addedCultures, HashSet<string> removedCultures, HashSet<string> updatedCultures) _previousCultureChanges;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentBase"/> class.
|
||||
@@ -64,6 +71,8 @@ namespace Umbraco.Core.Models
|
||||
OnPropertyChanged(nameof(Properties));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Id of the user who wrote/updated this entity
|
||||
/// </summary>
|
||||
@@ -180,7 +189,7 @@ namespace Umbraco.Core.Models
|
||||
if (culture.IsNullOrWhiteSpace()) return null;
|
||||
if (!ContentTypeBase.VariesByCulture()) return null;
|
||||
if (_cultureInfos == null) return null;
|
||||
return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null;
|
||||
return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?)null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -210,12 +219,6 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCultureInfos()
|
||||
{
|
||||
_cultureInfos?.Clear();
|
||||
_cultureInfos = null;
|
||||
}
|
||||
|
||||
private void ClearCultureInfo(string culture)
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
@@ -233,6 +236,38 @@ namespace Umbraco.Core.Models
|
||||
private void CultureInfosCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(CultureInfos));
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
var cultureInfo = e.NewItems.Cast<ContentCultureInfos>().First();
|
||||
if (_currentCultureChanges.addedCultures == null) _currentCultureChanges.addedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
if (_currentCultureChanges.updatedCultures == null) _currentCultureChanges.updatedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
_currentCultureChanges.addedCultures.Add(cultureInfo.Culture);
|
||||
_currentCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
_currentCultureChanges.removedCultures?.Remove(cultureInfo.Culture);
|
||||
break;
|
||||
}
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
//remove listening for changes
|
||||
var cultureInfo = e.OldItems.Cast<ContentCultureInfos>().First();
|
||||
if (_currentCultureChanges.removedCultures == null) _currentCultureChanges.removedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
_currentCultureChanges.removedCultures.Add(cultureInfo.Culture);
|
||||
_currentCultureChanges.updatedCultures?.Remove(cultureInfo.Culture);
|
||||
_currentCultureChanges.addedCultures?.Remove(cultureInfo.Culture);
|
||||
break;
|
||||
}
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
{
|
||||
//replace occurs when an Update occurs
|
||||
var cultureInfo = e.NewItems.Cast<ContentCultureInfos>().First();
|
||||
if (_currentCultureChanges.updatedCultures == null) _currentCultureChanges.updatedCultures = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
_currentCultureChanges.updatedCultures.Add(cultureInfo.Culture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -281,124 +316,42 @@ namespace Umbraco.Core.Models
|
||||
|
||||
#endregion
|
||||
|
||||
#region Copy
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyFrom(IContent other, string culture = "*")
|
||||
{
|
||||
if (other.ContentTypeId != ContentTypeId)
|
||||
throw new InvalidOperationException("Cannot copy values from a different content type.");
|
||||
|
||||
culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull();
|
||||
|
||||
// the variation should be supported by the content type properties
|
||||
// if the content type is invariant, only '*' and 'null' is ok
|
||||
// if the content type varies, everything is ok because some properties may be invariant
|
||||
if (!ContentTypeBase.SupportsPropertyVariation(culture, "*", true))
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentTypeBase.Alias}\" with variation \"{ContentTypeBase.Variations}\".");
|
||||
|
||||
// copying from the same Id and VersionPk
|
||||
var copyingFromSelf = Id == other.Id && VersionId == other.VersionId;
|
||||
var published = copyingFromSelf;
|
||||
|
||||
// note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails
|
||||
|
||||
// clear all existing properties for the specified culture
|
||||
foreach (var property in Properties)
|
||||
{
|
||||
// each property type may or may not support the variation
|
||||
if (!property.PropertyType.SupportsVariation(culture, "*", wildcards: true))
|
||||
continue;
|
||||
|
||||
foreach (var pvalue in property.Values)
|
||||
if (property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) &&
|
||||
(culture == "*" || pvalue.Culture.InvariantEquals(culture)))
|
||||
{
|
||||
property.SetValue(null, pvalue.Culture, pvalue.Segment);
|
||||
}
|
||||
}
|
||||
|
||||
// copy properties from 'other'
|
||||
var otherProperties = other.Properties;
|
||||
foreach (var otherProperty in otherProperties)
|
||||
{
|
||||
if (!otherProperty.PropertyType.SupportsVariation(culture, "*", wildcards: true))
|
||||
continue;
|
||||
|
||||
var alias = otherProperty.PropertyType.Alias;
|
||||
foreach (var pvalue in otherProperty.Values)
|
||||
{
|
||||
if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) &&
|
||||
(culture == "*" || pvalue.Culture.InvariantEquals(culture)))
|
||||
{
|
||||
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
|
||||
SetValue(alias, value, pvalue.Culture, pvalue.Segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy names, too
|
||||
|
||||
if (culture == "*")
|
||||
ClearCultureInfos();
|
||||
|
||||
if (culture == null || culture == "*")
|
||||
Name = other.Name;
|
||||
|
||||
// ReSharper disable once UseDeconstruction
|
||||
foreach (var cultureInfo in other.CultureInfos)
|
||||
{
|
||||
if (culture == "*" || culture == cultureInfo.Culture)
|
||||
SetCultureName(cultureInfo.Name, cultureInfo.Culture);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Validation
|
||||
|
||||
/// <inheritdoc />
|
||||
public Property[] ValidateProperties(string culture = "*")
|
||||
{
|
||||
// select invalid properties
|
||||
return Properties.Where(x =>
|
||||
{
|
||||
// if culture is null, we validate invariant properties only
|
||||
// if culture is '*' we validate both variant and invariant properties, automatically
|
||||
// if culture is specific eg 'en-US' we both too, but explicitly
|
||||
|
||||
var varies = x.PropertyType.VariesByCulture();
|
||||
|
||||
if (culture == null)
|
||||
return !(varies || x.IsValid(null)); // validate invariant property, invariant culture
|
||||
|
||||
if (culture == "*")
|
||||
return !x.IsValid(culture); // validate property, all cultures
|
||||
|
||||
return varies
|
||||
? !x.IsValid(culture) // validate variant property, explicit culture
|
||||
: !x.IsValid(null); // validate invariant property, explicit culture
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dirty
|
||||
|
||||
public override void ResetWereDirtyProperties()
|
||||
{
|
||||
base.ResetWereDirtyProperties();
|
||||
_previousCultureChanges.addedCultures = null;
|
||||
_previousCultureChanges.removedCultures = null;
|
||||
_previousCultureChanges.updatedCultures = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to include user properties.</remarks>
|
||||
public override void ResetDirtyProperties(bool rememberDirty)
|
||||
{
|
||||
base.ResetDirtyProperties(rememberDirty);
|
||||
|
||||
if (rememberDirty)
|
||||
{
|
||||
_previousCultureChanges.addedCultures = _currentCultureChanges.addedCultures == null ? null : new HashSet<string>(_currentCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousCultureChanges.removedCultures = _currentCultureChanges.removedCultures == null ? null : new HashSet<string>(_currentCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
_previousCultureChanges.updatedCultures = _currentCultureChanges.updatedCultures == null ? null : new HashSet<string>(_currentCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
_previousCultureChanges.addedCultures = null;
|
||||
_previousCultureChanges.removedCultures = null;
|
||||
_previousCultureChanges.updatedCultures = null;
|
||||
}
|
||||
_currentCultureChanges.addedCultures?.Clear();
|
||||
_currentCultureChanges.removedCultures?.Clear();
|
||||
_currentCultureChanges.updatedCultures?.Clear();
|
||||
|
||||
// also reset dirty changes made to user's properties
|
||||
foreach (var prop in Properties)
|
||||
prop.ResetDirtyProperties(rememberDirty);
|
||||
|
||||
_cultureInfos2 = _cultureInfos1;
|
||||
_cultureInfos1 = _cultureInfos == null ? null : new ContentCultureInfosCollection(_cultureInfos);
|
||||
|
||||
// take care of culture infos
|
||||
if (_cultureInfos == null) return;
|
||||
|
||||
@@ -443,6 +396,23 @@ namespace Umbraco.Core.Models
|
||||
if (base.IsPropertyDirty(propertyName))
|
||||
return true;
|
||||
|
||||
//Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith("_addedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_addedCulture_");
|
||||
return _currentCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_removedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_removedCulture_");
|
||||
return _currentCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_updatedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_updatedCulture_");
|
||||
return _currentCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return Properties.Contains(propertyName) && Properties[propertyName].IsDirty();
|
||||
}
|
||||
|
||||
@@ -453,6 +423,23 @@ namespace Umbraco.Core.Models
|
||||
if (base.WasPropertyDirty(propertyName))
|
||||
return true;
|
||||
|
||||
//Special check here since we want to check if the request is for changed cultures
|
||||
if (propertyName.StartsWith("_addedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_addedCulture_");
|
||||
return _previousCultureChanges.addedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_removedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_removedCulture_");
|
||||
return _previousCultureChanges.removedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
if (propertyName.StartsWith("_updatedCulture_"))
|
||||
{
|
||||
var culture = propertyName.TrimStart("_updatedCulture_");
|
||||
return _previousCultureChanges.updatedCultures?.Contains(culture) ?? false;
|
||||
}
|
||||
|
||||
return Properties.Contains(propertyName) && Properties[propertyName].WasDirty();
|
||||
}
|
||||
|
||||
@@ -474,12 +461,6 @@ namespace Umbraco.Core.Models
|
||||
return instanceProperties.Concat(propertyTypes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSavingCulture(string culture) => _cultureInfos.IsCultureUpdated(_cultureInfos1, culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasSavedCulture(string culture) => _cultureInfos1.IsCultureUpdated(_cultureInfos2, culture);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -496,7 +477,7 @@ namespace Umbraco.Core.Models
|
||||
if (clonedContent._cultureInfos != null)
|
||||
{
|
||||
clonedContent._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any
|
||||
clonedContent._cultureInfos = (ContentCultureInfosCollection) _cultureInfos.DeepClone(); //manually deep clone
|
||||
clonedContent._cultureInfos = (ContentCultureInfosCollection)_cultureInfos.DeepClone(); //manually deep clone
|
||||
clonedContent._cultureInfos.CollectionChanged += clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler
|
||||
}
|
||||
|
||||
@@ -504,7 +485,7 @@ namespace Umbraco.Core.Models
|
||||
if (clonedContent._properties != null)
|
||||
{
|
||||
clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any
|
||||
clonedContent._properties = (PropertyCollection) _properties.DeepClone(); //manually deep clone
|
||||
clonedContent._properties = (PropertyCollection)_properties.DeepClone(); //manually deep clone
|
||||
clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public static class ContentCultureInfosCollectionExtensions
|
||||
{
|
||||
public static bool IsCultureUpdated(this ContentCultureInfosCollection to, ContentCultureInfosCollection from, string culture)
|
||||
=> to != null && to.ContainsKey(culture) &&
|
||||
(from == null || !from.ContainsKey(culture) || from[culture].Date != to[culture].Date);
|
||||
|
||||
public static bool IsCultureRemoved(this ContentCultureInfosCollection to, ContentCultureInfosCollection from, string culture)
|
||||
=> (to == null || !to.ContainsKey(culture)) && from != null && from.ContainsKey(culture);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,127 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
internal static class ContentRepositoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the cultures that have been flagged for unpublishing.
|
||||
/// </summary>
|
||||
/// <remarks>Gets cultures for which content.UnpublishCulture() has been invoked.</remarks>
|
||||
public static IReadOnlyList<string> GetCulturesUnpublishing(this IContent content)
|
||||
{
|
||||
if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos"))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var culturesUnpublishing = content.CultureInfos.Values
|
||||
.Where(x => content.IsPropertyDirty("_unpublishedCulture_" + x.Culture))
|
||||
.Select(x => x.Culture);
|
||||
|
||||
return culturesUnpublishing.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies values from another document.
|
||||
/// </summary>
|
||||
public static void CopyFrom(this IContent content, IContent other, string culture = "*")
|
||||
{
|
||||
if (other.ContentTypeId != content.ContentTypeId)
|
||||
throw new InvalidOperationException("Cannot copy values from a different content type.");
|
||||
|
||||
culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull();
|
||||
|
||||
// the variation should be supported by the content type properties
|
||||
// if the content type is invariant, only '*' and 'null' is ok
|
||||
// if the content type varies, everything is ok because some properties may be invariant
|
||||
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
|
||||
|
||||
// copying from the same Id and VersionPk
|
||||
var copyingFromSelf = content.Id == other.Id && content.VersionId == other.VersionId;
|
||||
var published = copyingFromSelf;
|
||||
|
||||
// note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails
|
||||
|
||||
// clear all existing properties for the specified culture
|
||||
foreach (var property in content.Properties)
|
||||
{
|
||||
// each property type may or may not support the variation
|
||||
if (!property.PropertyType.SupportsVariation(culture, "*", wildcards: true))
|
||||
continue;
|
||||
|
||||
foreach (var pvalue in property.Values)
|
||||
if (property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) &&
|
||||
(culture == "*" || pvalue.Culture.InvariantEquals(culture)))
|
||||
{
|
||||
property.SetValue(null, pvalue.Culture, pvalue.Segment);
|
||||
}
|
||||
}
|
||||
|
||||
// copy properties from 'other'
|
||||
var otherProperties = other.Properties;
|
||||
foreach (var otherProperty in otherProperties)
|
||||
{
|
||||
if (!otherProperty.PropertyType.SupportsVariation(culture, "*", wildcards: true))
|
||||
continue;
|
||||
|
||||
var alias = otherProperty.PropertyType.Alias;
|
||||
foreach (var pvalue in otherProperty.Values)
|
||||
{
|
||||
if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) &&
|
||||
(culture == "*" || pvalue.Culture.InvariantEquals(culture)))
|
||||
{
|
||||
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
|
||||
content.SetValue(alias, value, pvalue.Culture, pvalue.Segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy names, too
|
||||
|
||||
if (culture == "*")
|
||||
{
|
||||
content.CultureInfos.Clear();
|
||||
content.CultureInfos = null;
|
||||
}
|
||||
|
||||
|
||||
if (culture == null || culture == "*")
|
||||
content.Name = other.Name;
|
||||
|
||||
// ReSharper disable once UseDeconstruction
|
||||
foreach (var cultureInfo in other.CultureInfos)
|
||||
{
|
||||
if (culture == "*" || culture == cultureInfo.Culture)
|
||||
content.SetCultureName(cultureInfo.Name, cultureInfo.Culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the content item's properties pass variant rules
|
||||
/// </summary>
|
||||
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
|
||||
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
|
||||
public static Property[] ValidateProperties(this IContentBase content, string culture = "*")
|
||||
{
|
||||
// select invalid properties
|
||||
return content.Properties.Where(x =>
|
||||
{
|
||||
// if culture is null, we validate invariant properties only
|
||||
// if culture is '*' we validate both variant and invariant properties, automatically
|
||||
// if culture is specific eg 'en-US' we both too, but explicitly
|
||||
|
||||
var varies = x.PropertyType.VariesByCulture();
|
||||
|
||||
if (culture == null)
|
||||
return !(varies || x.IsValid(null)); // validate invariant property, invariant culture
|
||||
|
||||
if (culture == "*")
|
||||
return !x.IsValid(culture); // validate property, all cultures
|
||||
|
||||
return varies
|
||||
? !x.IsValid(culture) // validate variant property, explicit culture
|
||||
: !x.IsValid(null); // validate invariant property, explicit culture
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static void SetPublishInfo(this IContent content, string culture, string name, DateTime date)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
@@ -32,6 +153,8 @@ namespace Umbraco.Core.Models
|
||||
//fixme: Removing the logic here for the old WasCulturePublished and the _publishInfosOrig has broken
|
||||
// the test Can_Rollback_Version_On_Multilingual, but we need to understand what it's doing since I don't
|
||||
|
||||
//fixme: Because this is being called, we end up updating a culture which triggers the dirty change tracking
|
||||
// which ends up in error because the culture is not actually being updated which causes the tests to fail
|
||||
content.PublishCultureInfos.AddOrUpdate(culture, publishInfos.Name, date);
|
||||
|
||||
if (content.CultureInfos.TryGetValue(culture, out var infos))
|
||||
@@ -147,10 +270,13 @@ namespace Umbraco.Core.Models
|
||||
content.PublishCultureInfos.Remove(culture);
|
||||
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCultureInfo(culture);
|
||||
content.TouchCulture(culture);
|
||||
}
|
||||
|
||||
public static void TouchCultureInfo(this IContent content, string culture)
|
||||
/// <summary>
|
||||
/// Updates a culture date, if the culture exists.
|
||||
/// </summary>
|
||||
public static void TouchCulture(this IContent content, string culture)
|
||||
{
|
||||
if (!content.CultureInfos.TryGetValue(culture, out var infos)) return;
|
||||
content.CultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now);
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Umbraco.Core.Models.Entities
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetWereDirtyProperties()
|
||||
public virtual void ResetWereDirtyProperties()
|
||||
{
|
||||
// note: cannot .Clear() because when memberwise-cloning this will be the SAME
|
||||
// instance as the one on the clone, so we need to create a new instance.
|
||||
|
||||
@@ -135,29 +135,5 @@ namespace Umbraco.Core.Models
|
||||
/// <returns></returns>
|
||||
IContent DeepCloneWithResetIdentities();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture is being published, during a Publishing event.
|
||||
/// </summary>
|
||||
/// <remarks>Outside of a Publishing event handler, the returned value is unspecified.</remarks>
|
||||
bool IsPublishingCulture(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture is being unpublished, during a Publishing event.
|
||||
/// </summary>
|
||||
/// <remarks>Outside of a Publishing event handler, the returned value is unspecified.</remarks>
|
||||
bool IsUnpublishingCulture(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture has been published, during a Published event.
|
||||
/// </summary>
|
||||
/// <remarks>Outside of a Published event handler, the returned value is unspecified.</remarks>
|
||||
bool HasPublishedCulture(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture has been unpublished, during a Published event.
|
||||
/// </summary>
|
||||
/// <remarks>Outside of a Published event handler, the returned value is unspecified.</remarks>
|
||||
bool HasUnpublishedCulture(string culture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,33 +132,5 @@ namespace Umbraco.Core.Models
|
||||
/// <remarks>Values 'null' and 'empty' are equivalent for culture and segment.</remarks>
|
||||
void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null);
|
||||
|
||||
/// <summary>
|
||||
/// Copies values from another document.
|
||||
/// </summary>
|
||||
void CopyFrom(IContent other, string culture = "*");
|
||||
|
||||
/// <summary>
|
||||
/// Validates the content item's properties pass variant rules
|
||||
/// </summary>
|
||||
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
|
||||
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
|
||||
Property[] ValidateProperties(string culture = "*");
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture is being saved, during a Saving event.
|
||||
/// </summary>
|
||||
/// <remarks>Outside of a Saving event handler, the returned value is unspecified.</remarks>
|
||||
bool IsSavingCulture(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a culture has been saved, during a Saved event.
|
||||
/// </summary>
|
||||
/// <remarks>Outside of a Saved event handler, the returned value is unspecified.</remarks>
|
||||
bool HasSavedCulture(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a culture date, if the culture exists.
|
||||
/// </summary>
|
||||
void TouchCulture(string culture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,13 +142,15 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
public override void ResetDirtyProperties(bool rememberDirty)
|
||||
{
|
||||
{
|
||||
base.ResetDirtyProperties(rememberDirty);
|
||||
|
||||
_addedProperties.Clear();
|
||||
_removedProperties.Clear();
|
||||
base.ResetDirtyProperties(rememberDirty);
|
||||
|
||||
foreach (var prop in Properties)
|
||||
{
|
||||
((BeingDirtyBase)prop).ResetDirtyProperties(rememberDirty);
|
||||
prop.ResetDirtyProperties(rememberDirty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1197,7 +1197,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
foreach (var v in contentVariation)
|
||||
content.SetPublishInfo(v.Culture, v.Name, v.Date);
|
||||
content.AknPublishInfo();
|
||||
}
|
||||
if (documentVariations.TryGetValue(content.Id, out var documentVariation))
|
||||
content.SetCultureEdited(documentVariation.Where(x => x.Edited).Select(x => x.Culture));
|
||||
|
||||
@@ -307,15 +307,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
if (withIdentity)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
// if saving is cancelled, content remains without an identity
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(content);
|
||||
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
|
||||
return;
|
||||
|
||||
_documentRepository.Save(content);
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshNode).ToEventArgs());
|
||||
}
|
||||
|
||||
@@ -758,7 +759,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(content, evtMsgs);
|
||||
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
|
||||
{
|
||||
scope.Complete();
|
||||
@@ -784,8 +785,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
|
||||
}
|
||||
var changeType = TreeChangeTypes.RefreshNode;
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
@@ -814,7 +814,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(contentsA, evtMsgs);
|
||||
var saveEventArgs = new ContentSavingEventArgs(contentsA, evtMsgs);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
|
||||
{
|
||||
scope.Complete();
|
||||
@@ -835,8 +835,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
|
||||
}
|
||||
scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs());
|
||||
Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content");
|
||||
@@ -948,8 +947,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// all cultures = unpublish whole
|
||||
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
|
||||
{
|
||||
//TODO: Stop casting https://github.com/umbraco/Umbraco-CMS/issues/4234
|
||||
((Content)content).PublishedState = PublishedState.Unpublishing;
|
||||
content.PublishedState = PublishedState.Unpublishing;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1001,7 +999,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; //TODO: fix this https://github.com/umbraco/Umbraco-CMS/issues/4234
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
|
||||
// state here is either Publishing or Unpublishing
|
||||
// (even though, Publishing to unpublish a culture may end up unpublishing everything)
|
||||
@@ -1022,26 +1020,23 @@ namespace Umbraco.Core.Services.Implement
|
||||
var previouslyPublished = content.HasIdentity && content.Published;
|
||||
|
||||
// always save
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(content, evtMsgs);
|
||||
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
|
||||
if (publishing)
|
||||
{
|
||||
//to continue, we need to have a reference to the original IContent item that is currently persisted
|
||||
var persisted = content.HasIdentity ? GetById(content.Id) : null;
|
||||
|
||||
culturesUnpublishing = content.GetCulturesUnpublishing(persisted);
|
||||
culturesUnpublishing = content.GetCulturesUnpublishing();
|
||||
culturesPublishing = variesByCulture
|
||||
? content.PublishCultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
|
||||
: null;
|
||||
|
||||
// ensure that the document can be published, and publish handling events, business rules, etc
|
||||
publishResult = StrategyCanPublish(scope, content, userId, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs);
|
||||
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs);
|
||||
if (publishResult.Success)
|
||||
{
|
||||
// note: StrategyPublish flips the PublishedState to Publishing!
|
||||
publishResult = StrategyPublish(scope, content, userId, culturesPublishing, culturesUnpublishing, evtMsgs);
|
||||
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1061,7 +1056,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// reset published state from temp values (publishing, unpublishing) to original value
|
||||
// (published, unpublished) in order to save the document, unchanged
|
||||
((Content)content).Published = content.Published;
|
||||
//TODO: why? this seems odd, were just setting the exact same value that it already has
|
||||
// instead do we want to just set the PublishState?
|
||||
content.Published = content.Published;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1077,14 +1074,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
// 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);
|
||||
unpublishResult = StrategyCanUnpublish(scope, content, evtMsgs);
|
||||
if (unpublishResult.Success)
|
||||
unpublishResult = StrategyUnpublish(scope, content, userId, evtMsgs);
|
||||
else
|
||||
{
|
||||
// reset published state from temp values (publishing, unpublishing) to original value
|
||||
// (published, unpublished) in order to save the document, unchanged
|
||||
((Content)content).Published = content.Published;
|
||||
//TODO: why? this seems odd, were just setting the exact same value that it already has
|
||||
// instead do we want to just set the PublishState?
|
||||
content.Published = content.Published;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1107,8 +1106,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// raise the Saved event, always
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
|
||||
}
|
||||
|
||||
if (unpublishing) // we have tried to unpublish - won't happen in a branch
|
||||
@@ -1151,16 +1149,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (!branchOne) // for branches, handled by SaveAndPublishBranch
|
||||
{
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(content, false, false), "Published");
|
||||
scope.Events.Dispatch(Published, this, saveEventArgs.ToContentPublishedEventArgs(), nameof(Published));
|
||||
}
|
||||
|
||||
// if was not published and now is... descendants that were 'published' (but
|
||||
// it was not published and now is... descendants that were 'published' (but
|
||||
// had an unpublished ancestor) are 're-published' ie not explicitly published
|
||||
// but back as 'published' nevertheless
|
||||
if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id))
|
||||
{
|
||||
var descendants = GetPublishedDescendantsLocked(content).ToArray();
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(descendants, false, false), "Published");
|
||||
scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(descendants, false, evtMsgs), "Published");
|
||||
}
|
||||
|
||||
switch (publishResult.Result)
|
||||
@@ -1456,7 +1454,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (!document.HasIdentity)
|
||||
throw new InvalidOperationException("Cannot not branch-publish a new document.");
|
||||
|
||||
var publishedState = ((Content)document).PublishedState;
|
||||
var publishedState = document.PublishedState;
|
||||
if (publishedState == PublishedState.Publishing)
|
||||
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
|
||||
|
||||
@@ -1510,7 +1508,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// trigger events for the entire branch
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(document, TreeChangeTypes.RefreshBranch).ToEventArgs());
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(publishedDocuments, false, false), "Published");
|
||||
|
||||
//fixme - in the SaveAndPublishBranchOne -> CommitDocumentChangesInternal publishing/published is going to be raised there, so are we raising it 2x for the same thing?
|
||||
scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(publishedDocuments, false, evtMsgs), nameof(Published));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1772,7 +1772,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
// however, it had been masked when being trashed, so there's no need for
|
||||
// any special event here - just change its state
|
||||
((Content)content).PublishedState = PublishedState.Unpublishing;
|
||||
content.PublishedState = PublishedState.Unpublishing;
|
||||
}
|
||||
|
||||
PerformMoveLocked(content, parentId, parent, userId, moves, trashed);
|
||||
@@ -1949,7 +1949,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// a copy is not published (but not really unpublishing either)
|
||||
// update the create author and last edit author
|
||||
if (copy.Published)
|
||||
((Content)copy).Published = false;
|
||||
copy.Published = false;
|
||||
copy.CreatorId = userId;
|
||||
copy.WriterId = userId;
|
||||
|
||||
@@ -1993,7 +1993,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// a copy is not published (but not really unpublishing either)
|
||||
// update the create author and last edit author
|
||||
if (descendantCopy.Published)
|
||||
((Content)descendantCopy).Published = false;
|
||||
descendantCopy.Published = false;
|
||||
descendantCopy.CreatorId = userId;
|
||||
descendantCopy.WriterId = userId;
|
||||
|
||||
@@ -2130,7 +2130,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents)
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(itemsA);
|
||||
var saveEventArgs = new ContentSavingEventArgs(itemsA, evtMsgs);
|
||||
if (raiseEvents)
|
||||
{
|
||||
//raise cancelable sorting event
|
||||
@@ -2172,15 +2172,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
var savedEventsArgs = saveEventArgs.ToContentSavedEventArgs();
|
||||
//first saved, then sorted
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs, nameof(Saved));
|
||||
scope.Events.Dispatch(Sorted, this, saveEventArgs, nameof(Sorted));
|
||||
scope.Events.Dispatch(Saved, this, savedEventsArgs, nameof(Saved));
|
||||
scope.Events.Dispatch(Sorted, this, savedEventsArgs, nameof(Sorted));
|
||||
}
|
||||
|
||||
scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.RefreshNode)).ToEventArgs());
|
||||
|
||||
if (raiseEvents && published.Any())
|
||||
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(published, false, false), "Published");
|
||||
scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(published, false, evtMsgs), "Published");
|
||||
|
||||
Audit(AuditType.Sort, userId, 0, "Sorting content performed by user");
|
||||
return OperationResult.Succeed(evtMsgs);
|
||||
@@ -2271,12 +2272,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <summary>
|
||||
/// Occurs before Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, SaveEventArgs<IContent>> Saving;
|
||||
public static event TypedEventHandler<IContentService, ContentSavingEventArgs> Saving;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, SaveEventArgs<IContent>> Saved;
|
||||
public static event TypedEventHandler<IContentService, ContentSavedEventArgs> Saved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Create
|
||||
@@ -2350,12 +2351,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <summary>
|
||||
/// Occurs before publish
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Publishing;
|
||||
public static event TypedEventHandler<IContentService, ContentPublishingEventArgs> Publishing;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after publish
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Published;
|
||||
public static event TypedEventHandler<IContentService, ContentPublishedEventArgs> Published;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before unpublish
|
||||
@@ -2391,14 +2392,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="checkPath"></param>
|
||||
/// <param name="culturesUnpublishing"></param>
|
||||
/// <param name="evtMsgs"></param>
|
||||
/// <param name="culturesPublishing"></param>
|
||||
/// <param name="savingEventArgs"></param>
|
||||
/// <returns></returns>
|
||||
private PublishResult StrategyCanPublish(IScope scope, IContent content, int userId, bool checkPath, IReadOnlyList<string> culturesPublishing, IReadOnlyList<string> culturesUnpublishing, EventMessages evtMsgs)
|
||||
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing, IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
|
||||
{
|
||||
// raise Publishing event
|
||||
if (scope.Events.DispatchCancelable(Publishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
|
||||
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
|
||||
{
|
||||
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled");
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
@@ -2425,7 +2428,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// ensure that the document has published values
|
||||
// either because it is 'publishing' or because it already has a published version
|
||||
if (((Content)content).PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0)
|
||||
if (content.PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0)
|
||||
{
|
||||
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document does not have published values");
|
||||
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
|
||||
@@ -2481,20 +2484,20 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <summary>
|
||||
/// Publishes a document
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="culturesUnpublishing"></param>
|
||||
/// <param name="evtMsgs"></param>
|
||||
/// <param name="culturesPublishing"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// It is assumed that all publishing checks have passed before calling this method like <see cref="StrategyCanPublish"/>
|
||||
/// </remarks>
|
||||
private PublishResult StrategyPublish(IScope scope, IContent content, int userId,
|
||||
IReadOnlyList<string> culturesPublishing, IReadOnlyList<string> culturesUnpublishing,
|
||||
private PublishResult StrategyPublish(IContent content,
|
||||
IReadOnlyCollection<string> culturesPublishing, IReadOnlyCollection<string> culturesUnpublishing,
|
||||
EventMessages evtMsgs)
|
||||
{
|
||||
// change state to publishing
|
||||
((Content)content).PublishedState = PublishedState.Publishing;
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
|
||||
//if this is a variant then we need to log which cultures have been published/unpublished and return an appropriate result
|
||||
if (content.ContentType.VariesByCulture())
|
||||
@@ -2529,10 +2532,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="evtMsgs"></param>
|
||||
/// <returns></returns>
|
||||
private PublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs)
|
||||
private PublishResult StrategyCanUnpublish(IScope scope, IContent content, EventMessages evtMsgs)
|
||||
{
|
||||
// raise Unpublishing event
|
||||
if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
|
||||
@@ -2573,7 +2575,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", content.Name, content.Id);
|
||||
|
||||
// change state to unpublishing
|
||||
((Content)content).PublishedState = PublishedState.Unpublishing;
|
||||
content.PublishedState = PublishedState.Unpublishing;
|
||||
|
||||
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name, content.Id);
|
||||
return attempt;
|
||||
@@ -2591,7 +2593,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <para>Deletes content items of the specified type, and only that type. Does *not* handle content types
|
||||
/// inheritance and compositions, which need to be managed outside of this method.</para>
|
||||
/// </remarks>
|
||||
/// <param name="contentTypeId">Id of the <see cref="IContentType"/></param>
|
||||
/// <param name="contentTypeIds">Id of the <see cref="IContentType"/></param>
|
||||
/// <param name="userId">Optional Id of the user issuing the delete operation</param>
|
||||
public void DeleteOfTypes(IEnumerable<int> contentTypeIds, int userId = 0)
|
||||
{
|
||||
@@ -2710,7 +2712,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
var blueprint = _documentBlueprintRepository.Get(id);
|
||||
if (blueprint != null)
|
||||
((Content)blueprint).Blueprint = true;
|
||||
blueprint.Blueprint = true;
|
||||
return blueprint;
|
||||
}
|
||||
}
|
||||
@@ -2722,7 +2724,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
var blueprint = _documentBlueprintRepository.Get(id);
|
||||
if (blueprint != null)
|
||||
((Content)blueprint).Blueprint = true;
|
||||
blueprint.Blueprint = true;
|
||||
return blueprint;
|
||||
}
|
||||
}
|
||||
@@ -2733,7 +2735,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (content.ParentId != -1)
|
||||
content.ParentId = -1;
|
||||
|
||||
((Content)content).Blueprint = true;
|
||||
content.Blueprint = true;
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -2809,7 +2811,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
return _documentBlueprintRepository.Get(query).Select(x =>
|
||||
{
|
||||
((Content)x).Blueprint = true;
|
||||
x.Blueprint = true;
|
||||
return x;
|
||||
});
|
||||
}
|
||||
@@ -2828,7 +2830,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var blueprints = _documentBlueprintRepository.Get(query).Select(x =>
|
||||
{
|
||||
((Content)x).Blueprint = true;
|
||||
x.Blueprint = true;
|
||||
return x;
|
||||
}).ToArray();
|
||||
|
||||
|
||||
@@ -299,6 +299,11 @@
|
||||
<Compile Include="Deploy\IDataTypeConfigurationConnector.cs" />
|
||||
<Compile Include="DisposableObjectSlim.cs" />
|
||||
<Compile Include="EnumExtensions.cs" />
|
||||
<Compile Include="Events\CancellableEnumerableObjectEventArgs.cs" />
|
||||
<Compile Include="Events\ContentPublishedEventArgs.cs" />
|
||||
<Compile Include="Events\ContentPublishingEventArgs.cs" />
|
||||
<Compile Include="Events\ContentSavedEventArgs.cs" />
|
||||
<Compile Include="Events\ContentSavingEventArgs.cs" />
|
||||
<Compile Include="Events\ExportedMemberEventArgs.cs" />
|
||||
<Compile Include="Events\RolesEventArgs.cs" />
|
||||
<Compile Include="Events\UserGroupWithUsers.cs" />
|
||||
@@ -389,7 +394,6 @@
|
||||
<Compile Include="Models\Consent.cs" />
|
||||
<Compile Include="Models\ConsentExtensions.cs" />
|
||||
<Compile Include="Models\ConsentState.cs" />
|
||||
<Compile Include="Models\ContentCultureInfosCollectionExtensions.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentApp.cs" />
|
||||
<Compile Include="Models\ContentEditing\IContentAppFactory.cs" />
|
||||
<Compile Include="Models\ContentRepositoryExtensions.cs" />
|
||||
|
||||
@@ -47,35 +47,35 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
|
||||
var document = new Content("content", -1, contentType);
|
||||
IContent document = new Content("content", -1, contentType);
|
||||
document.SetCultureName("hello", "en-US");
|
||||
document.SetCultureName("bonjour", "fr-FR");
|
||||
contentService.Save(document);
|
||||
|
||||
//re-get - dirty properties need resetting
|
||||
document = contentService.GetById(document.Id);
|
||||
|
||||
// properties: title, bodyText, keywords, description
|
||||
document.SetValue("title", "title-en", "en-US");
|
||||
|
||||
// touch the culture - required for IsSaving/HasSaved to work
|
||||
document.TouchCulture("fr-FR");
|
||||
|
||||
void OnSaving(IContentService sender, SaveEventArgs<IContent> e)
|
||||
void OnSaving(IContentService sender, ContentSavingEventArgs e)
|
||||
{
|
||||
var saved = e.SavedEntities.First();
|
||||
|
||||
Assert.AreSame(document, saved);
|
||||
|
||||
Assert.IsTrue(saved.IsSavingCulture("fr-FR"));
|
||||
Assert.IsFalse(saved.IsSavingCulture("en-UK"));
|
||||
Assert.IsTrue(e.IsSavingCulture(saved, "fr-FR"));
|
||||
Assert.IsFalse(e.IsSavingCulture(saved, "en-UK"));
|
||||
}
|
||||
|
||||
void OnSaved(IContentService sender, SaveEventArgs<IContent> e)
|
||||
void OnSaved(IContentService sender, ContentSavedEventArgs e)
|
||||
{
|
||||
var saved = e.SavedEntities.First();
|
||||
|
||||
Assert.AreSame(document, saved);
|
||||
|
||||
Assert.IsTrue(saved.HasSavedCulture("fr-FR"));
|
||||
Assert.IsFalse(saved.HasSavedCulture("en-UK"));
|
||||
Assert.IsTrue(e.HasSavedCulture(saved, "fr-FR"));
|
||||
Assert.IsFalse(e.HasSavedCulture(saved, "en-UK"));
|
||||
}
|
||||
|
||||
ContentService.Saving += OnSaving;
|
||||
@@ -103,35 +103,35 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
|
||||
var document = new Content("content", -1, contentType);
|
||||
IContent document = new Content("content", -1, contentType);
|
||||
document.SetCultureName("hello", "en-US");
|
||||
document.SetCultureName("bonjour", "fr-FR");
|
||||
contentService.Save(document);
|
||||
|
||||
// ensure it works and does not throw
|
||||
Assert.IsFalse(document.WasCulturePublished("fr-FR"));
|
||||
Assert.IsFalse(document.WasCulturePublished("en-US"));
|
||||
Assert.IsFalse(document.IsCulturePublished("fr-FR"));
|
||||
Assert.IsFalse(document.IsCulturePublished("en-US"));
|
||||
|
||||
void OnPublishing(IContentService sender, PublishEventArgs<IContent> e)
|
||||
//re-get - dirty properties need resetting
|
||||
document = contentService.GetById(document.Id);
|
||||
|
||||
void OnPublishing(IContentService sender, ContentPublishingEventArgs e)
|
||||
{
|
||||
var publishing = e.PublishedEntities.First();
|
||||
|
||||
Assert.AreSame(document, publishing);
|
||||
|
||||
Assert.IsFalse(publishing.IsPublishingCulture("en-US"));
|
||||
Assert.IsTrue(publishing.IsPublishingCulture("fr-FR"));
|
||||
Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US"));
|
||||
Assert.IsTrue(e.IsPublishingCulture(publishing, "fr-FR"));
|
||||
}
|
||||
|
||||
void OnPublished(IContentService sender, PublishEventArgs<IContent> e)
|
||||
void OnPublished(IContentService sender, ContentPublishedEventArgs e)
|
||||
{
|
||||
var published = e.PublishedEntities.First();
|
||||
|
||||
Assert.AreSame(document, published);
|
||||
|
||||
Assert.IsFalse(published.HasPublishedCulture("en-US"));
|
||||
Assert.IsTrue(published.HasPublishedCulture("fr-FR"));
|
||||
Assert.IsFalse(e.HasPublishedCulture(published, "en-US"));
|
||||
Assert.IsTrue(e.HasPublishedCulture(published, "fr-FR"));
|
||||
}
|
||||
|
||||
ContentService.Publishing += OnPublishing;
|
||||
@@ -140,11 +140,9 @@ namespace Umbraco.Tests.Services
|
||||
ContentService.Publishing -= OnPublishing;
|
||||
ContentService.Published -= OnPublished;
|
||||
|
||||
document = (Content) contentService.GetById(document.Id);
|
||||
document = contentService.GetById(document.Id);
|
||||
|
||||
// ensure it works and does not throw
|
||||
Assert.IsTrue(document.WasCulturePublished("fr-FR"));
|
||||
Assert.IsFalse(document.WasCulturePublished("en-US"));
|
||||
Assert.IsTrue(document.IsCulturePublished("fr-FR"));
|
||||
Assert.IsFalse(document.IsCulturePublished("en-US"));
|
||||
}
|
||||
@@ -165,58 +163,55 @@ namespace Umbraco.Tests.Services
|
||||
propertyType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentService = (ContentService)ServiceContext.ContentService;
|
||||
|
||||
var document = new Content("content", -1, contentType);
|
||||
IContent document = new Content("content", -1, contentType);
|
||||
document.SetCultureName("hello", "en-US");
|
||||
document.SetCultureName("bonjour", "fr-FR");
|
||||
contentService.SaveAndPublish(document);
|
||||
|
||||
// ensure it works and does not throw
|
||||
Assert.IsTrue(document.WasCulturePublished("fr-FR"));
|
||||
Assert.IsTrue(document.WasCulturePublished("en-US"));
|
||||
Assert.IsTrue(document.IsCulturePublished("fr-FR"));
|
||||
Assert.IsTrue(document.IsCulturePublished("en-US"));
|
||||
|
||||
void OnPublishing(IContentService sender, PublishEventArgs<IContent> e)
|
||||
//re-get - dirty properties need resetting
|
||||
document = contentService.GetById(document.Id);
|
||||
|
||||
void OnPublishing(IContentService sender, ContentPublishingEventArgs e)
|
||||
{
|
||||
var publishing = e.PublishedEntities.First();
|
||||
|
||||
Assert.AreSame(document, publishing);
|
||||
|
||||
Assert.IsFalse(publishing.IsPublishingCulture("en-US"));
|
||||
Assert.IsFalse(publishing.IsPublishingCulture("fr-FR"));
|
||||
Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US"));
|
||||
Assert.IsFalse(e.IsPublishingCulture(publishing, "fr-FR"));
|
||||
|
||||
Assert.IsFalse(publishing.IsUnpublishingCulture("en-US"));
|
||||
Assert.IsTrue(publishing.IsUnpublishingCulture("fr-FR"));
|
||||
Assert.IsFalse(e.IsUnpublishingCulture(publishing, "en-US"));
|
||||
Assert.IsTrue(e.IsUnpublishingCulture(publishing, "fr-FR"));
|
||||
}
|
||||
|
||||
void OnPublished(IContentService sender, PublishEventArgs<IContent> e)
|
||||
void OnPublished(IContentService sender, ContentPublishedEventArgs e)
|
||||
{
|
||||
var published = e.PublishedEntities.First();
|
||||
|
||||
Assert.AreSame(document, published);
|
||||
|
||||
Assert.IsFalse(published.HasPublishedCulture("en-US"));
|
||||
Assert.IsFalse(published.HasPublishedCulture("fr-FR"));
|
||||
Assert.IsFalse(e.HasPublishedCulture(published, "en-US"));
|
||||
Assert.IsFalse(e.HasPublishedCulture(published, "fr-FR"));
|
||||
|
||||
Assert.IsFalse(published.HasUnpublishedCulture("en-US"));
|
||||
Assert.IsTrue(published.HasUnpublishedCulture("fr-FR"));
|
||||
Assert.IsFalse(e.HasUnpublishedCulture(published, "en-US"));
|
||||
Assert.IsTrue(e.HasUnpublishedCulture(published, "fr-FR"));
|
||||
}
|
||||
|
||||
document.UnpublishCulture("fr-FR");
|
||||
|
||||
ContentService.Publishing += OnPublishing;
|
||||
ContentService.Published += OnPublished;
|
||||
contentService.SavePublishing(document);
|
||||
contentService.CommitDocumentChanges(document);
|
||||
ContentService.Publishing -= OnPublishing;
|
||||
ContentService.Published -= OnPublished;
|
||||
|
||||
document = (Content) contentService.GetById(document.Id);
|
||||
document = contentService.GetById(document.Id);
|
||||
|
||||
// ensure it works and does not throw
|
||||
Assert.IsFalse(document.WasCulturePublished("fr-FR"));
|
||||
Assert.IsTrue(document.WasCulturePublished("en-US"));
|
||||
Assert.IsFalse(document.IsCulturePublished("fr-FR"));
|
||||
Assert.IsTrue(document.IsCulturePublished("en-US"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user