Merge branch 'temp8' into temp8-contentAndType

# Conflicts:
#	src/Umbraco.Core/Models/Content.cs
#	src/Umbraco.Core/Models/ContentBase.cs
#	src/Umbraco.Tests/Models/ContentTests.cs
This commit is contained in:
Shannon
2019-02-07 11:41:06 +11:00
58 changed files with 1753 additions and 999 deletions

View File

@@ -18,9 +18,15 @@ namespace Umbraco.Core.Models
private ContentScheduleCollection _schedule;
private bool _published;
private PublishedState _publishedState;
private ContentCultureInfosCollection _publishInfos;
private ContentCultureInfosCollection _publishInfosOrig;
private HashSet<string> _editedCultures;
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
@@ -91,7 +97,7 @@ namespace Umbraco.Core.Models
}
set
{
if(_schedule != null)
if (_schedule != null)
_schedule.CollectionChanged -= ScheduleCollectionChanged;
SetPropertyValueAndDetectChanges(value, ref _schedule, nameof(ContentSchedule));
if (_schedule != null)
@@ -127,15 +133,16 @@ namespace Umbraco.Core.Models
/// <summary>
/// Gets or sets a value indicating whether this content item is published or not.
/// </summary>
/// <remarks>
/// the setter is should only be invoked from
/// - the ContentFactory when creating a content entity from a dto
/// - the ContentRepository when updating a content entity
/// </remarks>
[DataMember]
public bool Published
{
get => _published;
// the setter is internal and should only be invoked from
// - the ContentFactory when creating a content entity from a dto
// - the ContentRepository when updating a content entity
internal set
set
{
SetPropertyValueAndDetectChanges(value, ref _published, nameof(Published));
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
@@ -161,27 +168,31 @@ namespace Umbraco.Core.Models
}
[IgnoreDataMember]
public bool Edited { get; internal set; }
public bool Edited { get; set; }
/// <inheritdoc />
[IgnoreDataMember]
public DateTime? PublishDate { get; internal set; } // set by persistence
public DateTime? PublishDate { get; set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public int? PublisherId { get; internal set; } // set by persistence
public int? PublisherId { get; set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public int? PublishTemplateId { get; internal set; } // set by persistence
public int? PublishTemplateId { get; set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public string PublishName { get; internal set; } // set by persistence
public string PublishName { get; set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public IEnumerable<string> EditedCultures => CultureInfos.Keys.Where(IsCultureEdited);
public IEnumerable<string> EditedCultures
{
get => CultureInfos.Keys.Where(IsCultureEdited);
set => _editedCultures = value == null ? null : new HashSet<string>(value, StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc />
[IgnoreDataMember]
@@ -193,32 +204,6 @@ namespace Umbraco.Core.Models
// a non-available culture could not become published anyways
=> _publishInfos != null && _publishInfos.ContainsKey(culture);
/// <inheritdoc />
public bool WasCulturePublished(string culture)
// just check _publishInfosOrig - a copy of _publishInfos
// a non-available culture could not become published anyways
=> _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
// adjust dates to sync between version, cultures etc
// used by the repo when persisting
internal void AdjustDates(DateTime date)
{
foreach (var culture in PublishedCultures.ToList())
{
if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos))
continue;
if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig)
&& publishInfosOrig.Date == publishInfos.Date)
continue;
_publishInfos.AddOrUpdate(culture, publishInfos.Name, date);
if (CultureInfos.TryGetValue(culture, out var infos))
SetCultureInfo(culture, infos.Name, date);
}
}
/// <inheritdoc />
public bool IsCultureEdited(string culture)
=> IsCultureAvailable(culture) && // is available, and
@@ -227,7 +212,23 @@ namespace Umbraco.Core.Models
/// <inheritdoc/>
[IgnoreDataMember]
public IReadOnlyDictionary<string, ContentCultureInfos> PublishCultureInfos => _publishInfos ?? NoInfos;
public ContentCultureInfosCollection PublishCultureInfos
{
get
{
if (_publishInfos != null) return _publishInfos;
_publishInfos = new ContentCultureInfosCollection();
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
return _publishInfos;
}
set
{
if (_publishInfos != null) _publishInfos.CollectionChanged -= PublishNamesCollectionChanged;
_publishInfos = value;
if (_publishInfos != null)
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
}
}
/// <inheritdoc/>
public string GetPublishName(string culture)
@@ -244,67 +245,7 @@ namespace Umbraco.Core.Models
if (culture.IsNullOrWhiteSpace()) return PublishDate;
if (!ContentType.VariesByCulture()) return null;
if (_publishInfos == null) return null;
return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null;
}
// internal for repository
internal void SetPublishInfo(string culture, string name, DateTime date)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullOrEmptyException(nameof(name));
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_publishInfos == null)
{
_publishInfos = new ContentCultureInfosCollection();
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
}
_publishInfos.AddOrUpdate(culture, name, date);
}
private void ClearPublishInfos()
{
_publishInfos = null;
}
private void ClearPublishInfo(string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_publishInfos == null) return;
_publishInfos.Remove(culture);
if (_publishInfos.Count == 0) _publishInfos = null;
// set the culture to be dirty - it's been modified
TouchCultureInfo(culture);
}
// sets a publish edited
internal void SetCultureEdited(string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_editedCultures == null)
_editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_editedCultures.Add(culture.ToLowerInvariant());
}
// sets all publish edited
internal void SetCultureEdited(IEnumerable<string> cultures)
{
if (cultures == null)
{
_editedCultures = null;
}
else
{
var editedCultures = new HashSet<string>(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase);
_editedCultures = editedCultures.Count > 0 ? editedCultures : null;
}
return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?)null;
}
/// <summary>
@@ -313,94 +254,54 @@ 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]
public int PublishedVersionId { get; internal set; }
public int PublishedVersionId { get; set; }
[DataMember]
public bool Blueprint { get; internal set; }
/// <inheritdoc />
public bool PublishCulture(string culture = "*")
{
culture = culture.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 (!ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\".");
// the values we want to publish should be valid
if (ValidateProperties(culture).Any())
return false;
var alsoInvariant = false;
if (culture == "*") // all cultures
{
foreach (var c in AvailableCultures)
{
var name = GetCultureName(c);
if (string.IsNullOrWhiteSpace(name))
return false;
SetPublishInfo(c, name, DateTime.Now);
}
}
else if (culture == null) // invariant culture
{
if (string.IsNullOrWhiteSpace(Name))
return false;
// PublishName set by repository - nothing to do here
}
else // one single culture
{
var name = GetCultureName(culture);
if (string.IsNullOrWhiteSpace(name))
return false;
SetPublishInfo(culture, name, DateTime.Now);
alsoInvariant = true; // we also want to publish invariant values
}
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in Properties)
{
property.PublishValues(culture);
if (alsoInvariant)
property.PublishValues(null);
}
_publishedState = PublishedState.Publishing;
return true;
}
/// <inheritdoc />
public void UnpublishCulture(string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
// the variation should be supported by the content type properties
if (!ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\".");
if (culture == "*") // all cultures
ClearPublishInfos();
else // one single culture
ClearPublishInfo(culture);
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in Properties)
property.UnpublishValues(culture);
_publishedState = PublishedState.Publishing;
}
public bool Blueprint { get; set; }
/// <summary>
/// Changes the <see cref="ContentType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
public void ChangeContentType(IContentType contentType)
internal void ChangeContentType(IContentType contentType)
{
ChangeContentType(contentType, false);
}
@@ -411,7 +312,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
public void ChangeContentType(IContentType contentType, bool clearProperties)
internal void ChangeContentType(IContentType contentType, bool clearProperties)
{
ChangeContentType(new SimpleContentType(contentType));
@@ -424,25 +325,91 @@ namespace Umbraco.Core.Models
Properties.CollectionChanged += PropertiesChanged;
}
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 || _currentPublishCultureChanges.addedCultures.Count == 0 ? null : new HashSet<string>(_currentPublishCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase);
_previousPublishCultureChanges.removedCultures = _currentPublishCultureChanges.removedCultures == null || _currentPublishCultureChanges.removedCultures.Count == 0 ? null : new HashSet<string>(_currentPublishCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase);
_previousPublishCultureChanges.updatedCultures = _currentPublishCultureChanges.updatedCultures == null || _currentPublishCultureChanges.updatedCultures.Count == 0 ? 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;
// Make a copy of the _publishInfos, this is purely so that we can detect
// if this entity's previous culture publish state (regardless of the rememberDirty flag)
_publishInfosOrig = _publishInfos == null
? null
: new ContentCultureInfosCollection(_publishInfos);
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>
@@ -466,11 +433,13 @@ namespace Umbraco.Core.Models
var clonedContent = (Content)clone;
//fixme - need to reset change tracking bits
//if culture infos exist then deal with event bindings
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
}
@@ -481,6 +450,14 @@ namespace Umbraco.Core.Models
clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone
clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler
}
clonedContent._currentPublishCultureChanges.updatedCultures = null;
clonedContent._currentPublishCultureChanges.addedCultures = null;
clonedContent._currentPublishCultureChanges.removedCultures = null;
clonedContent._previousPublishCultureChanges.updatedCultures = null;
clonedContent._previousPublishCultureChanges.addedCultures = null;
clonedContent._previousPublishCultureChanges.removedCultures = null;
}
}
}

View File

@@ -18,13 +18,19 @@ namespace Umbraco.Core.Models
[DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentType.Alias}")]
public abstract class ContentBase : TreeEntityBase, IContentBase
{
protected static readonly ContentCultureInfosCollection NoInfos = new ContentCultureInfosCollection();
private int _contentTypeId;
private int _writerId;
private PropertyCollection _properties;
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.
/// </summary>
@@ -74,24 +80,26 @@ namespace Umbraco.Core.Models
OnPropertyChanged(nameof(Properties));
}
/// <summary>
/// Id of the user who wrote/updated this entity
/// </summary>
[DataMember]
public virtual int WriterId
public int WriterId
{
get => _writerId;
set => SetPropertyValueAndDetectChanges(value, ref _writerId, nameof(WriterId));
}
[IgnoreDataMember]
public int VersionId { get; internal set; }
public int VersionId { get; set; }
/// <summary>
/// Integer Id of the default ContentType
/// </summary>
[DataMember]
public virtual int ContentTypeId
public int ContentTypeId
{
get
{
@@ -114,11 +122,12 @@ namespace Umbraco.Core.Models
/// </remarks>
[DataMember]
[DoNotClone]
public virtual PropertyCollection Properties
public PropertyCollection Properties
{
get => _properties;
set
{
if (_properties != null) _properties.CollectionChanged -= PropertiesChanged;
_properties = value;
_properties.CollectionChanged += PropertiesChanged;
}
@@ -142,7 +151,23 @@ namespace Umbraco.Core.Models
/// <inheritdoc />
[DataMember]
public virtual IReadOnlyDictionary<string, ContentCultureInfos> CultureInfos => _cultureInfos ?? NoInfos;
public ContentCultureInfosCollection CultureInfos
{
get
{
if (_cultureInfos != null) return _cultureInfos;
_cultureInfos = new ContentCultureInfosCollection();
_cultureInfos.CollectionChanged += CultureInfosCollectionChanged;
return _cultureInfos;
}
set
{
if (_cultureInfos != null) _cultureInfos.CollectionChanged -= CultureInfosCollectionChanged;
_cultureInfos = value;
if (_cultureInfos != null)
_cultureInfos.CollectionChanged += CultureInfosCollectionChanged;
}
}
/// <inheritdoc />
public string GetCultureName(string culture)
@@ -159,7 +184,7 @@ namespace Umbraco.Core.Models
if (culture.IsNullOrWhiteSpace()) return null;
if (!ContentType.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 />
@@ -177,7 +202,7 @@ namespace Umbraco.Core.Models
}
else // set
{
SetCultureInfo(culture, name, DateTime.Now);
this.SetCultureInfo(culture, name, DateTime.Now);
}
}
else // set on invariant content type
@@ -189,13 +214,7 @@ namespace Umbraco.Core.Models
}
}
protected void ClearCultureInfos()
{
_cultureInfos?.Clear();
_cultureInfos = null;
}
protected void ClearCultureInfo(string culture)
private void ClearCultureInfo(string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
@@ -206,36 +225,44 @@ namespace Umbraco.Core.Models
_cultureInfos = null;
}
protected void TouchCultureInfo(string culture)
{
if (_cultureInfos == null || !_cultureInfos.TryGetValue(culture, out var infos)) return;
_cultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now);
}
// internal for repository
internal void SetCultureInfo(string culture, string name, DateTime date)
{
if (name.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(name));
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_cultureInfos == null)
{
_cultureInfos = new ContentCultureInfosCollection();
_cultureInfos.CollectionChanged += CultureInfosCollectionChanged;
}
_cultureInfos.AddOrUpdate(culture, name, date);
}
/// <summary>
/// Handles culture infos collection changes.
/// </summary>
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
@@ -243,11 +270,11 @@ namespace Umbraco.Core.Models
#region Has, Get, Set, Publish Property Value
/// <inheritdoc />
public virtual bool HasProperty(string propertyTypeAlias)
public bool HasProperty(string propertyTypeAlias)
=> Properties.Contains(propertyTypeAlias);
/// <inheritdoc />
public virtual object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false)
public object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false)
{
return Properties.TryGetValue(propertyTypeAlias, out var property)
? property.GetValue(culture, segment, published)
@@ -255,7 +282,7 @@ namespace Umbraco.Core.Models
}
/// <inheritdoc />
public virtual TValue GetValue<TValue>(string propertyTypeAlias, string culture = null, string segment = null, bool published = false)
public TValue GetValue<TValue>(string propertyTypeAlias, string culture = null, string segment = null, bool published = false)
{
if (!Properties.TryGetValue(propertyTypeAlias, out var property))
return default;
@@ -265,11 +292,13 @@ namespace Umbraco.Core.Models
}
/// <inheritdoc />
public virtual void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null)
public void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null)
{
if (Properties.Contains(propertyTypeAlias))
{
Properties[propertyTypeAlias].SetValue(value, culture, segment);
//bump the culture to be flagged for updating
this.TouchCulture(culture);
return;
}
@@ -281,120 +310,45 @@ namespace Umbraco.Core.Models
var property = propertyType.CreateProperty();
property.SetValue(value, culture, segment);
Properties.Add(property);
}
#endregion
#region Copy
/// <inheritdoc />
public virtual 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 (!ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.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;
foreach (var (otherCulture, otherInfos) in other.CultureInfos)
{
if (culture == "*" || culture == otherCulture)
SetCultureName(otherInfos.Name, otherCulture);
}
}
#endregion
#region Validation
/// <inheritdoc />
public virtual 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();
//bump the culture to be flagged for updating
this.TouchCulture(culture);
}
#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 || _currentCultureChanges.addedCultures.Count == 0 ? null : new HashSet<string>(_currentCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase);
_previousCultureChanges.removedCultures = _currentCultureChanges.removedCultures == null || _currentCultureChanges.removedCultures.Count == 0 ? null : new HashSet<string>(_currentCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase);
_previousCultureChanges.updatedCultures = _currentCultureChanges.updatedCultures == null || _currentCultureChanges.updatedCultures.Count == 0 ? 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);
@@ -443,6 +397,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 +424,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();
}
@@ -493,7 +481,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
}
@@ -501,9 +489,17 @@ 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
}
clonedContent._currentCultureChanges.updatedCultures = null;
clonedContent._currentCultureChanges.addedCultures = null;
clonedContent._currentCultureChanges.removedCultures = null;
clonedContent._previousCultureChanges.updatedCultures = null;
clonedContent._previousCultureChanges.addedCultures = null;
clonedContent._previousCultureChanges.removedCultures = null;
}
}
}

View File

@@ -17,20 +17,6 @@ namespace Umbraco.Core.Models
public ContentCultureInfosCollection()
: base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ContentCultureInfosCollection"/> class with items.
/// </summary>
public ContentCultureInfosCollection(IEnumerable<ContentCultureInfos> items)
: base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase)
{
// make sure to add *copies* and not the original items,
// as items can be modified by AddOrUpdate, and therefore
// the new collection would be impacted by changes made
// to the old collection
foreach (var item in items)
Add(new ContentCultureInfos(item));
}
/// <summary>
/// Adds or updates a <see cref="ContentCultureInfos"/> instance.
@@ -53,7 +39,7 @@ namespace Umbraco.Core.Models
Name = name,
Date = date
});
}
}
}
/// <inheritdoc />

View File

@@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Models
{
/// <summary>
/// Extension methods used to manipulate content variations by the document repository
/// </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))
throw new ArgumentNullOrEmptyException(nameof(name));
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
content.PublishCultureInfos.AddOrUpdate(culture, name, date);
}
/// <summary>
/// Used to synchronize all culture dates to the same date if they've been modified
/// </summary>
/// <param name="content"></param>
/// <param name="date"></param>
/// <remarks>
/// This is so that in an operation where (for example) 2 languages are updates like french and english, it is possible that
/// these dates assigned to them differ by a couple of Ticks, but we need to ensure they are persisted at the exact same time.
/// </remarks>
public static void AdjustDates(this IContent content, DateTime date)
{
foreach (var culture in content.PublishedCultures.ToList())
{
if (!content.PublishCultureInfos.TryGetValue(culture, out var publishInfos))
continue;
if (!publishInfos.IsDirty())
continue; //if it's not dirty, it means it hasn't changed so there's nothing to adjust
content.PublishCultureInfos.AddOrUpdate(culture, publishInfos.Name, date);
if (content.CultureInfos.TryGetValue(culture, out var infos))
SetCultureInfo(content, culture, infos.Name, date);
}
}
// sets the edited cultures on the content
public static void SetCultureEdited(this IContent content, IEnumerable<string> cultures)
{
if (cultures == null)
content.EditedCultures = null;
else
{
var editedCultures = new HashSet<string>(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase);
content.EditedCultures = editedCultures.Count > 0 ? editedCultures : null;
}
}
public static void SetCultureInfo(this IContentBase content, string culture, string name, DateTime date)
{
if (name.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(name));
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
content.CultureInfos.AddOrUpdate(culture, name, date);
}
public static bool PublishCulture(this IContent content, string culture = "*")
{
culture = culture.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}\".");
// the values we want to publish should be valid
if (content.ValidateProperties(culture).Any())
return false;
var alsoInvariant = false;
if (culture == "*") // all cultures
{
foreach (var c in content.AvailableCultures)
{
var name = content.GetCultureName(c);
if (string.IsNullOrWhiteSpace(name))
return false;
content.SetPublishInfo(c, name, DateTime.Now);
}
}
else if (culture == null) // invariant culture
{
if (string.IsNullOrWhiteSpace(content.Name))
return false;
// PublishName set by repository - nothing to do here
}
else // one single culture
{
var name = content.GetCultureName(culture);
if (string.IsNullOrWhiteSpace(name))
return false;
content.SetPublishInfo(culture, name, DateTime.Now);
alsoInvariant = true; // we also want to publish invariant values
}
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in content.Properties)
{
property.PublishValues(culture);
if (alsoInvariant)
property.PublishValues(null);
}
content.PublishedState = PublishedState.Publishing;
return true;
}
public static void UnpublishCulture(this IContent content, string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
// the variation should be supported by the content type properties
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}\".");
if (culture == "*") // all cultures
content.ClearPublishInfos();
else // one single culture
content.ClearPublishInfo(culture);
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in content.Properties)
property.UnpublishValues(culture);
content.PublishedState = PublishedState.Publishing;
}
public static void ClearPublishInfos(this IContent content)
{
content.PublishCultureInfos = null;
}
public static void ClearPublishInfo(this IContent content, string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
content.PublishCultureInfos.Remove(culture);
// set the culture to be dirty - it's been modified
content.TouchCulture(culture);
}
/// <summary>
/// Updates a culture date, if the culture exists.
/// </summary>
public static void TouchCulture(this IContentBase content, string culture)
{
if (culture.IsNullOrWhiteSpace()) return;
if (!content.CultureInfos.TryGetValue(culture, out var infos)) return;
content.CultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now);
}
}
}

View File

@@ -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.

View File

@@ -25,46 +25,46 @@ namespace Umbraco.Core.Models
/// <summary>
/// Gets a value indicating whether the content is published.
/// </summary>
bool Published { get; }
bool Published { get; set; }
PublishedState PublishedState { get; }
PublishedState PublishedState { get; set; }
/// <summary>
/// Gets a value indicating whether the content has been edited.
/// </summary>
bool Edited { get; }
bool Edited { get; set; }
/// <summary>
/// Gets the published version identifier.
/// </summary>
int PublishedVersionId { get; }
int PublishedVersionId { get; set; }
/// <summary>
/// Gets a value indicating whether the content item is a blueprint.
/// </summary>
bool Blueprint { get; }
bool Blueprint { get; set; }
/// <summary>
/// Gets the template id used to render the published version of the content.
/// </summary>
/// <remarks>When editing the content, the template can change, but this will not until the content is published.</remarks>
int? PublishTemplateId { get; }
int? PublishTemplateId { get; set; }
/// <summary>
/// Gets the name of the published version of the content.
/// </summary>
/// <remarks>When editing the content, the name can change, but this will not until the content is published.</remarks>
string PublishName { get; }
string PublishName { get; set; }
/// <summary>
/// Gets the identifier of the user who published the content.
/// </summary>
int? PublisherId { get; }
int? PublisherId { get; set; }
/// <summary>
/// Gets the date and time the content was published.
/// </summary>
DateTime? PublishDate { get; }
DateTime? PublishDate { get; set; }
/// <summary>
/// Gets a value indicating whether a culture is published.
@@ -74,19 +74,11 @@ namespace Umbraco.Core.Models
/// and the content published name for this culture is non-null. It becomes non-published
/// whenever values for this culture are unpublished.</para>
/// <para>A culture becomes published as soon as PublishCulture has been invoked,
/// even though the document might now have been saved yet (and can have no identity).</para>
/// even though the document might not have been saved yet (and can have no identity).</para>
/// <para>Does not support the '*' wildcard (returns false).</para>
/// </remarks>
bool IsCulturePublished(string culture);
/// <summary>
/// Gets a value indicating whether a culture was published.
/// </summary>
/// <remarks>
/// <para>Mirrors <see cref="IsCulturePublished"/> whenever the content item is saved.</para>
/// </remarks>
bool WasCulturePublished(string culture);
/// <summary>
/// Gets the date a culture was published.
/// </summary>
@@ -120,7 +112,7 @@ namespace Umbraco.Core.Models
/// <para>Because a dictionary key cannot be <c>null</c> this cannot get the invariant
/// name, which must be get via the <see cref="PublishName"/> property.</para>
/// </remarks>
IReadOnlyDictionary<string, ContentCultureInfos> PublishCultureInfos { get; }
ContentCultureInfosCollection PublishCultureInfos { get; set; }
/// <summary>
/// Gets the published cultures.
@@ -130,47 +122,13 @@ namespace Umbraco.Core.Models
/// <summary>
/// Gets the edited cultures.
/// </summary>
IEnumerable<string> EditedCultures { get; }
// TODO: these two should move to some kind of service
/// <summary>
/// Changes the <see cref="IContentType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
void ChangeContentType(IContentType contentType);
/// <summary>
/// Changes the <see cref="IContentType"/> for the current content object and removes PropertyTypes,
/// which are not part of the new ContentType.
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
void ChangeContentType(IContentType contentType, bool clearProperties);
IEnumerable<string> EditedCultures { get; set; }
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <returns></returns>
IContent DeepCloneWithResetIdentities();
/// <summary>
/// Registers a culture to be published.
/// </summary>
/// <returns>A value indicating whether the culture can be published.</returns>
/// <remarks>
/// <para>Fails if properties don't pass variant validation rules.</para>
/// <para>Publishing must be finalized via the content service SavePublishing method.</para>
/// </remarks>
bool PublishCulture(string culture = "*");
/// <summary>
/// Registers a culture to be unpublished.
/// </summary>
/// <remarks>
/// <para>Unpublishing must be finalized via the content service SavePublishing method.</para>
/// </remarks>
void UnpublishCulture(string culture = "*");
}
}

View File

@@ -31,7 +31,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Gets the version identifier.
/// </summary>
int VersionId { get; }
int VersionId { get; set; }
/// <summary>
/// Sets the name of the content item for a specified culture.
@@ -62,8 +62,8 @@ namespace Umbraco.Core.Models
/// <para>Because a dictionary key cannot be <c>null</c> this cannot contain the invariant
/// culture name, which must be get or set via the <see cref="TreeEntityBase.Name"/> property.</para>
/// </remarks>
IReadOnlyDictionary<string, ContentCultureInfos> CultureInfos { get; }
ContentCultureInfosCollection CultureInfos { get; set; }
/// <summary>
/// Gets the available cultures.
/// </summary>
@@ -125,16 +125,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 = "*");
}
}

View File

@@ -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);
}
}

View File

@@ -70,9 +70,7 @@ namespace Umbraco.Core.Models.PublishedContent
if (_publishedDataTypes == null)
{
var dataTypes = _dataTypeService.GetAll();
_publishedDataTypes = dataTypes.ToDictionary(
x => x.Id,
x => new PublishedDataType(x.Id, x.EditorAlias, x is DataType d ? d.GetLazyConfiguration() : new Lazy<object>(() => x.Configuration)));
_publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType);
}
publishedDataTypes = _publishedDataTypes;
@@ -89,12 +87,12 @@ namespace Umbraco.Core.Models.PublishedContent
{
lock (_publishedDataTypesLocker)
{
foreach (var id in ids)
_publishedDataTypes.Remove(id);
var dataTypes = _dataTypeService.GetAll(ids);
foreach (var dataType in dataTypes)
_publishedDataTypes[dataType.Id] = new PublishedDataType(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy<object>(() => dataType.Configuration));
_publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType);
}
}
private PublishedDataType CreatePublishedDataType(IDataType dataType)
=> new PublishedDataType(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy<object>(() => dataType.Configuration));
}
}