From 09f499d5d62faa0b90e2f3c5abf9ac4c6a2197e6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 7 Nov 2017 19:49:14 +0100 Subject: [PATCH] More content refactoring (WIP, barely builds) --- .../Collections/DeepCloneableList.cs | 31 +- src/Umbraco.Core/IO/MediaFileSystem.cs | 8 +- .../Media/UploadAutoFillProperties.cs | 16 +- src/Umbraco.Core/Models/Content.cs | 50 +- src/Umbraco.Core/Models/ContentBase.cs | 588 +++++++++----- src/Umbraco.Core/Models/ContentExtensions.cs | 40 +- .../Models/EntityBase/ICanBeDirty.cs | 22 +- .../Models/EntityBase/IRememberBeingDirty.cs | 27 +- .../EntityBase/TracksChangesEntityBase.cs | 213 +++-- src/Umbraco.Core/Models/IContentBase.cs | 49 +- .../Models/Identity/BackOfficeIdentityUser.cs | 72 +- src/Umbraco.Core/Models/Macro.cs | 6 +- src/Umbraco.Core/Models/MediaExtensions.cs | 2 +- src/Umbraco.Core/Models/Member.cs | 54 +- src/Umbraco.Core/Models/Property.cs | 347 +++++--- src/Umbraco.Core/Models/PropertyCollection.cs | 188 +++-- src/Umbraco.Core/Models/PropertyType.cs | 45 +- src/Umbraco.Core/Models/PublicAccessEntry.cs | 6 +- src/Umbraco.Core/Models/PublishedState.cs | 35 +- src/Umbraco.Core/Models/Rdbms/ContentDto.cs | 4 + .../Models/Rdbms/ContentVersionDto.cs | 5 + src/Umbraco.Core/Models/Rdbms/DocumentDto.cs | 28 +- .../Models/Rdbms/DocumentVersionDto.cs | 27 + src/Umbraco.Core/Models/Rdbms/MemberDto.cs | 4 + .../Persistence/Constants-DatabaseSchema.cs | 1 + .../Persistence/Factories/ContentFactory.cs | 221 +++-- .../Persistence/Factories/MediaFactory.cs | 166 ++-- .../Persistence/Factories/MemberFactory.cs | 152 ++-- .../Persistence/Factories/PropertyFactory.cs | 157 ++-- .../Persistence/Mappers/ContentMapper.cs | 2 +- .../Persistence/Mappers/PropertyMapper.cs | 1 - .../Migrations/Syntax/Create/CreateBuilder.cs | 5 +- .../Syntax/Create/ICreateBuilder.cs | 2 +- .../TargetVersionEight/VariantsMigration.cs | 192 +++-- .../Persistence/NPocoSqlExtensions.cs | 2 +- .../Repositories/ContentRepository.cs | 756 +++++++----------- .../Interfaces/IRepositoryVersionable.cs | 8 +- .../Repositories/MediaRepository.cs | 470 ++++++----- .../Repositories/MemberRepository.cs | 626 +++++++-------- .../Repositories/SimilarNodeName.cs | 14 +- .../Repositories/VersionableRepositoryBase.cs | 240 +++--- .../PropertyEditors/PropertyValueEditor.cs | 24 +- src/Umbraco.Core/Services/ContentService.cs | 6 +- .../Services/EntityXmlSerializer.cs | 2 +- .../Services/NotificationService.cs | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Examine/UmbracoContentIndexer.cs | 8 +- src/Umbraco.Examine/UmbracoMemberIndexer.cs | 4 +- .../Integration/ContentEventsTests.cs | 24 +- .../SqlScripts/SqlResources.Designer.cs | 2 +- .../Upgrades/SqlCeDataUpgradeTest.cs | 76 -- src/Umbraco.Tests/Models/Collections/Item.cs | 5 + .../Models/ContentExtensionsTests.cs | 36 +- src/Umbraco.Tests/Models/ContentTests.cs | 40 +- src/Umbraco.Tests/Models/ContentXmlTest.cs | 8 +- .../Mapping/ContentWebModelMappingTests.cs | 8 +- src/Umbraco.Tests/Models/MediaXmlTest.cs | 10 +- .../Persistence/NPocoTests/NPocoSqlTests.cs | 8 +- .../ContentRepositorySqlClausesTest.cs | 15 +- .../Persistence/Querying/QueryBuilderTests.cs | 5 +- .../Repositories/ContentRepositoryTest.cs | 9 +- .../Repositories/ContentTypeRepositoryTest.cs | 4 +- .../Repositories/SimilarNodeNameTests.cs | 42 +- .../MultiValuePropertyEditorTests.cs | 15 +- .../PropertyEditorValueEditorTests.cs | 15 +- .../PublishedContent/PublishedMediaTests.cs | 2 +- .../Services/ContentServiceTests.cs | 30 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../Editors/ContentControllerBase.cs | 4 +- src/Umbraco.Web/Editors/ImagesController.cs | 4 +- src/Umbraco.Web/Editors/MemberController.cs | 2 +- src/Umbraco.Web/Models/PublishedProperty.cs | 2 +- .../PropertyEditors/DatePropertyEditor.cs | 2 +- .../FileUploadPropertyEditor.cs | 10 +- .../ImageCropperPropertyEditor.cs | 12 +- .../ImageCropperPropertyValueEditor.cs | 4 +- .../MultipleTextStringPropertyEditor.cs | 4 +- .../NestedContentPropertyEditor.cs | 18 +- .../PublishValueValueEditor.cs | 4 +- .../PublishValuesMultipleValueEditor.cs | 6 +- .../PropertyEditors/RichTextPropertyEditor.cs | 4 +- .../PropertyEditors/TextOnlyValueEditor.cs | 4 +- .../NuCache/DataSource/Database.cs | 16 +- .../PublishedCache/NuCache/PublishedMember.cs | 2 +- .../NuCache/PublishedSnapshotService.cs | 2 +- .../XmlPublishedCache/PublishedMediaCache.cs | 2 +- src/Umbraco.Web/Security/MembershipHelper.cs | 8 +- .../PublishAfterUpgradeToVersionSixth.cs | 76 -- src/Umbraco.Web/Umbraco.Web.csproj | 1 - src/Umbraco.Web/umbraco.presentation/page.cs | 2 +- 90 files changed, 2706 insertions(+), 2769 deletions(-) create mode 100644 src/Umbraco.Core/Models/Rdbms/DocumentVersionDto.cs delete mode 100644 src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs delete mode 100644 src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs index 8afedca08b..d0eb7db414 100644 --- a/src/Umbraco.Core/Collections/DeepCloneableList.cs +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -94,26 +92,27 @@ namespace Umbraco.Core.Collections return this.OfType().Any(x => x.WasDirty()); } - /// - /// Always returns false, the list has no properties we need to report - /// - /// - /// + /// + /// Always return false, the list has no properties that can be dirty. public bool IsPropertyDirty(string propName) { return false; } - /// - /// Always returns false, the list has no properties we need to report - /// - /// - /// + /// + /// Always return false, the list has no properties that can be dirty. public bool WasPropertyDirty(string propertyName) { return false; } + /// + /// Always return an empty enumerable, the list has no properties that can be dirty. + public IEnumerable GetDirtyProperties() + { + return Enumerable.Empty(); + } + public void ResetDirtyProperties() { foreach (var dc in this.OfType()) @@ -122,19 +121,19 @@ namespace Umbraco.Core.Collections } } - public void ForgetPreviouslyDirtyProperties() + public void ResetWereDirtyProperties() { foreach (var dc in this.OfType()) { - dc.ForgetPreviouslyDirtyProperties(); + dc.ResetWereDirtyProperties(); } } - public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public void ResetDirtyProperties(bool rememberDirty) { foreach (var dc in this.OfType()) { - dc.ResetDirtyProperties(rememberPreviouslyChangedProperties); + dc.ResetDirtyProperties(rememberDirty); } } } diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index 5f6e0e20d7..b5955f825d 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -344,21 +344,21 @@ namespace Umbraco.Core.IO public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream) { var property = GetProperty(content, propertyTypeAlias); - var svalue = property.Value as string; + var svalue = property.GetValue() as string; var oldpath = svalue == null ? null : GetRelativePath(svalue); var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath); - property.Value = GetUrl(filepath); + property.SetValue(GetUrl(filepath)); SetUploadFile(content, property, filepath, filestream); } public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath) { var property = GetProperty(content, propertyTypeAlias); - var svalue = property.Value as string; + var svalue = property.GetValue() as string; var oldpath = svalue == null ? null : GetRelativePath(svalue); // FIXME DELETE? if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath) DeleteFile(oldpath); - property.Value = GetUrl(filepath); + property.SetValue(GetUrl(filepath)); using (var filestream = OpenFile(filepath)) { SetUploadFile(content, property, filepath, filestream); diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index e38c2adf3c..2639024cb0 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -180,16 +180,16 @@ namespace Umbraco.Core.Media if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias].Value = size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty; + content.Properties[autoFillConfig.WidthFieldAlias].SetValue(size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty); if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias].Value = size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty; + content.Properties[autoFillConfig.HeightFieldAlias].SetValue(size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty); if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias].Value = length; + content.Properties[autoFillConfig.LengthFieldAlias].SetValue(length); if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias].Value = extension; + content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(extension); } private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig) @@ -198,16 +198,16 @@ namespace Umbraco.Core.Media if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.WidthFieldAlias].SetValue(string.Empty); if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.HeightFieldAlias].SetValue(string.Empty); if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.LengthFieldAlias].SetValue(string.Empty); if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(string.Empty); } } } diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 8b3c72a0ac..b16270a867 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -22,6 +22,8 @@ namespace Umbraco.Core.Models private int _writer; private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name. + private static readonly Lazy Ps = new Lazy(); + /// /// Constructor for creating a Content object /// @@ -70,8 +72,7 @@ namespace Umbraco.Core.Models PublishedState = PublishedState.Unpublished; } - private static readonly Lazy Ps = new Lazy(); - + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); @@ -94,8 +95,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual ITemplate Template { - get { return _template ?? _contentType.DefaultTemplate; } - set { SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); } + get => _template ?? _contentType.DefaultTemplate; + set => SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); } /// @@ -132,7 +133,7 @@ namespace Umbraco.Core.Models [DataMember] public bool Published { - get { return _published; } + get => _published; internal set { SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); @@ -142,10 +143,7 @@ namespace Umbraco.Core.Models } [IgnoreDataMember] - public bool PublishedOriginal - { - get { return _publishedOriginal ?? false; } - } + public bool PublishedOriginal => _publishedOriginal ?? false; /// /// Language of the data contained within this Content object. @@ -154,8 +152,8 @@ namespace Umbraco.Core.Models [EditorBrowsable(EditorBrowsableState.Never)] public string Language { - get { return _language; } - set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } + get => _language; + set => SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } /// @@ -164,8 +162,8 @@ namespace Umbraco.Core.Models [DataMember] public DateTime? ReleaseDate { - get { return _releaseDate; } - set { SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); } + get => _releaseDate; + set => SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); } /// @@ -174,8 +172,8 @@ namespace Umbraco.Core.Models [DataMember] public DateTime? ExpireDate { - get { return _expireDate; } - set { SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); } + get => _expireDate; + set => SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); } /// @@ -184,8 +182,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual int WriterId { - get { return _writer; } - set { SetPropertyValueAndDetectChanges(value, ref _writer, Ps.Value.WriterSelector); } + get => _writer; + set => SetPropertyValueAndDetectChanges(value, ref _writer, Ps.Value.WriterSelector); } /// @@ -197,18 +195,15 @@ namespace Umbraco.Core.Models [DataMember] internal string NodeName { - get { return _nodeName; } - set { SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); } + get => _nodeName; + set => SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); } /// /// Gets the ContentType used by this content object /// [IgnoreDataMember] - public IContentType ContentType - { - get { return _contentType; } - } + public IContentType ContentType => _contentType; /// /// Changes the for the current content object @@ -276,9 +271,9 @@ namespace Umbraco.Core.Models [DataMember] public bool IsBlueprint { get; internal set; } - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberDirty) { - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + base.ResetDirtyProperties(rememberDirty); // take care of the published state switch (PublishedState) @@ -327,10 +322,7 @@ namespace Umbraco.Core.Models clone.ResetIdentity(); foreach (var property in clone.Properties) - { property.ResetIdentity(); - property.Version = clone.Version; - } clone.PublishedVersionGuid = Guid.Empty; @@ -339,7 +331,7 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = (Content)base.DeepClone(); + var clone = (Content) base.DeepClone(); //turn off change tracking clone.DisableChangeTracking(); //need to manually clone this since it's not settable diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 522c6cd29f..531c358e22 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -20,74 +20,74 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] public abstract class ContentBase : Entity, IContentBase { + private static readonly Lazy Ps = new Lazy(); + + private int _contentTypeId; protected IContentTypeComposition ContentTypeBase; private Lazy _parentId; - private string _name;//NOTE Once localization is introduced this will be the localized Name of the Content/Media. - private int _sortOrder; private int _level; private string _path; - private int _creatorId; + private int _sortOrder; + private bool _trashed; - private int _contentTypeId; + private int _creatorId; + + // fixme need to deal with localized names, how? + // for the time being, this is the node text = unique + private string _name; + private PropertyCollection _properties; - private readonly List _lastInvalidProperties = new List(); + private readonly List _invalidProperties = new List(); + + [EditorBrowsable(EditorBrowsableState.Never)] + IDictionary IUmbracoEntity.AdditionalData => _lazyAdditionalData.Value; + private readonly Lazy> _lazyAdditionalData = new Lazy>(); /// - /// Protected constructor for ContentBase (Base for Content and Media) + /// Initializes a new instance of the class. /// - /// Localized Name of the entity - /// - /// - /// protected ContentBase(string name, int parentId, IContentTypeComposition contentType, PropertyCollection properties) + : this(name, contentType, properties) { if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); - - ContentTypeBase = contentType ?? throw new ArgumentNullException(nameof(contentType)); - Version = Guid.NewGuid(); - _parentId = new Lazy(() => parentId); - _name = name; - _contentTypeId = contentType.Id; - _properties = properties ?? throw new ArgumentNullException(nameof(properties)); - _properties.EnsurePropertyTypes(PropertyTypes); - _additionalData = new Dictionary(); } /// - /// Protected constructor for ContentBase (Base for Content and Media) + /// Initializes a new instance of the class. /// - /// Localized Name of the entity - /// - /// - /// protected ContentBase(string name, IContentBase parent, IContentTypeComposition contentType, PropertyCollection properties) + : this(name, contentType, properties) { if (parent == null) throw new ArgumentNullException(nameof(parent)); - - ContentTypeBase = contentType ?? throw new ArgumentNullException(nameof(contentType)); - Version = Guid.NewGuid(); - _parentId = new Lazy(() => parent.Id); + } + + private ContentBase(string name, IContentTypeComposition contentType, PropertyCollection properties) + { + ContentTypeBase = contentType ?? throw new ArgumentNullException(nameof(contentType)); + + // initially, all new instances have + Id = 0; // no identity + Version = Guid.NewGuid(); // a new unique version id + _name = name; _contentTypeId = contentType.Id; _properties = properties ?? throw new ArgumentNullException(nameof(properties)); _properties.EnsurePropertyTypes(PropertyTypes); - _additionalData = new Dictionary(); } - private static readonly Lazy Ps = new Lazy(); - + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); } @@ -98,7 +98,7 @@ namespace Umbraco.Core.Models } /// - /// Gets or sets the Id of the Parent entity + /// Gets or sets the identifier of the parent entity. /// [DataMember] public virtual int ParentId @@ -120,79 +120,78 @@ namespace Umbraco.Core.Models } /// - /// Sets the ParentId from the lazy integer id + /// Sets the identifier of the parent entity. /// /// Id of the Parent - internal protected void SetLazyParentId(Lazy parentId) + protected internal void SetLazyParentId(Lazy parentId) { _parentId = parentId; OnPropertyChanged(Ps.Value.ParentIdSelector); } /// - /// Gets or sets the name of the entity - /// - [DataMember] - public virtual string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - public virtual int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - /// - /// Gets or sets the level of the content entity + /// Gets or sets the level of the entity. /// [DataMember] public virtual int Level { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } + get => _level; + set => SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } /// - /// Gets or sets the path + /// Gets or sets the path of the entity. /// [DataMember] public virtual string Path //Setting this value should be handled by the class not the user { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } + get => _path; + set => SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } /// - /// Profile of the user who created this Content + /// Gets or sets the sort order of the entity. + /// + [DataMember] + public virtual int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); + } + + /// + /// Gets or sets a value indicating whether the entity is trashed. + /// + /// A trashed entity is unpublished and in the recycle bin. + [DataMember] + public virtual bool Trashed // fixme setting this value should be handled by the class not the user + { + get => _trashed; + internal set => SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); + } + + /// + /// Gets or sets the name of the entity. + /// + [DataMember] + public virtual string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + } + + /// + /// Gets or sets the identifier of the user who created the entity. /// [DataMember] public virtual int CreatorId { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } + get => _creatorId; + set => SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } /// - /// Boolean indicating whether this Content is Trashed or not. - /// If Content is Trashed it will be located in the Recyclebin. - /// - /// When content is trashed it should be unpublished - [DataMember] - public virtual bool Trashed //Setting this value should be handled by the class not the user - { - get { return _trashed; } - internal set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } - } - - /// - /// Guid Id of the curent Version + /// Gets or sets the identifier of the version. /// [DataMember] public Guid Version { get; internal set; } @@ -217,12 +216,12 @@ namespace Umbraco.Core.Models } /// - /// Collection of properties, which make up all the data available for this Content object + /// Gets or sets the collection of properties for the entity. /// [DataMember] public virtual PropertyCollection Properties { - get { return _properties; } + get => _properties; set { _properties = value; @@ -230,166 +229,317 @@ namespace Umbraco.Core.Models } } - private readonly IDictionary _additionalData; - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - [EditorBrowsable(EditorBrowsableState.Never)] - IDictionary IUmbracoEntity.AdditionalData - { - get { return _additionalData; } - } - - /// - /// List of PropertyGroups available on this Content object + /// Gets the enumeration of property groups for the entity. + /// fixme is a proxy, kill this /// [IgnoreDataMember] - public IEnumerable PropertyGroups { get { return ContentTypeBase.CompositionPropertyGroups; } } + public IEnumerable PropertyGroups => ContentTypeBase.CompositionPropertyGroups; /// - /// List of PropertyTypes available on this Content object + /// Gets the numeration of property types for the entity. + /// fixme is a proxy, kill this /// [IgnoreDataMember] - public IEnumerable PropertyTypes { get { return ContentTypeBase.CompositionPropertyTypes; } } + public IEnumerable PropertyTypes => ContentTypeBase.CompositionPropertyTypes; + + #region Has, Get, Set Property Value /// - /// Indicates whether the content object has a property with the supplied alias + /// Gets a value indicating whether the content entity has a property with the supplied alias. + /// fixme with a value, or just the property? /// - /// Alias of the PropertyType - /// True if Property with given alias exists, otherwise False public virtual bool HasProperty(string propertyTypeAlias) + => Properties.Contains(propertyTypeAlias); + + /// + /// Gets the neutral value of a property. + /// + public virtual object GetValue(string propertyTypeAlias, bool published = false) { - return Properties.Contains(propertyTypeAlias); + return Properties.Contains(propertyTypeAlias) ? Properties[propertyTypeAlias].GetValue(published) : null; } /// - /// Gets the value of a Property + /// Gets the culture value of a property. /// - /// Alias of the PropertyType - /// Value as an - public virtual object GetValue(string propertyTypeAlias) + public virtual object GetValue(string propertyTypeAlias, int languageId, bool published = false) { - return Properties.Contains(propertyTypeAlias) ? Properties[propertyTypeAlias].Value : null; + return Properties.Contains(propertyTypeAlias) ? Properties[propertyTypeAlias].GetValue(languageId, published) : null; } /// - /// Gets the value of a Property + /// Gets the segment value of a property. /// - /// Type of the value to return - /// Alias of the PropertyType - /// Value as a - public virtual TPassType GetValue(string propertyTypeAlias) + public virtual object GetValue(string propertyTypeAlias, int languageId, string segment, bool published = false) + { + return Properties.Contains(propertyTypeAlias) ? Properties[propertyTypeAlias].GetValue(languageId, segment, published) : null; + } + + /// + /// Gets the typed neutral value of a property. + /// + public virtual TPropertyValue GetValue(string propertyTypeAlias, bool published = false) { if (Properties.Contains(propertyTypeAlias) == false) - { - return default(TPassType); - } + return default; - var convertAttempt = Properties[propertyTypeAlias].Value.TryConvertTo(); - return convertAttempt.Success ? convertAttempt.Result : default(TPassType); + var convertAttempt = Properties[propertyTypeAlias].GetValue(published).TryConvertTo(); + return convertAttempt.Success ? convertAttempt.Result : default; } /// - /// Sets the value of a Property + /// Gets the typed culture value of a property. + /// + public virtual TPropertyValue GetValue(string propertyTypeAlias, int languageId, bool published = false) + { + if (Properties.Contains(propertyTypeAlias) == false) + return default; + + var convertAttempt = Properties[propertyTypeAlias].GetValue(languageId, published).TryConvertTo(); + return convertAttempt.Success ? convertAttempt.Result : default; + } + + /// + /// Gets the typed segment value of a property. + /// + public virtual TPropertyValue GetValue(string propertyTypeAlias, int languageId, string segment, bool published = false) + { + if (Properties.Contains(propertyTypeAlias) == false) + return default; + + var convertAttempt = Properties[propertyTypeAlias].GetValue(languageId, segment, published).TryConvertTo(); + return convertAttempt.Success ? convertAttempt.Result : default; + } + + /// + /// Sets the neutral (draft) value of a property. /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetValue(string propertyTypeAlias, object value) { if (value == null) { - SetValueOnProperty(propertyTypeAlias, value); + SetValueOnProperty(propertyTypeAlias, null); return; } // .NET magic to call one of the 'SetPropertyValue' handlers with matching signature - ((dynamic)this).SetPropertyValue(propertyTypeAlias, (dynamic)value); + ((dynamic) this).SetPropertyValue(propertyTypeAlias, (dynamic) value); } /// - /// Sets the value of a Property + /// Sets the culture (draft) value of a property. + /// + public virtual void SetValue(string propertyTypeAlias, int languageId, object value) + { + if (value == null) + { + SetValueOnProperty(propertyTypeAlias, languageId, null); + return; + } + + // .NET magic to call one of the 'SetPropertyValue' handlers with matching signature + ((dynamic) this).SetPropertyValue(propertyTypeAlias, languageId, (dynamic) value); + } + + /// + /// Sets the segment (draft) value of a property. + /// + public virtual void SetValue(string propertyTypeAlias, int languageId, string segment, object value) + { + if (value == null) + { + SetValueOnProperty(propertyTypeAlias, languageId, segment, null); + return; + } + + // .NET magic to call one of the 'SetPropertyValue' handlers with matching signature + ((dynamic) this).SetPropertyValue(propertyTypeAlias, languageId, segment, (dynamic) value); + } + + /// + /// Sets the neutral (draft) string value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, string value) { SetValueOnProperty(propertyTypeAlias, value); } /// - /// Sets the value of a Property + /// Sets the culture (draft) string value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string value) + { + SetValueOnProperty(propertyTypeAlias, languageId, value); + } + + /// + /// Sets the segment (draft) string value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string segment, string value) + { + SetValueOnProperty(propertyTypeAlias, languageId, segment, value); + } + + /// + /// Sets the neutral (draft) int value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, int value) { SetValueOnProperty(propertyTypeAlias, value); } /// - /// Sets the value of a Property + /// Sets the culture (draft) int value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, int value) + { + SetValueOnProperty(propertyTypeAlias, languageId, value); + } + + /// + /// Sets the segment (draft) int value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string segment, int value) + { + SetValueOnProperty(propertyTypeAlias, languageId, segment, value); + } + + /// + /// Sets the neutral (draft) long value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, long value) { SetValueOnProperty(propertyTypeAlias, value); } /// - /// Sets the value of a Property + /// Sets the culture (draft) long value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, long value) + { + SetValueOnProperty(propertyTypeAlias, languageId, value); + } + + /// + /// Sets the segment (draft) long value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string segment, long value) + { + SetValueOnProperty(propertyTypeAlias, languageId, segment, value); + } + + /// + /// Sets the neutral (draft) decimal value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, decimal value) { SetValueOnProperty(propertyTypeAlias, value); } /// - /// Sets the value of a Property + /// Sets the culture (draft) decimal value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, decimal value) + { + SetValueOnProperty(propertyTypeAlias, languageId, value); + } + + /// + /// Sets the segment (draft) decimal value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string segment, decimal value) + { + SetValueOnProperty(propertyTypeAlias, languageId, segment, value); + } + + /// + /// Sets the neutral (draft) double value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, double value) { SetValueOnProperty(propertyTypeAlias, value); } /// - /// Sets the value of a Property + /// Sets the culture (draft) double value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, double value) + { + SetValueOnProperty(propertyTypeAlias, languageId, value); + } + + /// + /// Sets the segment (draft) double value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string segment, double value) + { + SetValueOnProperty(propertyTypeAlias, languageId, segment, value); + } + + /// + /// Sets the neutral (draft) boolean value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, bool value) { - int val = Convert.ToInt32(value); + var val = Convert.ToInt32(value); SetValueOnProperty(propertyTypeAlias, val); } /// - /// Sets the value of a Property + /// Sets the culture (draft) boolean value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, bool value) + { + var val = Convert.ToInt32(value); + SetValueOnProperty(propertyTypeAlias, languageId, val); + } + + /// + /// Sets the segment (draft) boolean value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string segment, bool value) + { + var val = Convert.ToInt32(value); + SetValueOnProperty(propertyTypeAlias, languageId, segment, val); + } + + /// + /// Sets the neutral (draft) DateTime value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, DateTime value) { SetValueOnProperty(propertyTypeAlias, value); } /// - /// Sets the value of a Property + /// Sets the culture (draft) DateTime value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, DateTime value) + { + SetValueOnProperty(propertyTypeAlias, languageId, value); + } + + /// + /// Sets the segment (draft) DateTime value of a Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, int languageId, string segment, DateTime value) + { + SetValueOnProperty(propertyTypeAlias, languageId, segment, value); + } + + // fixme - these three use an extension method that needs to be adapted too + + /// + /// Sets the posted file value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFile value) { ContentExtensions.SetValue(this, propertyTypeAlias, value); } /// - /// Sets the value of a Property + /// Sets the posted file base value of a Property /// /// Alias of the PropertyType /// Value to set for the Property @@ -399,10 +549,8 @@ namespace Umbraco.Core.Models } /// - /// Sets the value of a Property + /// Sets the posted file wrapper value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property [Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")] public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFileWrapper value) { @@ -410,143 +558,153 @@ namespace Umbraco.Core.Models } /// - /// Private method to set the value of a property + /// Sets the neutral (draft) value of a property. /// - /// - /// private void SetValueOnProperty(string propertyTypeAlias, object value) { if (Properties.Contains(propertyTypeAlias)) { - Properties[propertyTypeAlias].Value = value; + Properties[propertyTypeAlias].SetValue(value); return; } var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (propertyType == null) - { - throw new Exception(String.Format("No PropertyType exists with the supplied alias: {0}", propertyTypeAlias)); - } - Properties.Add(propertyType.CreatePropertyFromValue(value)); + throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); + + var property = propertyType.CreateProperty(); + property.SetValue(value); + Properties.Add(property); } /// - /// Boolean indicating whether the content and its properties are valid + /// Sets the culture (draft) value of a property. /// - /// True if content is valid otherwise false - public virtual bool IsValid() + private void SetValueOnProperty(string propertyTypeAlias, int languageId, object value) { - _lastInvalidProperties.Clear(); - _lastInvalidProperties.AddRange(Properties.Where(property => property.IsValid() == false)); - return _lastInvalidProperties.Any() == false; + if (Properties.Contains(propertyTypeAlias)) + { + Properties[propertyTypeAlias].SetValue(languageId, value); + return; + } + + var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) + throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); + + var property = propertyType.CreateProperty(); + property.SetValue(languageId, value); + Properties.Add(property); } /// - /// Returns a collection of the result of the last validation process, this collection contains all invalid properties. + /// Sets the segment (draft) value of a property. + /// + private void SetValueOnProperty(string propertyTypeAlias, int languageId, string segment, object value) + { + if (Properties.Contains(propertyTypeAlias)) + { + Properties[propertyTypeAlias].SetValue(languageId, segment, value); + return; + } + + var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) + throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); + + var property = propertyType.CreateProperty(); + property.SetValue(languageId, segment, value); + Properties.Add(property); + } + + #endregion + + #region Validation + + /// + /// Gets a value indicating whether the content and its properties are valid. + /// + public virtual bool Validate() // fixme would it depends on the property varyBy? or would we validate for a given culture/segment? + { + _invalidProperties.Clear(); + _invalidProperties.AddRange(Properties.Where(property => property.IsValid() == false)); + return _invalidProperties.Any() == false; + } + + /// + /// Gets the properties marked as invalid during the last validation. /// [IgnoreDataMember] - internal IEnumerable LastInvalidProperties - { - get { return _lastInvalidProperties; } - } + internal IEnumerable InvalidProperties => _invalidProperties; - #region Dirty property handling + #endregion + + #region Dirty /// - /// We will override this method to ensure that when we reset the dirty properties that we - /// also reset the dirty changes made to the content's Properties (user defined) + /// Resets dirty properties. /// - /// - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberDirty) { - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + base.ResetDirtyProperties(rememberDirty); + // also reset dirty changes made to user's properties foreach (var prop in Properties) - { - prop.ResetDirtyProperties(rememberPreviouslyChangedProperties); - } + prop.ResetDirtyProperties(rememberDirty); } /// - /// Indicates whether the current entity is dirty. + /// Gets a value indicating whether the current entity is dirty. /// - /// True if entity is dirty, otherwise False public override bool IsDirty() { return IsEntityDirty() || this.IsAnyUserPropertyDirty(); } /// - /// Indicates whether the current entity was dirty. + /// Gets a value indicating whether the current entity was dirty. /// - /// True if entity was dirty, otherwise False public override bool WasDirty() { return WasEntityDirty() || this.WasAnyUserPropertyDirty(); } /// - /// Returns true if only the entity properties are dirty + /// Gets a value indicating whether the current entity's own properties (not user) are dirty. /// - /// public bool IsEntityDirty() { return base.IsDirty(); } /// - /// Returns true if only the entity properties were dirty + /// Gets a value indicating whether the current entity's own properties (not user) were dirty. /// - /// public bool WasEntityDirty() { return base.WasDirty(); } /// - /// Indicates whether a specific property on the current entity is dirty. + /// Gets a value indicating whether a user property is dirty. /// - /// Name of the property to check - /// - /// True if any of the class properties are dirty or - /// True if any of the user defined PropertyType properties are dirty based on their alias, - /// otherwise False - /// public override bool IsPropertyDirty(string propertyName) { - bool existsInEntity = base.IsPropertyDirty(propertyName); - if (existsInEntity) + if (base.IsPropertyDirty(propertyName)) return true; - if (Properties.Contains(propertyName)) - { - return Properties[propertyName].IsDirty(); - } - - return false; + return Properties.Contains(propertyName) && Properties[propertyName].IsDirty(); } /// - /// Indicates whether a specific property on the current entity was changed and the changes were committed + /// Gets a value indicating whether a user property was dirty. /// - /// Name of the property to check - /// - /// True if any of the class properties are dirty or - /// True if any of the user defined PropertyType properties are dirty based on their alias, - /// otherwise False - /// public override bool WasPropertyDirty(string propertyName) { - bool existsInEntity = base.WasPropertyDirty(propertyName); - if (existsInEntity) + if (base.WasPropertyDirty(propertyName)) return true; - if (Properties.Contains(propertyName)) - { - return Properties[propertyName].WasDirty(); - } - - return false; + return Properties.Contains(propertyName) && Properties[propertyName].WasDirty(); } #endregion diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index ec57aaffc9..9dcabec098 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -380,10 +380,12 @@ namespace Umbraco.Core.Models entity.Name = entity.Name.ToValidXmlString(); foreach (var property in entity.Properties) { - if (property.Value is string) + foreach (var propertyValue in property.Values) { - var value = (string) property.Value; - property.Value = value.ToValidXmlString(); + if (propertyValue.DraftValue is string draftString) + propertyValue.DraftValue = draftString.ToValidXmlString(); + if (propertyValue.PublishedValue is string publishedString) + propertyValue.PublishedValue = publishedString.ToValidXmlString(); } } } @@ -463,7 +465,7 @@ namespace Umbraco.Core.Models /// /// Set property values by alias with an annonymous object /// - public static void PropertyValues(this IContentBase content, object value) + public static void PropertyValues(this IContentBase content, object value) // fixme kill that one! won't work with variants { if (value == null) throw new Exception("No properties has been passed in"); @@ -474,23 +476,21 @@ namespace Umbraco.Core.Models //Check if a PropertyType with alias exists thus being a valid property var propertyType = content.PropertyTypes.FirstOrDefault(x => x.Alias == propertyInfo.Name); if (propertyType == null) - throw new Exception( - string.Format( - "The property alias {0} is not valid, because no PropertyType with this alias exists", - propertyInfo.Name)); + throw new Exception($"The property alias {propertyInfo.Name} is not valid, because no PropertyType with this alias exists"); //Check if a Property with the alias already exists in the collection thus being updated or inserted var item = content.Properties.FirstOrDefault(x => x.Alias == propertyInfo.Name); if (item != null) { - item.Value = propertyInfo.GetValue(value, null); + item.SetValue(propertyInfo.GetValue(value, null)); //Update item with newly added value content.Properties.Add(item); } else { //Create new Property to add to collection - var property = propertyType.CreatePropertyFromValue(propertyInfo.GetValue(value, null)); + var property = propertyType.CreateProperty(); + property.SetValue(propertyInfo.GetValue(value, null)); content.Properties.Add(property); } } @@ -710,9 +710,10 @@ namespace Umbraco.Core.Models property.SetTags(storageType, propertyTypeAlias, tags, replaceTags, tagGroup); } + // fixme - totally not ok with variants internal static void SetTags(this Property property, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable tags, bool replaceTags, string tagGroup = "default") { - if (property == null) throw new ArgumentNullException("property"); + if (property == null) throw new ArgumentNullException(nameof(property)); var trimmedTags = tags.Select(x => x.Trim()).ToArray(); @@ -726,11 +727,11 @@ namespace Umbraco.Core.Models switch (storageType) { case TagCacheStorageType.Csv: - property.Value = string.Join(",", trimmedTags); + property.SetValue(string.Join(",", trimmedTags)); break; case TagCacheStorageType.Json: //json array - property.Value = JsonConvert.SerializeObject(trimmedTags); + property.SetValue(JsonConvert.SerializeObject(trimmedTags)); break; } @@ -740,19 +741,19 @@ namespace Umbraco.Core.Models switch (storageType) { case TagCacheStorageType.Csv: - var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var currTags = property.GetValue().ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()); - property.Value = string.Join(",", trimmedTags.Union(currTags)); + property.SetValue(string.Join(",", trimmedTags.Union(currTags))); break; case TagCacheStorageType.Json: - var currJson = JsonConvert.DeserializeObject(property.Value.ToString()); + var currJson = JsonConvert.DeserializeObject(property.GetValue().ToString()); //need to append the new ones foreach (var tag in trimmedTags) { currJson.Add(tag); } //json array - property.Value = JsonConvert.SerializeObject(currJson); + property.SetValue(JsonConvert.SerializeObject(currJson)); break; } } @@ -765,6 +766,7 @@ namespace Umbraco.Core.Models /// /// /// The group/category that the tags are currently assigned to, the default value is "default" + // fixme - totally not ok with variants public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable tags, string tagGroup = "default") { var property = content.Properties[propertyTypeAlias]; @@ -780,10 +782,10 @@ namespace Umbraco.Core.Models property.TagSupport.Tags = trimmedTags.Select(x => new Tuple(x, tagGroup)); //set the property value - var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var currTags = property.GetValue().ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()); - property.Value = string.Join(",", currTags.Except(trimmedTags)); + property.SetValue(string.Join(",", currTags.Except(trimmedTags))); } #endregion diff --git a/src/Umbraco.Core/Models/EntityBase/ICanBeDirty.cs b/src/Umbraco.Core/Models/EntityBase/ICanBeDirty.cs index 9c6d062f55..536cf11bfc 100644 --- a/src/Umbraco.Core/Models/EntityBase/ICanBeDirty.cs +++ b/src/Umbraco.Core/Models/EntityBase/ICanBeDirty.cs @@ -1,12 +1,30 @@ -namespace Umbraco.Core.Models.EntityBase +using System.Collections.Generic; + +namespace Umbraco.Core.Models.EntityBase { /// - /// An interface that defines the object is tracking property changes and if it is Dirty + /// Defines an entity that tracks property changes and can be dirty. /// public interface ICanBeDirty { + /// + /// Gets a value indicating whether the current entity is dirty. + /// bool IsDirty(); + + /// + /// Gets a value indicating whether a specific property is dirty. + /// bool IsPropertyDirty(string propName); + + /// + /// Gets properties that are dirty. + /// + IEnumerable GetDirtyProperties(); + + /// + /// Resets dirty properties. + /// void ResetDirtyProperties(); } } diff --git a/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs b/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs index 64a2ded4e3..db7f27357b 100644 --- a/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs +++ b/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs @@ -1,14 +1,33 @@ namespace Umbraco.Core.Models.EntityBase { /// - /// An interface that defines if the object is tracking property changes and that is is also - /// remembering what property changes had been made after the changes were committed. + /// Defines an entity that tracks property changes and can be dirty, and remembers + /// which properties were dirty when the changes were committed. /// public interface IRememberBeingDirty : ICanBeDirty { + /// + /// Gets a value indicating whether the current entity is dirty. + /// + /// A property was dirty if it had been changed and the changes were committed. bool WasDirty(); + + /// + /// Gets a value indicating whether a specific property was dirty. + /// + /// A property was dirty if it had been changed and the changes were committed. bool WasPropertyDirty(string propertyName); - void ForgetPreviouslyDirtyProperties(); - void ResetDirtyProperties(bool rememberPreviouslyChangedProperties); + + /// + /// Resets properties that were dirty. + /// + void ResetWereDirtyProperties(); + + /// + /// Resets dirty properties. + /// + /// A value indicating whether to remember dirty properties. + /// When is true, dirty properties are saved so they can be checked with WasDirty. + void ResetDirtyProperties(bool rememberDirty); } } diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index 8e0befbde9..3b69c64d04 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -16,210 +16,191 @@ namespace Umbraco.Core.Models.EntityBase [DataContract(IsReference = true)] public abstract class TracksChangesEntityBase : IRememberBeingDirty { - //TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662 + private bool _changeTrackingEnabled = true; // should we track changes? + private IDictionary _propertyChangedInfo; // which properties have changed? + private IDictionary _lastPropertyChangedInfo; // which properties had changed at last commit? + + /// + /// Gets properties that are dirty. + /// public virtual IEnumerable GetDirtyProperties() { - return _propertyChangedInfo == null - ? Enumerable.Empty() - : _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); + if (_propertyChangedInfo == null) + return Enumerable.Empty(); + + return _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); } - private bool _changeTrackingEnabled = true; - /// - /// Tracks the properties that have changed - /// - private IDictionary _propertyChangedInfo; - - /// - /// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties) - /// - private IDictionary _lastPropertyChangedInfo; - - /// - /// Property changed event + /// Occurs when a property changes. /// public event PropertyChangedEventHandler PropertyChanged; /// - /// Method to call on a property setter. + /// Registers that a property has changed. /// - /// The property info. protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) { - //return if we're not tracking changes - if (_changeTrackingEnabled == false) return; + if (_changeTrackingEnabled == false) + return; if (_propertyChangedInfo == null) _propertyChangedInfo = new Dictionary(); _propertyChangedInfo[propertyInfo.Name] = true; - if (PropertyChanged != null) - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); } /// - /// Indicates whether a specific property on the current entity is dirty. + /// Gets a value indicating whether a specific property is dirty. /// - /// Name of the property to check - /// True if Property is dirty, otherwise False public virtual bool IsPropertyDirty(string propertyName) { return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName); } /// - /// Indicates whether the current entity is dirty. + /// Gets a value indicating whether a specific property was dirty. /// - /// True if entity is dirty, otherwise False - public virtual bool IsDirty() - { - return _propertyChangedInfo != null && _propertyChangedInfo.Any(); - } - - /// - /// Indicates that the entity had been changed and the changes were committed - /// - /// - public virtual bool WasDirty() - { - return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(); - } - - /// - /// Indicates whether a specific property on the current entity was changed and the changes were committed - /// - /// Name of the property to check - /// True if Property was changed, otherwise False. Returns false if the entity had not been previously changed. + /// A property was dirty if it had been changed and the changes were committed. public virtual bool WasPropertyDirty(string propertyName) { return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName); } /// - /// Resets the remembered dirty properties from before the last commit + /// Gets a value indicating whether the current entity is dirty. /// - public void ForgetPreviouslyDirtyProperties() + public virtual bool IsDirty() { - //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME - // instance as the one on the clone, so we need to create a new instance. - _lastPropertyChangedInfo = null; + return _propertyChangedInfo != null && _propertyChangedInfo.Any(); } /// - /// Resets dirty properties by clearing the dictionary used to track changes. + /// Gets a value indicating whether the current entity is dirty. /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// + /// A property was dirty if it had been changed and the changes were committed. + public virtual bool WasDirty() + { + return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(); + } + + /// + /// Resets dirty properties. + /// + /// Saves dirty properties so they can be checked with WasDirty. public virtual void ResetDirtyProperties() { ResetDirtyProperties(true); } /// - /// Resets dirty properties by clearing the dictionary used to track changes. + /// Resets dirty properties. /// - /// - /// true if we are to remember the last changes made after resetting - /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// - public virtual void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + /// A value indicating whether to remember dirty properties. + /// When is true, dirty properties are saved so they can be checked with WasDirty. + public virtual void ResetDirtyProperties(bool rememberDirty) { - if (rememberPreviouslyChangedProperties) + if (rememberDirty && _propertyChangedInfo != null) { - //copy the changed properties to the last changed properties - if (_propertyChangedInfo != null) - { - _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); - } + _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); } - //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME + // note: cannot .Clear() because when memberwise-clone this will be the SAME // instance as the one on the clone, so we need to create a new instance. _propertyChangedInfo = null; } + /// + /// Resets properties that were dirty. + /// + public 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. + _lastPropertyChangedInfo = null; + } + + /// + /// Resets all change tracking infos. + /// public void ResetChangeTrackingCollections() { _propertyChangedInfo = null; _lastPropertyChangedInfo = null; } + /// + /// Disables change tracking. + /// public void DisableChangeTracking() { _changeTrackingEnabled = false; } + /// + /// Enables change tracking. + /// public void EnableChangeTracking() { _changeTrackingEnabled = true; } /// - /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did - /// it will ensure that the property has a dirty flag set. + /// Sets a property value, detects changes and manages the dirty flag. /// - /// - /// - /// - /// returns true if the value changed - /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set - /// to the same value, so it's really not dirty. - /// - internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector) + /// The type of the value. + /// The new value. + /// A reference to the value to set. + /// The property selector. + internal void SetPropertyValueAndDetectChanges(T value, ref T valueRef, PropertyInfo propertySelector) { if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom(typeof(T))) { throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required"); } - SetPropertyValueAndDetectChanges(newVal, ref origVal, propertySelector, EqualityComparer.Default); + SetPropertyValueAndDetectChanges(value, ref valueRef, propertySelector, EqualityComparer.Default); } /// - /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did - /// it will ensure that the property has a dirty flag set. + /// Sets a property value, detects changes and manages the dirty flag. /// - /// - /// - /// - /// The equality comparer to use - /// returns true if the value changed - /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set - /// to the same value, so it's really not dirty. - /// - internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer comparer) + /// The type of the value. + /// The new value. + /// A reference to the value to set. + /// The property selector. + /// A comparer to compare property values. + internal void SetPropertyValueAndDetectChanges(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer comparer) + { + var changed = _changeTrackingEnabled && comparer.Equals(valueRef, value) == false; + + valueRef = value; + + if (changed) + OnPropertyChanged(propertySelector); + } + + /// + /// Detects changes and manages the dirty flag. + /// + /// The type of the value. + /// The new value. + /// The original value. + /// The property selector. + /// A comparer to compare property values. + /// A value indicating whether we know values have changed and no comparison is required. + internal void DetectChanges(T value, T orig, PropertyInfo propertySelector, IEqualityComparer comparer, bool changed) { - //don't track changes, just set the value if (_changeTrackingEnabled == false) - { - //set the original value - origVal = newVal; - } - else - { - //check changed - var changed = comparer.Equals(origVal, newVal) == false; + return; - //set the original value - origVal = newVal; + if (!changed) + changed = comparer.Equals(orig, value) == false; - //raise the event if it was changed - if (changed) - { - OnPropertyChanged(propertySelector); - } - } + if (changed) + OnPropertyChanged(propertySelector); } } } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 1446606d19..d60764a8f5 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -46,31 +46,54 @@ namespace Umbraco.Core.Models bool HasProperty(string propertyTypeAlias); /// - /// Gets the value of a Property + /// Gets the neutral value of a Property /// - /// Alias of the PropertyType - /// Value as an - object GetValue(string propertyTypeAlias); + object GetValue(string propertyTypeAlias, bool published = false); /// - /// Gets the value of a Property + /// Gets the culture value of a Property /// - /// Type of the value to return - /// Alias of the PropertyType - /// Value as a - TPassType GetValue(string propertyTypeAlias); + object GetValue(string propertyTypeAlias, int languageId, bool published = false); /// - /// Sets the value of a Property + /// Gets the segment value of a Property + /// + object GetValue(string propertyTypeAlias, int languageId, string segment, bool published = false); + + /// + /// Gets the typed neutral value of a Property + /// + TPropertyValue GetValue(string propertyTypeAlias, bool published = false); + + /// + /// Gets the typed neutral value of a Property + /// + TPropertyValue GetValue(string propertyTypeAlias, int languageId, bool published = false); + + /// + /// Gets the typed neutral value of a Property + /// + TPropertyValue GetValue(string propertyTypeAlias, int languageId, string segment, bool published = false); + + /// + /// Sets the neutral (draft) value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property void SetValue(string propertyTypeAlias, object value); + /// + /// Sets the culture (draft) value of a Property + /// + void SetValue(string propertyTypeAlias, int languageId, object value); + + /// + /// Sets the segment (draft) value of a Property + /// + void SetValue(string propertyTypeAlias, int languageId, string segment, object value); + /// /// Boolean indicating whether the content and its properties are valid /// /// True if content is valid otherwise false - bool IsValid(); + bool Validate(); } } diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 7ecb500a30..cfabcb2d64 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -76,17 +76,14 @@ namespace Umbraco.Core.Models.Identity /// /// Returns true if an Id has been set on this object this will be false if the object is new and not peristed to the database /// - public bool HasIdentity - { - get { return _hasIdentity; } - } + public bool HasIdentity => _hasIdentity; public int[] CalculatedMediaStartNodeIds { get; internal set; } public int[] CalculatedContentStartNodeIds { get; internal set; } public override int Id { - get { return _id; } + get => _id; set { _id = value; @@ -99,8 +96,8 @@ namespace Umbraco.Core.Models.Identity /// public override string Email { - get { return _email; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } + get => _email; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } /// @@ -108,8 +105,8 @@ namespace Umbraco.Core.Models.Identity /// public override string UserName { - get { return _userName; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _userName, Ps.Value.UserNameSelector); } + get => _userName; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _userName, Ps.Value.UserNameSelector); } /// @@ -117,8 +114,8 @@ namespace Umbraco.Core.Models.Identity /// public override DateTime? LastLoginDateUtc { - get { return _lastLoginDateUtc; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, Ps.Value.LastLoginDateUtcSelector); } + get => _lastLoginDateUtc; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, Ps.Value.LastLoginDateUtcSelector); } /// @@ -126,8 +123,8 @@ namespace Umbraco.Core.Models.Identity /// public override bool EmailConfirmed { - get { return _emailConfirmed; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, Ps.Value.EmailConfirmedSelector); } + get => _emailConfirmed; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, Ps.Value.EmailConfirmedSelector); } /// @@ -135,8 +132,8 @@ namespace Umbraco.Core.Models.Identity /// public string Name { - get { return _name; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } /// @@ -144,8 +141,8 @@ namespace Umbraco.Core.Models.Identity /// public override int AccessFailedCount { - get { return _accessFailedCount; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, Ps.Value.AccessFailedCountSelector); } + get => _accessFailedCount; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, Ps.Value.AccessFailedCountSelector); } /// @@ -153,8 +150,8 @@ namespace Umbraco.Core.Models.Identity /// public override string PasswordHash { - get { return _passwordHash; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _passwordHash, Ps.Value.PasswordHashSelector); } + get => _passwordHash; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _passwordHash, Ps.Value.PasswordHashSelector); } @@ -163,7 +160,7 @@ namespace Umbraco.Core.Models.Identity /// public int[] StartContentIds { - get { return _startContentIds; } + get => _startContentIds; set { if (value == null) value = new int[0]; @@ -176,7 +173,7 @@ namespace Umbraco.Core.Models.Identity /// public int[] StartMediaIds { - get { return _startMediaIds; } + get => _startMediaIds; set { if (value == null) value = new int[0]; @@ -194,13 +191,13 @@ namespace Umbraco.Core.Models.Identity public string Culture { - get { return _culture; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); } + get => _culture; + set => _tracker.SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); } public IReadOnlyUserGroup[] Groups { - get { return _groups; } + get => _groups; set { //so they recalculate @@ -242,7 +239,7 @@ namespace Umbraco.Core.Models.Identity { get { - var isLocked = (LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now); + var isLocked = LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now; return isLocked; } } @@ -296,7 +293,7 @@ namespace Umbraco.Core.Models.Identity { Roles.Add(new IdentityUserRole { - UserId = this.Id.ToString(), + UserId = Id.ToString(), RoleId = role }); } @@ -304,10 +301,7 @@ namespace Umbraco.Core.Models.Identity /// /// Override Roles because the value of these are the user's group aliases /// - public override ICollection> Roles - { - get { return _roles; } - } + public override ICollection> Roles => _roles; /// /// Used to set a lazy call back to populate the user's Login list @@ -315,8 +309,7 @@ namespace Umbraco.Core.Models.Identity /// public void SetLoginsCallback(Lazy> callback) { - if (callback == null) throw new ArgumentNullException("callback"); - _getLogins = callback; + _getLogins = callback ?? throw new ArgumentNullException("callback"); } #region Change tracking @@ -368,14 +361,19 @@ namespace Umbraco.Core.Models.Identity return _tracker.WasPropertyDirty(propertyName); } - void IRememberBeingDirty.ForgetPreviouslyDirtyProperties() + IEnumerable ICanBeDirty.GetDirtyProperties() { - _tracker.ForgetPreviouslyDirtyProperties(); + return _tracker.GetDirtyProperties(); } - public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + void IRememberBeingDirty.ResetWereDirtyProperties() { - _tracker.ResetDirtyProperties(rememberPreviouslyChangedProperties); + _tracker.ResetWereDirtyProperties(); + } + + public void ResetDirtyProperties(bool rememberDirty) + { + _tracker.ResetDirtyProperties(rememberDirty); } private static readonly Lazy Ps = new Lazy(); @@ -438,7 +436,5 @@ namespace Umbraco.Core.Models.Identity } } #endregion - - } } diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index fdeebf3424..c004e040cc 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -174,14 +174,14 @@ namespace Umbraco.Core.Models OnPropertyChanged(Ps.Value.PropertiesSelector); } - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberDirty) { _addedProperties.Clear(); _removedProperties.Clear(); - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + base.ResetDirtyProperties(rememberDirty); foreach (var prop in Properties) { - ((TracksChangesEntityBase)prop).ResetDirtyProperties(rememberPreviouslyChangedProperties); + ((TracksChangesEntityBase)prop).ResetDirtyProperties(rememberDirty); } } diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index 6509926877..72150c8955 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Models var val = media.Properties[propertyType]; if (val == null) return string.Empty; - var jsonString = val.Value as string; + var jsonString = val.GetValue() as string; if (jsonString == null) return string.Empty; if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias) diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index c25e139b33..e621abdf61 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -193,9 +193,9 @@ namespace Umbraco.Core.Models var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordQuestion, "PasswordQuestion", default(string)); if (a.Success == false) return a.Result; - return Properties[Constants.Conventions.Member.PasswordQuestion].Value == null + return Properties[Constants.Conventions.Member.PasswordQuestion].GetValue() == null ? string.Empty - : Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); + : Properties[Constants.Conventions.Member.PasswordQuestion].GetValue().ToString(); } set { @@ -203,7 +203,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.PasswordQuestion, "PasswordQuestion") == false) return; - Properties[Constants.Conventions.Member.PasswordQuestion].Value = value; + Properties[Constants.Conventions.Member.PasswordQuestion].SetValue(value); } } @@ -224,9 +224,9 @@ namespace Umbraco.Core.Models var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordAnswer, "PasswordAnswer", default(string)); if (a.Success == false) return a.Result; - return Properties[Constants.Conventions.Member.PasswordAnswer].Value == null + return Properties[Constants.Conventions.Member.PasswordAnswer].GetValue() == null ? string.Empty - : Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); + : Properties[Constants.Conventions.Member.PasswordAnswer].GetValue().ToString(); } set { @@ -234,7 +234,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.PasswordAnswer, "PasswordAnswer") == false) return; - Properties[Constants.Conventions.Member.PasswordAnswer].Value = value; + Properties[Constants.Conventions.Member.PasswordAnswer].SetValue(value); } } @@ -253,9 +253,9 @@ namespace Umbraco.Core.Models var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, "Comments", default(string)); if (a.Success == false) return a.Result; - return Properties[Constants.Conventions.Member.Comments].Value == null + return Properties[Constants.Conventions.Member.Comments].GetValue() == null ? string.Empty - : Properties[Constants.Conventions.Member.Comments].Value.ToString(); + : Properties[Constants.Conventions.Member.Comments].GetValue().ToString(); } set { @@ -263,7 +263,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.Comments, "Comments") == false) return; - Properties[Constants.Conventions.Member.Comments].Value = value; + Properties[Constants.Conventions.Member.Comments].SetValue(value); } } @@ -283,8 +283,8 @@ namespace Umbraco.Core.Models //This is the default value if the prop is not found true); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsApproved].Value == null) return true; - var tryConvert = Properties[Constants.Conventions.Member.IsApproved].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.IsApproved].GetValue() == null) return true; + var tryConvert = Properties[Constants.Conventions.Member.IsApproved].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -298,7 +298,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.IsApproved, "IsApproved") == false) return; - Properties[Constants.Conventions.Member.IsApproved].Value = value; + Properties[Constants.Conventions.Member.IsApproved].SetValue(value); } } @@ -316,8 +316,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, "IsLockedOut", false); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsLockedOut].Value == null) return false; - var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) return false; + var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -331,7 +331,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.IsLockedOut, "IsLockedOut") == false) return; - Properties[Constants.Conventions.Member.IsLockedOut].Value = value; + Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value); } } @@ -349,8 +349,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, "LastLoginDate", default(DateTime)); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLoginDate].Value == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -364,7 +364,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.LastLoginDate, "LastLoginDate") == false) return; - Properties[Constants.Conventions.Member.LastLoginDate].Value = value; + Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value); } } @@ -382,8 +382,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate", default(DateTime)); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -397,7 +397,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate") == false) return; - Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value = value; + Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value); } } @@ -415,8 +415,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate", default(DateTime)); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLockoutDate].Value == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -430,7 +430,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate") == false) return; - Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; + Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value); } } @@ -449,8 +449,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts", 0); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value == null) return default(int); - var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) return default(int); + var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -464,7 +464,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts") == false) return; - Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value = value; + Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value); } } diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 7110d428a2..195aff88bd 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -8,16 +9,21 @@ using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { /// - /// A Property contains a single piece of data + /// Represents a property. /// [Serializable] [DataContract(IsReference = true)] public class Property : Entity { private PropertyType _propertyType; - private Guid _version; - private object _value; - private readonly PropertyTags _tagSupport = new PropertyTags(); + private readonly PropertyTags _tagSupport = new PropertyTags(); // fixme allocating even if no support? + + private List _values = new List(); + private PropertyValue _pvalue; + private Dictionary _lvalues; + private Dictionary> _svalues; + + private static readonly Lazy Ps = new Lazy(); protected Property() { } @@ -27,26 +33,24 @@ namespace Umbraco.Core.Models _propertyType = propertyType; } - public Property(PropertyType propertyType, object value) - { - _propertyType = propertyType; - Value = value; - } - - public Property(int id, Guid version, PropertyType propertyType, object value) + public Property(int id, PropertyType propertyType) { Id = id; _propertyType = propertyType; - _version = version; - Value = value; } - private static readonly Lazy Ps = new Lazy(); + public class PropertyValue + { + public int? LanguageId { get; set; } + public string Segment { get; set; } + public object PublishedValue { get; set; } + public object DraftValue { get; set; } + } + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { - public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + public readonly PropertyInfo ValuesSelector = ExpressionHelper.GetPropertyInfo(x => x.Values); public readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( (o, o1) => @@ -78,25 +82,48 @@ namespace Umbraco.Core.Models }, o => o.GetHashCode()); } + /// + /// Returns the PropertyType, which this Property is based on + /// + [IgnoreDataMember] + public PropertyType PropertyType => _propertyType; + + /// + /// Gets the list of values. + /// + [DataMember] + public List Values + { + get => _values; + set + { + _values = value; + + _lvalues = value.Where(x => x.LanguageId.HasValue && x.Segment == null) + .ToDictionary(x => x.LanguageId.Value, x => x); + + _svalues = value.Where(x => x.LanguageId.HasValue && x.Segment != null) + .GroupBy(x => x.LanguageId.Value) + .ToDictionary(x => x.Key, x => x.ToDictionary(y => y.Segment, y => y)); + } + } + /// /// Returns the instance of the tag support, by default tags are not enabled /// - internal PropertyTags TagSupport - { - get { return _tagSupport; } - } + internal PropertyTags TagSupport => _tagSupport; /// /// Returns the Alias of the PropertyType, which this Property is based on /// [DataMember] - public string Alias { get { return _propertyType.Alias; } } + public string Alias => _propertyType.Alias; /// /// Returns the Id of the PropertyType, which this Property is based on /// [IgnoreDataMember] - internal int PropertyTypeId { get { return _propertyType.Id; } } + internal int PropertyTypeId => _propertyType.Id; /// /// Returns the DatabaseType that the underlaying DataType is using to store its values @@ -105,114 +132,202 @@ namespace Umbraco.Core.Models /// Only used internally when saving the property value. /// [IgnoreDataMember] - internal DataTypeDatabaseType DataTypeDatabaseType + internal DataTypeDatabaseType DataTypeDatabaseType => _propertyType.DataTypeDatabaseType; + + /// + /// Gets the neutral value. + /// + public object GetValue(bool published = false) { - get { return _propertyType.DataTypeDatabaseType; } + if (_pvalue == null) return null; + return published ? _pvalue.PublishedValue : _pvalue.DraftValue; } /// - /// Returns the PropertyType, which this Property is based on + /// Gets the culture value. /// - [IgnoreDataMember] - public PropertyType PropertyType { get { return _propertyType; } } + public object GetValue(int languageId, bool published = false) + { + if (_lvalues == null) return null; + if (!_lvalues.TryGetValue(languageId, out var value)) return null; + return published ? value.PublishedValue : value.DraftValue; + } /// - /// Gets or Sets the version id for the Property + /// Gets the segment value. /// - /// - /// The version will be the same for all Property objects in a collection on a Content - /// object, so not sure how much this makes sense but adding it to align with: - /// umbraco.interfaces.IProperty - /// - [DataMember] - public Guid Version + public object GetValue(int languageId, string segment, bool published = false) { - get { return _version; } - set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } + if (_svalues == null) return null; + if (!_svalues.TryGetValue(languageId, out var svalues)) return null; + if (!svalues.TryGetValue(segment, out var value)) return null; + return published ? value.PublishedValue : value.DraftValue; + } + + /// + /// Sets a (draft) neutral value. + /// + public void SetValue(object value) + { + var change = false; + if (_pvalue == null) + { + _pvalue = new PropertyValue(); + _values.Add(_pvalue); + change = true; + } + var origValue = _pvalue.DraftValue; + _pvalue.DraftValue = ConvertSetValue(value); + + DetectChanges(_pvalue.DraftValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, change); + } + + /// + /// Sets a (draft) culture value. + /// + public void SetValue(int? nLanguageId, object value) + { + if (nLanguageId == null) + { + SetValue(value); + return; + } + + var languageId = nLanguageId.Value; + + var change = false; + if (_lvalues == null) + { + _lvalues = new Dictionary(); + change = true; + } + if (!_lvalues.TryGetValue(languageId, out var pvalue)) + { + pvalue = _lvalues[languageId] = new PropertyValue(); + _values.Add(pvalue); + change = true; + } + var origValue = pvalue.DraftValue; + pvalue.DraftValue = ConvertSetValue(value); + + DetectChanges(pvalue.DraftValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, change); + } + + /// + /// Sets a (draft) segment value. + /// + public void SetValue(int? nLanguageId, string segment, object value) + { + if (segment == null) + { + SetValue(nLanguageId, value); + return; + } + + if (!nLanguageId.HasValue) + throw new ArgumentException("Cannot be null when segment is not null.", nameof(nLanguageId)); + + var languageId = nLanguageId.Value; + + var change = false; + if (_svalues == null) + { + _svalues = new Dictionary>(); + change = true; + } + if (!_svalues.TryGetValue(languageId, out var svalue)) + { + svalue = _svalues[languageId] = new Dictionary(); + change = true; + } + if (!svalue.TryGetValue(segment, out var pvalue)) + { + pvalue = svalue[segment] = new PropertyValue(); + _values.Add(pvalue); + change = true; + } + var origValue = pvalue.DraftValue; + pvalue.DraftValue = ConvertSetValue(value); + + DetectChanges(pvalue.DraftValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, change); + } + + private object ConvertSetValue(object value) + { + var isOfExpectedType = _propertyType.IsPropertyTypeValid(value); + + if (isOfExpectedType) + return value; + + // isOfExpectedType is true if value is null - so if false, value is *not* null + // "garbage-in", accept what we can & convert + // throw only if conversion is not possible + + var s = value.ToString(); + + switch (_propertyType.DataTypeDatabaseType) + { + case DataTypeDatabaseType.Nvarchar: + case DataTypeDatabaseType.Ntext: + return s; + + case DataTypeDatabaseType.Integer: + if (s.IsNullOrWhiteSpace()) + return null; // assume empty means null + var convInt = value.TryConvertTo(); + if (convInt == false) ThrowTypeException(value, typeof(int), _propertyType.Alias); + return convInt.Result; + + case DataTypeDatabaseType.Decimal: + if (s.IsNullOrWhiteSpace()) + return null; // assume empty means null + var convDecimal = value.TryConvertTo(); + if (convDecimal == false) ThrowTypeException(value, typeof(decimal), _propertyType.Alias); + // need to normalize the value (change the scaling factor and remove trailing zeroes) + // because the underlying database is going to mess with the scaling factor anyways. + return convDecimal.Result.Normalize(); + + case DataTypeDatabaseType.Date: + if (s.IsNullOrWhiteSpace()) + return null; // assume empty means null + var convDateTime = value.TryConvertTo(); + if (convDateTime == false) ThrowTypeException(value, typeof(DateTime), _propertyType.Alias); + return convDateTime.Result; + } + + return value; } private static void ThrowTypeException(object value, Type expected, string alias) { - throw new InvalidOperationException(string.Format("Value \"{0}\" of type \"{1}\" could not be converted" - + " to type \"{2}\" which is expected by property type \"{3}\".", - value, value.GetType(), expected, alias)); + throw new InvalidOperationException($"Cannot assign value \"{value}\" of type \"{value.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); } /// - /// Gets or Sets the value of the Property + /// Gets a value indicating whether the (draft) neutral value is valid. /// - /// - /// Setting the value will trigger a type validation. - /// The type of the value has to be valid in order to be saved. - /// - [DataMember] - public object Value - { - get { return _value; } - set - { - var isOfExpectedType = _propertyType.IsPropertyTypeValid(value); - - if (isOfExpectedType == false) // isOfExpectedType is true if value is null - so if false, value is *not* null - { - // "garbage-in", accept what we can & convert - // throw only if conversion is not possible - - var s = value.ToString(); - - switch (_propertyType.DataTypeDatabaseType) - { - case DataTypeDatabaseType.Nvarchar: - case DataTypeDatabaseType.Ntext: - value = s; - break; - case DataTypeDatabaseType.Integer: - if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null - else - { - var convInt = value.TryConvertTo(); - if (convInt == false) ThrowTypeException(value, typeof(int), _propertyType.Alias); - value = convInt.Result; - } - break; - case DataTypeDatabaseType.Decimal: - if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null - else - { - var convDecimal = value.TryConvertTo(); - if (convDecimal == false) ThrowTypeException(value, typeof (decimal), _propertyType.Alias); - // need to normalize the value (change the scaling factor and remove trailing zeroes) - // because the underlying database is going to mess with the scaling factor anyways. - value = convDecimal.Result.Normalize(); - } - break; - case DataTypeDatabaseType.Date: - if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null - else - { - var convDateTime = value.TryConvertTo(); - if (convDateTime == false) ThrowTypeException(value, typeof (DateTime), _propertyType.Alias); - value = convDateTime.Result; - } - break; - } - } - - SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, Ps.Value.PropertyValueComparer); - } - } - - /// - /// Boolean indicating whether the current value is valid - /// - /// - /// A valid value implies that it is ready for publishing. - /// Invalid property values can be saved, but not published. - /// - /// True is property value is valid, otherwise false + /// An invalid value can be saved, but only valid values can be published. public bool IsValid() { - return IsValid(Value); + return IsValid(_pvalue.DraftValue); + } + + /// + /// Gets a value indicating whether the (draft) culture value is valid. + /// + /// An invalid value can be saved, but only valid values can be published. + public bool IsValid(int languageId) + { + return IsValid(GetValue(languageId)); + } + + /// + /// Gets a value indicating whether the (draft) segment value is valid. + /// + /// An invalid value can be saved, but only valid values can be published. + public bool IsValue(int languageId, string segment) + { + return IsValid(GetValue(languageId, segment)); } /// @@ -227,14 +342,16 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = (Property)base.DeepClone(); + var clone = (Property) base.DeepClone(); + //turn off change tracking clone.DisableChangeTracking(); + //need to manually assign since this is a readonly property - clone._propertyType = (PropertyType)PropertyType.DeepClone(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); + clone._propertyType = (PropertyType) PropertyType.DeepClone(); + //re-enable tracking + clone.ResetDirtyProperties(false); // not needed really, since we're not tracking clone.EnableChangeTracking(); return clone; diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 9ededb5404..d9ca9a4813 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -4,13 +4,11 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; -using System.Threading; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { /// - /// Represents a Collection of objects + /// Represents a collection of property values. /// [Serializable] [DataContract(IsReference = true)] @@ -18,24 +16,28 @@ namespace Umbraco.Core.Models { private readonly object _addLocker = new object(); internal Action OnAdd; - internal Func ValidateAdd { get; set; } + internal Func AdditionValidator { get; set; } + /// + /// Initializes a new instance of the class. + /// internal PropertyCollection() : base(StringComparer.InvariantCultureIgnoreCase) + { } + + /// + /// Initializes a new instance of the class. + /// + /// A function validating added properties. + internal PropertyCollection(Func additionValidator) + : this() { + AdditionValidator = additionValidator; } /// - /// Initializes a new instance of the class with a delegate responsible for validating the addition of instances. + /// Initializes a new instance of the class. /// - /// The validation callback. - /// - internal PropertyCollection(Func validationCallback) - : this() - { - ValidateAdd = validationCallback; - } - public PropertyCollection(IEnumerable properties) : this() { @@ -43,24 +45,28 @@ namespace Umbraco.Core.Models } /// - /// Resets the collection to only contain the instances referenced in the parameter, whilst maintaining - /// any validation delegates such as + /// Replaces all properties, whilst maintaining validation delegates. /// - /// The properties. - /// internal void Reset(IEnumerable properties) { Clear(); - properties.ForEach(Add); + foreach (var property in properties) + Add(property); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - protected override void SetItem(int index, Property item) + /// + /// Replaces the property at the specified index with the specified property. + /// + protected override void SetItem(int index, Property property) { - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + base.SetItem(index, property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property, index)); } + /// + /// Removes the property at the specified index. + /// protected override void RemoveItem(int index) { var removed = this[index]; @@ -68,70 +74,68 @@ namespace Umbraco.Core.Models OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } - protected override void InsertItem(int index, Property item) + /// + /// Inserts the specified property at the specified index. + /// + protected override void InsertItem(int index, Property property) { - base.InsertItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + base.InsertItem(index, property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); } + /// + /// Removes all properties. + /// protected override void ClearItems() { base.ClearItems(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - internal new void Add(Property item) + /// + /// Adds a property. + /// + internal new void Add(Property property) { - lock (_addLocker) + lock (_addLocker) // fixme - why are we locking here and not everywhere else?! { - var key = GetKeyForItem(item); + var key = GetKeyForItem(property); if (key != null) { - var exists = this.Contains(key); - if (exists) + if (Contains(key)) { - //NOTE: Consider checking type before value is set: item.PropertyType.DataTypeId == property.PropertyType.DataTypeId - //Transfer the existing value to the new property - var property = this[key]; - if (item.Id == 0 && property.Id != 0) - { - item.Id = property.Id; - } - if (item.Value == null && property.Value != null) - { - item.Value = property.Value; - } + // transfer id and values if ... + var existing = this[key]; - SetItem(IndexOfKey(key), item); + if (property.Id == 0 && existing.Id != 0) + property.Id = existing.Id; + + if (property.Values.Count == 0 && existing.Values.Count > 0) + property.Values = existing.Values; + + // replace existing with property and return, + // SetItem invokes OnCollectionChanged (but not OnAdd) + SetItem(IndexOfKey(key), property); return; } } - base.Add(item); - OnAdd.IfNotNull(x => x.Invoke());//Could this not be replaced by a Mandate/Contract for ensuring item is not null - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + base.Add(property); + + OnAdd?.Invoke(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); } } /// - /// Determines whether this collection contains a whose alias matches the specified PropertyType. + /// Gets the index for a specified property alias. /// - /// Alias of the PropertyType. - /// true if the collection contains the specified alias; otherwise, false. - /// - public new bool Contains(string propertyTypeAlias) - { - return base.Contains(propertyTypeAlias); - } - public int IndexOfKey(string key) { - for (var i = 0; i < this.Count; i++) + for (var i = 0; i < Count; i++) { if (this[i].Alias.InvariantEquals(key)) - { return i; - } } return -1; } @@ -142,13 +146,8 @@ namespace Umbraco.Core.Models } /// - /// Gets the element with the specified PropertyType. + /// Gets the property with the specified PropertyType. /// - /// - /// - /// The element with the specified PropertyType. If an element with the specified PropertyType is not found, an exception is thrown. - /// - /// The PropertyType of the element to get. is null.An element with the specified key does not exist in the collection. internal Property this[PropertyType propertyType] { get @@ -157,66 +156,57 @@ namespace Umbraco.Core.Models } } + /// + /// Occurs when the collection changes. + /// public event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { - if (CollectionChanged != null) - { - CollectionChanged(this, args); - } + CollectionChanged?.Invoke(this, args); } /// - /// Ensures that the collection contains Properties for the passed in PropertyTypes + /// Ensures that the collection contains properties for the specified property types. /// - /// List of PropertyType protected internal void EnsurePropertyTypes(IEnumerable propertyTypes) { - if (/*!this.Any() &&*/ propertyTypes != null) - { - foreach (var propertyType in propertyTypes) - { - Add(new Property(propertyType)); - } - } + if (propertyTypes == null) + return; + + foreach (var propertyType in propertyTypes) + Add(new Property(propertyType)); } /// - /// Ensures that the collection is cleared from PropertyTypes not in the list of passed in PropertyTypes + /// Ensures that the collection does not contain properties not in the specified property types. /// - /// List of PropertyType protected internal void EnsureCleanPropertyTypes(IEnumerable propertyTypes) { - if (propertyTypes != null) - { - //Remove PropertyTypes that doesn't exist in the list of new PropertyTypes - var aliases = this.Select(p => p.Alias).Except(propertyTypes.Select(x => x.Alias)).ToList(); - foreach (var alias in aliases) - { - Remove(alias); - } + if (propertyTypes == null) + return; - //Add new PropertyTypes from the list of passed in PropertyTypes - foreach (var propertyType in propertyTypes) - { - Add(new Property(propertyType)); - } - } + var propertyTypesA = propertyTypes.ToArray(); + + var thisAliases = this.Select(x => x.Alias); + var typeAliases = propertyTypesA.Select(x => x.Alias); + var remove = thisAliases.Except(typeAliases).ToArray(); + foreach (var alias in remove) + Remove(alias); + + foreach (var propertyType in propertyTypesA) + Add(new Property(propertyType)); } /// - /// Create a deep clone of this property collection + /// Deep clones. /// - /// public object DeepClone() { - var newList = new PropertyCollection(); - foreach (var p in this) - { - newList.Add((Property)p.DeepClone()); - } - return newList; + var clone = new PropertyCollection(); + foreach (var property in this) + clone.Add((Property) property.DeepClone()); + return clone; } } } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 25c45c5b0d..5fd594bcdc 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -251,50 +251,11 @@ namespace Umbraco.Core.Models } /// - /// Create a new Property object from a "raw" database value. + /// Creates a new property of this property type. /// - /// Can be used for the "old" values where no serialization type exists - /// - /// - /// - /// - internal Property CreatePropertyFromRawValue(object value, Guid version, int id) + public Property CreateProperty() { - return new Property(id, version, this, value); - } - - /// - /// Create a new Property object from a "raw" database value. - /// In some cases the value will need to be deserialized. - /// - /// - /// - /// - internal Property CreatePropertyFromRawValue(object value, string serializationType) - { - //The value from the db needs to be deserialized and then added to the property - //if its not a simple type (Integer, Date, Nvarchar, Ntext) - /*if (DataTypeDatabaseType == DataTypeDatabaseType.Object) - { - Type type = Type.GetType(serializationType); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(value.ToString())); - var objValue = _service.FromStream(stream, type); - return new Property(this, objValue); - }*/ - - return new Property(this, value); - } - - /// - /// Create a new Property object that conforms to the Type of the DataType - /// and can be validated according to DataType validation / Mandatory-check. - /// - /// - /// - public Property CreatePropertyFromValue(object value) - { - //Note that validation will occur when setting the value on the Property - return new Property(this, value); + return new Property(this); } /// diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 81e71b5de3..d4b0bc8a3b 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -143,13 +143,13 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, Ps.Value.ProtectedNodeIdSelector); } } - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberDirty) { _removedRules.Clear(); - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + base.ResetDirtyProperties(rememberDirty); foreach (var publicAccessRule in _ruleCollection) { - publicAccessRule.ResetDirtyProperties(rememberPreviouslyChangedProperties); + publicAccessRule.ResetDirtyProperties(rememberDirty); } } } diff --git a/src/Umbraco.Core/Models/PublishedState.cs b/src/Umbraco.Core/Models/PublishedState.cs index 374f47de7f..0ef0ee608c 100644 --- a/src/Umbraco.Core/Models/PublishedState.cs +++ b/src/Umbraco.Core/Models/PublishedState.cs @@ -3,30 +3,49 @@ namespace Umbraco.Core.Models { /// - /// The IContent states of a content version. + /// The states of a content item. /// public enum PublishedState { - // when a content version is loaded, its state is one of those two: + // when a content item is loaded, its state is one of those two: /// - /// The version is published. + /// The content item is published. /// Published, /// - /// The version is not published. + /// The content item is not published. /// /// Also: the version is being saved, in order to register changes /// made to an unpublished version of the content. Unpublished, - // legacy - remove - [Obsolete("kill!", true)] - Saved, - // when it is saved, its state can also be one of those: + // fixme + // what we can do is: + // - save changes to unpublished data (update current version) + // - save changes resulting from a publish (spawn a version) + // - save changes resulting from unpublishing (spawn a version) + // + // ContentRepository.CreateVersion + // only if uDocument.Published already, and Publishing + // ie updating what's already published + // creates a new uContentVersion, uDocumentVersion + // by copying everything from the existing (current) ones + // becomes the current ones + // then + // move all non-published uPropertyData to the new version + // create uPropertyData for new published values + // update/return the content w/new version + // + // saving is just updating draft + // publishing is described above + // un-publishing is ... + // also create a version to remember what was published + // and then on the new version, there is no 'new published values' + /// /// The version is being saved, in order to register changes made to a published content. /// diff --git a/src/Umbraco.Core/Models/Rdbms/ContentDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentDto.cs index 361b61cb42..99d4bcf0c0 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentDto.cs @@ -26,5 +26,9 @@ namespace Umbraco.Core.Models.Rdbms [ResultColumn] [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")] public NodeDto NodeDto { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] // FIXME not one-to-one! BUT it depends on the query! + public ContentVersionDto ContentVersionDto { get; set; } } } diff --git a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs index 80be40b2a7..b2f9aa5e46 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs @@ -29,10 +29,15 @@ namespace Umbraco.Core.Models.Rdbms [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime VersionDate { get; set; } + [Column("current")] + [Constraint(Default = false)] // fixme or true? unique index on NodeId,current!! + public bool Current { get; set; } + [Column("text")] [NullSetting(NullSetting = NullSettings.Null)] public string Text { get; set; } + // fixme assess whether we really want this [ResultColumn] [Reference(ReferenceType.OneToOne, ColumnName = "NodeId", ReferenceMemberName = "NodeId")] public ContentDto ContentDto { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs index 46e9da8280..f031fb254c 100644 --- a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs @@ -22,17 +22,9 @@ namespace Umbraco.Core.Models.Rdbms [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Published")] public bool Published { get; set; } - // fixme writerUserId - [Column("documentUser")] + [Column("writerUserId")] public int WriterUserId { get; set; } - [Column("versionId")] - [PrimaryKeyColumn(AutoIncrement = false)] - public Guid VersionId { get; set; } - - [Column("text")] - public string Text { get; set; } - [Column("releaseDate")] [NullSetting(NullSetting = NullSettings.Null)] public DateTime? ReleaseDate { get; set; } @@ -45,24 +37,12 @@ namespace Umbraco.Core.Models.Rdbms [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime UpdateDate { get; set; } - [Column("templateId")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(TemplateDto), Column = "nodeId")] - public int? TemplateId { get; set; } - - // fixme kill that one - [Column("newest")] - [Constraint(Default = "0")] - [Index(IndexTypes.NonClustered, Name = "IX_cmsDocument_newest")] - public bool Newest { get; set; } - [ResultColumn] [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] - public ContentVersionDto ContentVersionDto { get; set; } + public ContentDto ContentDto { get; set; } - // fixme wtf [ResultColumn] - [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] - public DocumentPublishedReadOnlyDto DocumentPublishedReadOnlyDto { get; set; } + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] // FIXME not one-to-one! BUT it depends on the query! + public DocumentVersionDto DocumentVersionDto { get; set; } } } diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentVersionDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentVersionDto.cs new file mode 100644 index 0000000000..d53064a331 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/DocumentVersionDto.cs @@ -0,0 +1,27 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName(TableName)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class DocumentVersionDto + { + private const string TableName = Constants.DatabaseSchema.Tables.DocumentVersion; + + [Column("id")] + [PrimaryKeyColumn] + [ForeignKey(typeof(ContentVersionDto))] + public int Id { get; set; } + + [Column("templateId")] + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(TemplateDto), Column = "nodeId")] + public int? TemplateId { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "VersionId")] + public ContentVersionDto ContentVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs index 15c32e7d85..315c9b68f4 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs @@ -30,6 +30,10 @@ namespace Umbraco.Core.Models.Rdbms [Constraint(Default = "''")] public string Password { get; set; } + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] + public ContentDto ContentDto { get; set; } + [ResultColumn] [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] public ContentVersionDto ContentVersionDto { get; set; } diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index e07441959a..661f9be3db 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core public const string Content = TableNamePrefix + "Content"; public const string ContentVersion = TableNamePrefix + "ContentVersion"; public const string Document = TableNamePrefix + "Document"; + public const string DocumentVersion = TableNamePrefix + "DocumentVersion"; public const string PropertyType = /*TableNamePrefix*/ "cms" + "PropertyType"; public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup"; diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 1597021a1d..10016e39c1 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -1,82 +1,53 @@ using System; -using System.Globalization; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories { - internal class ContentFactory + internal static class ContentFactory { - private readonly IContentType _contentType; - private readonly Guid _nodeObjectTypeId; - private readonly int _id; - private int _primaryKey; - - public ContentFactory(IContentType contentType, Guid nodeObjectTypeId, int id) - { - _contentType = contentType; - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - public ContentFactory(Guid nodeObjectTypeId, int id) - { - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - #region Implementation of IEntityFactory - /// - /// Builds a IContent item from the dto(s) and content type + /// Builds an IContent item from a dto and content type. /// - /// - /// This DTO can contain all of the information to build an IContent item, however in cases where multiple entities are being built, - /// a separate publishedDto entity will be supplied in place of the 's own - /// ResultColumn DocumentPublishedReadOnlyDto - /// - /// - /// - /// When querying for multiple content items the main DTO will not contain the ResultColumn DocumentPublishedReadOnlyDto and a separate publishedDto instance will be supplied - /// - /// - public static IContent BuildEntity(DocumentDto dto, IContentType contentType, DocumentPublishedReadOnlyDto publishedDto = null) + public static Content BuildEntity(DocumentDto dto, IContentType contentType) { - var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType); + var contentDto = dto.ContentDto; + var nodeDto = contentDto.NodeDto; + var versionDto = dto.DocumentVersionDto; + var contentVersionDto = versionDto.ContentVersionDto; + + var content = new Content(nodeDto.Text, nodeDto.ParentId, contentType); try { content.DisableChangeTracking(); content.Id = dto.NodeId; - content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; - content.Name = dto.Text; - content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text; - content.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; - content.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; + content.Key = nodeDto.UniqueId; + content.Version = contentVersionDto.VersionId; + + content.Name = nodeDto.Text; + content.NodeName = nodeDto.Text; + + content.Path = nodeDto.Path; + content.Level = nodeDto.Level; + content.ParentId = nodeDto.ParentId; + content.SortOrder = nodeDto.SortOrder; + content.Trashed = nodeDto.Trashed; + + content.CreatorId = nodeDto.UserId ?? 0; content.WriterId = dto.WriterUserId; - content.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; - content.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; - content.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; - content.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; + content.CreateDate = nodeDto.CreateDate; + content.UpdateDate = contentVersionDto.VersionDate; + content.Published = dto.Published; - content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; - content.UpdateDate = dto.ContentVersionDto.VersionDate; - content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null; - content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null; - content.Version = dto.ContentVersionDto.VersionId; + content.ExpireDate = dto.ExpiresDate; + content.ReleaseDate = dto.ReleaseDate; + // if not published, published date has no meaning really + content.PublishedDate = dto.Published ? contentVersionDto.VersionDate : DateTime.MinValue; - //Check if the publishedDto has been supplied, if not the use the dto's own DocumentPublishedReadOnlyDto value - content.PublishedVersionGuid = publishedDto == null - ? (dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId) - : publishedDto.VersionId; - content.PublishedDate = publishedDto == null - ? (dto.DocumentPublishedReadOnlyDto == null ? default(DateTime) : dto.DocumentPublishedReadOnlyDto.VersionDate) - : publishedDto.VersionDate; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) content.ResetDirtyProperties(false); return content; } @@ -84,105 +55,93 @@ namespace Umbraco.Core.Persistence.Factories { content.EnableChangeTracking(); } - } - [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] - public IContent BuildEntity(DocumentDto dto) + /// + /// Buils a dto from an IContent item. + /// + public static DocumentDto BuildDto(IContent entity) { - return BuildEntity(dto, _contentType); - } + var contentDto = BuildContentDto(entity); - public DocumentDto BuildDto(IContent entity) - { - //NOTE Currently doesn't add Alias (legacy that eventually will go away) - var documentDto = new DocumentDto - { - Newest = true, - NodeId = entity.Id, - Published = entity.Published, - Text = entity.Name, - UpdateDate = entity.UpdateDate, - WriterUserId = entity.WriterId, - VersionId = entity.Version, - ExpiresDate = null, - ReleaseDate = null, - ContentVersionDto = BuildContentVersionDto(entity) - }; - - if (entity.Template != null && entity.Template.Id > 0) - documentDto.TemplateId = entity.Template.Id; - - if (entity.ExpireDate.HasValue) - documentDto.ExpiresDate = entity.ExpireDate.Value; - - if (entity.ReleaseDate.HasValue) - documentDto.ReleaseDate = entity.ReleaseDate.Value; - - return documentDto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private ContentVersionDto BuildContentVersionDto(IContent entity) - { - //TODO: Change this once the Language property is public on IContent - var content = entity as Content; - var lang = content == null ? string.Empty : content.Language; - - var contentVersionDto = new ContentVersionDto + var dto = new DocumentDto { NodeId = entity.Id, - VersionDate = entity.UpdateDate, - VersionId = entity.Version, - ContentDto = BuildContentDto(entity) + Published = entity.Published, + WriterUserId = entity.WriterId, + ReleaseDate = entity.ReleaseDate, + ExpiresDate = entity.ExpireDate, + UpdateDate = entity.UpdateDate, + + ContentDto = contentDto, + DocumentVersionDto = BuildDocumentVersionDto(entity, contentDto) }; - return contentVersionDto; + + return dto; } - private ContentDto BuildContentDto(IContent entity) + private static ContentDto BuildContentDto(IContent entity) { - var contentDto = new ContentDto + var dto = new ContentDto { + // Id = _primaryKey if >0 - fixme - kill that id entirely NodeId = entity.Id, ContentTypeId = entity.ContentTypeId, + NodeDto = BuildNodeDto(entity) }; - if (_primaryKey > 0) - { - contentDto.Id = _primaryKey; - } - - return contentDto; + return dto; } - private NodeDto BuildNodeDto(IContent entity) + private static NodeDto BuildNodeDto(IContent entity) { - //TODO: Change this once the Language property is public on IContent - var nodeName = entity.Name; - - var nodeDto = new NodeDto + var dto = new NodeDto { - CreateDate = entity.CreateDate, NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, + UniqueId = entity.Key, ParentId = entity.ParentId, + Level = Convert.ToInt16(entity.Level), Path = entity.Path, SortOrder = entity.SortOrder, - Text = nodeName, Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId + UserId = entity.CreatorId, + Text = entity.Name, + NodeObjectType = Constants.ObjectTypes.Document, + CreateDate = entity.CreateDate, }; - return nodeDto; + return dto; + } + + private static DocumentVersionDto BuildDocumentVersionDto(IContent entity, ContentDto contentDto) + { + var dto = new DocumentVersionDto + { + //Id =, // fixme + TemplateId = entity.Template?.Id ?? 0, + + ContentVersionDto = BuildContentVersionDto(entity, contentDto) + }; + + return dto; + } + + private static ContentVersionDto BuildContentVersionDto(IContent entity, ContentDto contentDto) + { + var dto = new ContentVersionDto + { + //Id =, // fixme + NodeId = entity.Id, + VersionId = entity.Version, + VersionDate = entity.UpdateDate, + Current = true, // always building the current one + Text = entity.Name, + + ContentDto = contentDto + }; + + return dto; } } } diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs index 1f1749ed91..e84dbf8da2 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -7,117 +6,106 @@ namespace Umbraco.Core.Persistence.Factories { internal class MediaFactory { - private readonly IMediaType _contentType; - private readonly Guid _nodeObjectTypeId; - private readonly int _id; - private int _primaryKey; - - public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId, int id) + /// + /// Builds an IMedia item from a dto and content type. + /// + public static Models.Media BuildEntity(ContentDto dto, IMediaType contentType) { - _contentType = contentType; - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } + var nodeDto = dto.NodeDto; + var contentVersionDto = dto.ContentVersionDto; - public MediaFactory(Guid nodeObjectTypeId, int id) - { - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - #region Implementation of IEntityFactory - - public static IMedia BuildEntity(ContentVersionDto dto, IMediaType contentType) - { - var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, contentType); + var content = new Models.Media(nodeDto.Text, nodeDto.ParentId, contentType); try { - media.DisableChangeTracking(); + content.DisableChangeTracking(); - media.Id = dto.NodeId; - media.Key = dto.ContentDto.NodeDto.UniqueId; - media.Path = dto.ContentDto.NodeDto.Path; - media.CreatorId = dto.ContentDto.NodeDto.UserId.Value; - media.Level = dto.ContentDto.NodeDto.Level; - media.ParentId = dto.ContentDto.NodeDto.ParentId; - media.SortOrder = dto.ContentDto.NodeDto.SortOrder; - media.Trashed = dto.ContentDto.NodeDto.Trashed; - media.CreateDate = dto.ContentDto.NodeDto.CreateDate; - media.UpdateDate = dto.VersionDate; - media.Version = dto.VersionId; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - media.ResetDirtyProperties(false); - return media; + content.Id = dto.NodeId; + content.Key = nodeDto.UniqueId; + content.Version = contentVersionDto.VersionId; + + // fixme missing names? + + content.Path = nodeDto.Path; + content.Level = nodeDto.Level; + content.ParentId = nodeDto.ParentId; + content.SortOrder = nodeDto.SortOrder; + content.Trashed = nodeDto.Trashed; + + content.CreatorId = nodeDto.UserId ?? 0; + // fixme missing writerId - which then should move to nodeDto + content.CreateDate = nodeDto.CreateDate; + content.UpdateDate = contentVersionDto.VersionDate; + + // reset dirty initial properties (U4-1946) + content.ResetDirtyProperties(false); + return content; } finally { - media.EnableChangeTracking(); + content.EnableChangeTracking(); } - } - [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] - public IMedia BuildEntity(ContentVersionDto dto) + /// + /// Buils a dto from an IMedia item. + /// + public static ContentDto BuildDto(IMedia entity) { - return BuildEntity(dto, _contentType); - } - - public ContentVersionDto BuildDto(IMedia entity) - { - var dto = new ContentVersionDto - { - NodeId = entity.Id, - VersionDate = entity.UpdateDate, - VersionId = entity.Version, - ContentDto = BuildContentDto(entity) - }; + var dto = BuildContentDto(entity); + dto.ContentVersionDto = BuildContentVersionDto(entity, dto); return dto; } - #endregion - - public void SetPrimaryKey(int primaryKey) + private static ContentDto BuildContentDto(IMedia entity) { - _primaryKey = primaryKey; - } - - private ContentDto BuildContentDto(IMedia entity) - { - var contentDto = new ContentDto - { - NodeId = entity.Id, - ContentTypeId = entity.ContentTypeId, - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) + var dto = new ContentDto { - contentDto.Id = _primaryKey; - } + // Id = _primaryKey if >0 - fixme - kill that id entirely + NodeId = entity.Id, + ContentTypeId = entity.ContentTypeId, - return contentDto; + NodeDto = BuildNodeDto(entity) + }; + + return dto; } - private NodeDto BuildNodeDto(IMedia entity) + private static NodeDto BuildNodeDto(IMedia entity) { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; + var dto = new NodeDto + { + NodeId = entity.Id, + UniqueId = entity.Key, + ParentId = entity.ParentId, + Level = Convert.ToInt16(entity.Level), + Path = entity.Path, + SortOrder = entity.SortOrder, + Trashed = entity.Trashed, + UserId = entity.CreatorId, + Text = entity.Name, + NodeObjectType = Constants.ObjectTypes.Media, + CreateDate = entity.CreateDate + }; - return nodeDto; + return dto; + } + + private static ContentVersionDto BuildContentVersionDto(IMedia entity, ContentDto contentDto) + { + var dto = new ContentVersionDto + { + //Id =, // fixme + NodeId = entity.Id, + VersionId = entity.Version, + VersionDate = entity.UpdateDate, + Current = true, // always building the current one + Text = entity.Name, + + ContentDto = contentDto + }; + + return dto; } } } diff --git a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs index 3f47137c74..0a16faa325 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs @@ -1,6 +1,4 @@ using System; -using System.Globalization; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -8,128 +6,118 @@ namespace Umbraco.Core.Persistence.Factories { internal class MemberFactory { - private readonly IMemberType _contentType; - private readonly Guid _nodeObjectTypeId; - private readonly int _id; - private int _primaryKey; - - public MemberFactory(IMemberType contentType, Guid nodeObjectTypeId, int id) + /// + /// Builds an IMedia item from a dto and content type. + /// + public static Member BuildEntity(MemberDto dto, IMemberType contentType) { - _contentType = contentType; - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } + var nodeDto = dto.ContentDto.NodeDto; + var contentVersionDto = dto.ContentVersionDto; - public MemberFactory(Guid nodeObjectTypeId, int id) - { - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - #region Implementation of IEntityFactory - - public static IMember BuildEntity(MemberDto dto, IMemberType contentType) - { - var member = new Member( - dto.ContentVersionDto.ContentDto.NodeDto.Text, - dto.Email, dto.LoginName, dto.Password, contentType); + var content = new Member(nodeDto.Text, dto.Email, dto.LoginName, dto.Password, contentType); try { - member.DisableChangeTracking(); + content.DisableChangeTracking(); - member.Id = dto.NodeId; - member.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; - member.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; - member.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; - member.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; - member.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; - member.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; - member.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; - member.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; - member.UpdateDate = dto.ContentVersionDto.VersionDate; - member.Version = dto.ContentVersionDto.VersionId; + content.Id = dto.NodeId; + content.Key = nodeDto.UniqueId; + content.Version = contentVersionDto.VersionId; - member.ProviderUserKey = member.Key; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - member.ResetDirtyProperties(false); - return member; + // fixme missing names? + + content.Path = nodeDto.Path; + content.Level = nodeDto.Level; + content.ParentId = nodeDto.ParentId; + content.SortOrder = nodeDto.SortOrder; + content.Trashed = nodeDto.Trashed; + + content.CreatorId = nodeDto.UserId ?? 0; + // fixme missing writerId - which then should move to nodeDto + content.CreateDate = nodeDto.CreateDate; + content.UpdateDate = contentVersionDto.VersionDate; + + content.ProviderUserKey = content.Key; // fixme explain + + // reset dirty initial properties (U4-1946) + content.ResetDirtyProperties(false); + return content; } finally { - member.EnableChangeTracking(); + content.EnableChangeTracking(); } } - [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] - public IMember BuildEntity(MemberDto dto) + /// + /// Buils a dto from an IMember item. + /// + public static MemberDto BuildDto(IMember entity) { - return BuildEntity(dto, _contentType); - } + var contentDto = BuildContentDto(entity); - public MemberDto BuildDto(IMember entity) - { var dto = new MemberDto { - ContentVersionDto = new ContentVersionDto - { - NodeId = entity.Id, - VersionDate = entity.UpdateDate, - VersionId = entity.Version, - ContentDto = BuildContentDto(entity) - }, Email = entity.Email, LoginName = entity.Username, NodeId = entity.Id, - Password = entity.RawPasswordValue + Password = entity.RawPasswordValue, + + ContentDto = contentDto, + ContentVersionDto = BuildContentVersionDto(entity, contentDto) }; return dto; } - #endregion - - public void SetPrimaryKey(int primaryKey) + private static ContentDto BuildContentDto(IMember entity) { - _primaryKey = primaryKey; - } - - private ContentDto BuildContentDto(IMember entity) - { - var contentDto = new ContentDto + var dto = new ContentDto { + // Id = _primaryKey if >0 - fixme - kill that id entirely NodeId = entity.Id, ContentTypeId = entity.ContentTypeId, + NodeDto = BuildNodeDto(entity) }; - if (_primaryKey > 0) - { - contentDto.Id = _primaryKey; - } - - return contentDto; + return dto; } - private NodeDto BuildNodeDto(IMember entity) + private static NodeDto BuildNodeDto(IMember entity) { - var nodeDto = new NodeDto + var dto = new NodeDto { - CreateDate = entity.CreateDate, NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, + UniqueId = entity.Key, ParentId = entity.ParentId, + Level = Convert.ToInt16(entity.Level), Path = entity.Path, SortOrder = entity.SortOrder, - Text = entity.Name, Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId + UserId = entity.CreatorId, + Text = entity.Name, + NodeObjectType = Constants.ObjectTypes.Member, + CreateDate = entity.CreateDate }; - return nodeDto; + return dto; + } + + private static ContentVersionDto BuildContentVersionDto(IMember entity, ContentDto contentDto) + { + var dto = new ContentVersionDto + { + //Id =, // fixme + NodeId = entity.Id, + VersionId = entity.Version, + VersionDate = entity.UpdateDate, + Current = true, // always building the current one + Text = entity.Name, + + ContentDto = contentDto + }; + + return dto; } } - } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 013e371252..bf000337f4 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -6,50 +6,24 @@ using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories { - internal class PropertyFactory + internal static class PropertyFactory { - private readonly PropertyType[] _compositionTypeProperties; - private readonly Guid _version; - private readonly int _id; - private readonly DateTime _createDate; - private readonly DateTime _updateDate; - - public PropertyFactory(PropertyType[] compositionTypeProperties, Guid version, int id) - { - _compositionTypeProperties = compositionTypeProperties; - _version = version; - _id = id; - } - - public PropertyFactory(PropertyType[] compositionTypeProperties, Guid version, int id, DateTime createDate, DateTime updateDate) - { - _compositionTypeProperties = compositionTypeProperties; - _version = version; - _id = id; - _createDate = createDate; - _updateDate = updateDate; - } - - public static IEnumerable BuildEntity(IReadOnlyCollection dtos, PropertyType[] compositionTypeProperties, DateTime createDate, DateTime updateDate) + public static IEnumerable BuildEntities(IReadOnlyCollection dtos, PropertyType[] propertyTypes) { var properties = new List(); - foreach (var propertyType in compositionTypeProperties) + foreach (var propertyType in propertyTypes) { - var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id); - var property = propertyDataDto == null - ? propertyType.CreatePropertyFromValue(null) - : propertyType.CreatePropertyFromRawValue(propertyDataDto.Value, - propertyDataDto.VersionId.Value, - propertyDataDto.Id); + var property = propertyType.CreateProperty(); + try { - //on initial construction we don't want to have dirty properties tracked property.DisableChangeTracking(); - property.CreateDate = createDate; - property.UpdateDate = updateDate; - // http://issues.umbraco.org/issue/U4-1946 + var propDtos = dtos.Where(x => x.PropertyTypeId == propertyType.Id); + foreach (var propDto in propDtos) + property.SetValue(propDto.LanguageId, propDto.Segment, propDto.Value); + property.ResetDirtyProperties(false); properties.Add(property); } @@ -57,78 +31,81 @@ namespace Umbraco.Core.Persistence.Factories { property.EnableChangeTracking(); } - } return properties; } - [Obsolete("Use the static method instead, there's no reason to allocate one of these classes everytime we want to map values")] - public IEnumerable BuildEntity(PropertyDataDto[] dtos) + private static PropertyDataDto BuildDto(int nodeId, Guid versionId, Property property, Property.PropertyValue propertyValue, bool published) { - return BuildEntity(dtos, _compositionTypeProperties, _createDate, _updateDate); + var dto = new PropertyDataDto { NodeId = nodeId, VersionId = versionId, PropertyTypeId = property.PropertyTypeId }; + + if (property.HasIdentity) + dto.Id = property.Id; + + if (propertyValue.LanguageId.HasValue) + dto.LanguageId = propertyValue.LanguageId; + + if (propertyValue.Segment != null) + dto.Segment = propertyValue.Segment; + + dto.Published = published; + + var value = published ? propertyValue.PublishedValue : propertyValue.DraftValue; + + if (property.DataTypeDatabaseType == DataTypeDatabaseType.Integer) + { + if (value is bool || property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.TrueFalseAlias) + { + dto.IntegerValue = value != null && string.IsNullOrEmpty(value.ToString()) ? 0 : Convert.ToInt32(value); + } + else if (value != null && string.IsNullOrWhiteSpace(value.ToString()) == false && int.TryParse(value.ToString(), out var val)) + { + dto.IntegerValue = val; + } + } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Decimal && value != null) + { + if (decimal.TryParse(value.ToString(), out var val)) + { + dto.DecimalValue = val; // property value should be normalized already + } + } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date && value != null && string.IsNullOrWhiteSpace(value.ToString()) == false) + { + if (DateTime.TryParse(value.ToString(), out var date)) + { + dto.DateValue = date; + } + } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Ntext && value != null) + { + dto.TextValue = value.ToString(); + } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar && value != null) + { + dto.VarcharValue = value.ToString(); + } + + return dto; } - public IEnumerable BuildDto(IEnumerable properties) + public static IEnumerable BuildDtos(int nodeId, Guid versionId, IEnumerable properties) { var propertyDataDtos = new List(); foreach (var property in properties) { - var dto = new PropertyDataDto { NodeId = _id, PropertyTypeId = property.PropertyTypeId, VersionId = _version }; - - //Check if property has an Id and set it, so that it can be updated if it already exists - if (property.HasIdentity) + foreach (var propertyValue in property.Values) { - dto.Id = property.Id; + if (propertyValue.DraftValue != null) + propertyDataDtos.Add(BuildDto(nodeId, versionId, property, propertyValue, false)); + if (propertyValue.PublishedValue != null) + propertyDataDtos.Add(BuildDto(nodeId, versionId, property, propertyValue, true)); } - - if (property.DataTypeDatabaseType == DataTypeDatabaseType.Integer) - { - if (property.Value is bool || property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.TrueFalseAlias) - { - dto.IntegerValue = property.Value != null && string.IsNullOrEmpty(property.Value.ToString()) - ? 0 - : Convert.ToInt32(property.Value); - } - else - { - int val; - if ((property.Value != null && string.IsNullOrWhiteSpace(property.Value.ToString()) == false) && int.TryParse(property.Value.ToString(), out val)) - { - dto.IntegerValue = val; - } - } - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Decimal && property.Value != null) - { - decimal val; - if (decimal.TryParse(property.Value.ToString(), out val)) - { - dto.DecimalValue = val; // property value should be normalized already - } - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date && property.Value != null && string.IsNullOrWhiteSpace(property.Value.ToString()) == false) - { - DateTime date; - if (DateTime.TryParse(property.Value.ToString(), out date)) - { - dto.DateValue = date; - } - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Ntext && property.Value != null) - { - dto.TextValue = property.Value.ToString(); - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar && property.Value != null) - { - dto.VarcharValue = property.Value.ToString(); - } - - propertyDataDtos.Add(dto); } + return propertyDataDtos; } - } } diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs index 4f8ea60e9d..55257517cd 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); CacheMap(src => src.UpdateDate, dto => dto.VersionDate); CacheMap(src => src.Version, dto => dto.VersionId); - CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Name, dto => dto.ContentDto.NodeDto.Text); CacheMap(src => src.ExpireDate, dto => dto.ExpiresDate); CacheMap(src => src.ReleaseDate, dto => dto.ReleaseDate); CacheMap(src => src.Published, dto => dto.Published); diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs index 3a69564c92..2026ed3fcb 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs @@ -14,7 +14,6 @@ namespace Umbraco.Core.Persistence.Mappers protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Version, dto => dto.VersionId); CacheMap(src => src.PropertyTypeId, dto => dto.PropertyTypeId); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs index 165f771ff2..e84d924001 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs @@ -26,11 +26,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create private ISqlSyntaxProvider SqlSyntax => _context.Database.SqlContext.SqlSyntax; - public void Table() + public void Table(bool withoutKeysAndIndexes = false) { var tableDefinition = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); AddSql(SqlSyntax.Format(tableDefinition)); + if (withoutKeysAndIndexes) + return; + AddSql(SqlSyntax.FormatPrimaryKey(tableDefinition)); foreach (var sql in SqlSyntax.Format(tableDefinition.ForeignKeys)) AddSql(sql); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs index 4ada8439cc..e9c43dec36 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create { public interface ICreateBuilder : IFluentSyntax { - void Table(); + void Table(bool withoutKeysAndIndexes = false); void KeysAndIndexes(); void KeysAndIndexes(Type typeOfDto); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs index 0b99f06e28..1853ff7eff 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs @@ -23,6 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight MigratePropertyData(); MigrateContent(); MigrateContentVersion(); + MigrateDocumentVersion(); MigrateDocument(); // re-create *all* keys and indexes @@ -31,52 +32,6 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight Create.KeysAndIndexes(x.Value); } - private void MigrateContent() - { - // if the table has already been renamed, we're done - if (TableExists(Constants.DatabaseSchema.Tables.Content)) - return; - - // migrate - if (ColumnExists(PreTables.Content, "contentType")) - ReplaceColumn(PreTables.Content, "contentType", "contentTypeId"); - if (ColumnExists(PreTables.Content, "pk")) - ReplaceColumn(PreTables.Content, "pk", "id"); - Rename.Table(PreTables.Content).To(Constants.DatabaseSchema.Tables.Content); - } - - private void MigrateContentVersion() - { - // if the table has already been renamed, we're done - if (TableExists(Constants.DatabaseSchema.Tables.ContentVersion)) - return; - - // migrate - if (!ColumnExists(PreTables.ContentVersion, "text")) - AddColumn(PreTables.ContentVersion, "text"); - if (ColumnExists(PreTables.ContentVersion, "ContentId")) - ReplaceColumn(PreTables.Content, "ContentId", "nodeId"); - Rename.Table(PreTables.ContentVersion).To(Constants.DatabaseSchema.Tables.ContentVersion); - } - - private void MigrateDocument() - { - // if the table has already been renamed, we're done - if (TableExists(Constants.DatabaseSchema.Tables.Document)) - return; - - // migrate - // todo! - // - rename nodeId - // - rename documentUser - // - only keep newest but update other versions with text? - // - document text vs node text? - // - we need uDocumentVersion FIRST not to lose templateId! - // - kill newest - - Rename.Table(PreTables.Document).To(Constants.DatabaseSchema.Tables.Document); - } - private void MigratePropertyData() { // if the table has already been renamed, we're done @@ -119,6 +74,151 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight Rename.Table(PreTables.PropertyData).To(Constants.DatabaseSchema.Tables.PropertyData); } + private void MigrateContent() + { + // if the table has already been renamed, we're done + if (TableExists(Constants.DatabaseSchema.Tables.Content)) + return; + + // rename columns + if (ColumnExists(PreTables.Content, "contentType")) + ReplaceColumn(PreTables.Content, "contentType", "contentTypeId"); + if (ColumnExists(PreTables.Content, "pk")) + ReplaceColumn(PreTables.Content, "pk", "id"); + + // rename table + Rename.Table(PreTables.Content).To(Constants.DatabaseSchema.Tables.Content); + } + + private void MigrateContentVersion() + { + // if the table has already been renamed, we're done + if (TableExists(Constants.DatabaseSchema.Tables.ContentVersion)) + return; + + // add text column + if (!ColumnExists(PreTables.ContentVersion, "text")) + AddColumn(PreTables.ContentVersion, "text"); + + // populate text column + Execute.Sql($@"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver +SET cver.text=doc.text +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc WHERE cver.versionId=doc.versionId"); + + // add current column + if (!ColumnExists(PreTables.ContentVersion, "current")) + AddColumn(PreTables.ContentVersion, "current"); + + // populate current column => done during MigrateDocument + + // rename contentId column + if (ColumnExists(PreTables.ContentVersion, "ContentId")) + ReplaceColumn(PreTables.Content, "ContentId", "nodeId"); + + // rename table + Rename.Table(PreTables.ContentVersion).To(Constants.DatabaseSchema.Tables.ContentVersion); + } + + private void MigrateDocumentVersion() + { + // if the table already exists, we're done + if (TableExists(Constants.DatabaseSchema.Tables.DocumentVersion)) + return; + + // create table + Create.Table(withoutKeysAndIndexes: true); + + Execute.Sql($@"INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, contentVersionId, templateId) +SELECT cver.Id, cver.versionId, doc.templateId +FROM {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver +JOIN {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc ON doc.versionId=cver.versionId"); + } + + private void MigrateDocument() + { + // if the table has already been renamed, we're done + if (TableExists(Constants.DatabaseSchema.Tables.Document)) + return; + + // drop some columns + Delete.Column("text").FromTable(PreTables.Document); // fixme usage + Delete.Column("templateId").FromTable(PreTables.Document); // fixme usage + + // replace some columns + ReplaceColumn(PreTables.Document, "documentUser", "writerUserId"); + + // update PropertyData.Published for published versions + if (Context.SqlContext.DatabaseType.IsMySql()) + { + // FIXME does MySql support such update syntax? + throw new NotSupportedException(); + } + else + { + Execute.Sql($@"UPDATE pdata +SET pdata.published=1 +FROM {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.PropertyData)} pdata +JOIN {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc ON doc.versionId=pdata.versionId AND doc.published=1"); + } + + // collapse draft version into published version (if any) + // ie we keep the published version and remove the draft one + Execute.Code(context => + { + var versions = context.Database.Fetch(@"SELECT + doc1.versionId versionId1, doc1.newest newest1, doc1.published published1, + doc2.versionId versionId2, doc2.newest newest2, doc2.published published2 +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc1 +JOIN {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc2 + ON doc1.nodeId=doc2.nodeId AND doc1.versionId<>doc2.versionId AND doc1.updateDate + { + var versions = context.Database.Fetch(@"SELECT + doc.nodeId, doc.versionId versionId1 +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc +WHERE doc.newest=1 +"); + foreach (var version in versions) + { + context.Database.Execute($@"DELETE FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} +WHERE nodeId={version.nodeId} AND versionId<>{version.versionId}"); + } + return string.Empty; + }); + + // drop some columns + Delete.Column("versionId").FromTable(PreTables.Document); // fixme usage + Delete.Column("newest").FromTable(PreTables.Document); // fixme usage + + // rename table + Rename.Table(PreTables.Document).To(Constants.DatabaseSchema.Tables.Document); + } + private static class PreTables { public const string Lock = "umbracoLock"; diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index 749b112adf..c5f254657e 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -276,7 +276,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. /// Expressions specifying the fields. /// The Sql statement. - public static Sql AndByDesc(this Sql sql, params Expression>[] fields) + public static Sql AndByDescending(this Sql sql, params Expression>[] fields) { var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index d22c5ffa4a..a03be685cd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,13 +1,12 @@ using NPoco; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -37,9 +36,6 @@ namespace Umbraco.Core.Persistence.Repositories _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); _cacheHelper = cacheHelper; - - _publishedQuery = work.SqlContext.Query().Where(x => x.Published); // fixme not used? - _contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger); EnsureUniqueNaming = settings.EnsureUniqueNaming; } @@ -52,136 +48,102 @@ namespace Umbraco.Core.Persistence.Repositories private PermissionRepository PermissionRepository => _permissionRepository ?? (_permissionRepository = new PermissionRepository(UnitOfWork, _cacheHelper, Logger)); - #region Overrides of RepositoryBase + #region Repository Base + + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Document; protected override IContent PerformGet(int id) { var sql = GetBaseQuery(QueryType.Single) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate); + .Where(x => x.NodeId == id) + .SelectTop(1); - var dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId); - - return content; + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null + ? null + : MapDtoToContent(dto); } protected override IEnumerable PerformGetAll(params int[] ids) { - Sql Translate(Sql tsql) - { - if (ids.Any()) - tsql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); + var sql = GetBaseQuery(QueryType.Many); - // we only want the newest ones with this method - tsql.Where(x => x.Newest); + if (ids.Any()) + sql.WhereIn(x => x.NodeId, ids); - return tsql; - } - - var sql = Translate(GetBaseQuery(QueryType.Many)); - return MapQueryDtos(Database.Fetch(sql), many: true); + return MapDtosToContent(Database.Fetch(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(QueryType.Many); + var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .Where(x => x.Newest) - //.OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.Level) - .OrderBy(x => x.SortOrder); + var sql = translator.Translate(); - return MapQueryDtos(Database.Fetch(sql), many: true); + sql // fixme why? + .OrderBy(x => x.Level) + .OrderBy(x => x.SortOrder); + + return MapDtosToContent(Database.Fetch(sql)); } - #endregion - - #region Static Queries - - private readonly IQuery _publishedQuery; - - #endregion - - #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(QueryType queryType) + { + return GetBaseQuery(queryType, true); + } + + protected virtual Sql GetBaseQuery(QueryType queryType, bool current) { var sql = SqlContext.Sql(); - switch (queryType) + switch (queryType) // FIXME pretend we still need these queries for now { case QueryType.Count: sql = sql.SelectCount(); break; case QueryType.Ids: - sql = sql.Select("cmsDocument.nodeId"); + sql = sql.Select(x => x.NodeId); break; case QueryType.Single: - sql = sql.Select(r => - r.Select(documentDto => documentDto.ContentVersionDto, r1 => - r1.Select(contentVersionDto => contentVersionDto.ContentDto, r2 => - r2.Select(contentDto => contentDto.NodeDto))) - .Select(documentDto => documentDto.DocumentPublishedReadOnlyDto, "cmsDocument2")); - break; case QueryType.Many: - // 'many' does not join on cmsDocument2 sql = sql.Select(r => - r.Select(documentDto => documentDto.ContentVersionDto, r1 => - r1.Select(contentVersionDto => contentVersionDto.ContentDto, r2 => - r2.Select(contentDto => contentDto.NodeDto)))); + r.Select(documentDto => documentDto.ContentDto, r1 => + r1.Select(contentDto => contentDto.NodeDto)) + .Select(documentDto => documentDto.DocumentVersionDto, r1 => + r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto))); break; } sql .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId); + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.Id, right => right.Id); - if (queryType == QueryType.Single) - { - //The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto - //information with the entire data set, so basically this will get both the latest document and also it's published - //version if it has one. When performing a count or when retrieving Ids like in paging, this is unecessary - //and causes huge performance overhead for the SQL server, especially when sorting the result. - //We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information - //in a separate query. For a single entity this is ok. + sql.Where(x => x.NodeObjectType == NodeObjectTypeId); - sql - .LeftJoin("cmsDocument2") - .On((x1, x2) => x1.NodeId == x2.NodeId && x2.Published, alias2: "cmsDocument2"); - } - - sql - .Where(x => x.NodeObjectType == NodeObjectTypeId); + if (current) + sql.Where(x => x.Current); // always get the current version return sql; } - // fixme - move that one up to Versionable! + // fixme - move that one up to Versionable! or better: kill it! protected override Sql GetBaseQuery(bool isCount) { return GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); } - protected override string GetBaseWhereClause() + protected override string GetBaseWhereClause() // fixme - can we kill / refactor this? { return "umbracoNode.id = @Id"; } protected override IEnumerable GetDeleteClauses() { - var list = new List + var list = new List // fixme table names, escape everything, etc { "DELETE FROM umbracoRedirectUrl WHERE contentKey IN (SELECT uniqueID FROM umbracoNode WHERE id = @Id)", "DELETE FROM cmsTask WHERE nodeId = @Id", @@ -193,10 +155,11 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM umbracoRelation WHERE childId = @Id", "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", - "DELETE FROM cmsDocument WHERE nodeId = @Id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Document + " WHERE nodeId = @Id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentVersion + " WHERE nodeId = @Id", "DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE nodeId = @Id", "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE ContentId = @Id", "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @Id", "DELETE FROM umbracoAccess WHERE nodeId = @Id", @@ -205,68 +168,27 @@ namespace Umbraco.Core.Persistence.Repositories return list; } - protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Document; - #endregion - #region Overrides of VersionableRepositoryBase + #region Versions - public override IEnumerable GetAllVersions(int id) + public override IEnumerable GetAllVersions(int nodeId) { - var sql = GetBaseQuery(QueryType.Many) - .Where(GetBaseWhereClause(), new { Id = id }) - .OrderByDescending(x => x.VersionDate); + var sql = GetBaseQuery(QueryType.Many, false) + .Where(x => x.NodeId == nodeId) + .OrderByDescending(x => x.Current) + .AndByDescending(x => x.VersionDate); - return MapQueryDtos(Database.Fetch(sql), true, true, true); + return MapDtosToContent(Database.Fetch(sql), true); } public override IContent GetByVersion(Guid versionId) { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); + var sql = GetBaseQuery(QueryType.Single) + .Where(x => x.VersionId == versionId); var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, versionId); - - return content; - } - - public override void DeleteVersion(Guid versionId) - { - var sql = SqlContext.Sql() - .SelectAll() - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.VersionId == versionId) - .Where(x => x.Newest != true); - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) return; - - PerformDeleteVersion(dto.NodeId, versionId); - } - - public override void DeleteVersions(int id, DateTime versionDate) - { - var sql = SqlContext.Sql() - .SelectAll() - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.NodeId == id) - .Where(x => x.VersionDate < versionDate) - .Where(x => x.Newest != true); - var list = Database.Fetch(sql); - if (list.Any() == false) return; - - foreach (var dto in list) - { - PerformDeleteVersion(id, dto.VersionId); - } + return dto == null ? null : MapDtoToContent(dto); } protected override void PerformDeleteVersion(int id, Guid versionId) @@ -274,6 +196,7 @@ namespace Umbraco.Core.Persistence.Repositories // raise event first else potential FK issues OnUowRemovingVersion(new UnitOfWorkVersionEventArgs(UnitOfWork, id, versionId)); + // fixme - syntax + ... Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); @@ -281,136 +204,110 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Unit of Work Implementation - - protected override void PersistDeletedItem(IContent entity) - { - // raise event first else potential FK issues - OnUowRemovingEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); - - //We need to clear out all access rules but we need to do this in a manual way since - // nothing in that table is joined to a content id - var subQuery = SqlContext.Sql() - .Select("umbracoAccessRule.accessId") - .From() - .InnerJoin() - .On(left => left.AccessId, right => right.Id) - .Where(dto => dto.NodeId == entity.Id); - Database.Execute(SqlContext.SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); - - //now let the normal delete clauses take care of everything else - base.PersistDeletedItem(entity); - } + #region Persist protected override void PersistNewItem(IContent entity) { - ((Content)entity).AddingEntity(); + ((Content) entity).AddingEntity(); - //ensure the default template is assigned + // ensure that the default template is assigned if (entity.Template == null) entity.Template = entity.ContentType.DefaultTemplate; - //Ensure unique name on the same level + // ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); - //Ensure that strings don't contain characters that are invalid in XML + // ensure that strings don't contain characters that are invalid in xml + // fixme - do we really want to keep doing this here? entity.SanitizeEntityPropertiesForXmlStorage(); - var factory = new ContentFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); + // create the dto + var dto = ContentFactory.BuildDto(entity); - //NOTE Should the logic below have some kind of fallback for empty parent ids ? - //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); + // derive path and level from parent + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetParentNode", tsql => + tsql.Select(x => x.NodeId).Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + ); + var parent = Database.Fetch(template.Sql(entity.ParentId)).First(); var level = parent.Level + 1; - var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentId = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - var sortOrder = maxSortOrder + 1; - //Create the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + // get sort order + var sortOrder = GetNewChildSortOrder(entity.ParentId, 0); + + // persist the node dto + var nodeDto = dto.ContentDto.NodeDto; nodeDto.Path = parent.Path; - nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); + nodeDto.Level = Convert.ToInt16(level); nodeDto.SortOrder = sortOrder; - // note: - // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, - // but I cannot figure out what was the point, as the node should obviously be new if - // we reach that point - removed. - // see if there's a reserved identifier for this unique id - var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservation); - var id = Database.ExecuteScalar(sql); + // and then either update or insert the node dto + template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetReservedId", tsql => + tsql.Select(x => x.NodeId).Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) + ); + var id = Database.ExecuteScalar(template.Sql(nodeDto.UniqueId)); // fixme can we mix named & non-named? if (id > 0) { nodeDto.NodeId = id; + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); Database.Update(nodeDto); } else { Database.Insert(nodeDto); + + // update path, now that we have an id + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); + Database.Update(nodeDto); } - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - nodeDto.ValidatePathWithException(); - Database.Update(nodeDto); - - //Update entity with correct values - entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set + // update entity + entity.Id = nodeDto.NodeId; entity.Path = nodeDto.Path; entity.SortOrder = sortOrder; entity.Level = level; - //Create the Content specific data - cmsContent - var contentDto = dto.ContentVersionDto.ContentDto; + // persist the content dto + var contentDto = dto.ContentDto; contentDto.NodeId = nodeDto.NodeId; Database.Insert(contentDto); - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - var contentVersionDto = dto.ContentVersionDto; + // persist the content version dto + // assumes a new version id and version date (modified date) has been set + var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto; // fixme version id etc? contentVersionDto.NodeId = nodeDto.NodeId; + contentVersionDto.Current = true; Database.Insert(contentVersionDto); - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - dto.NodeId = nodeDto.NodeId; + // persist the document version dto + var documentVersionDto = dto.DocumentVersionDto; + documentVersionDto.Id = contentVersionDto.Id; // fixme do we want this? + Database.Insert(documentVersionDto); + + // persist the document dto + dto.NodeId = nodeDto.NodeId; // fixme version id etc? Database.Insert(dto); - //Create the PropertyData for this version - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties + // persist the property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.Id, entity.Version, entity.Properties).ToArray(); foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } + Database.Insert(propertyDataDto); - //Update Properties with its newly set Id + // assign ids to properties, using propertyTypeId as a key + var xids = propertyDataDtos.ToDictionary(x => x.PropertyTypeId, x => x.Id); foreach (var property in entity.Properties) - property.Id = keyDictionary[property.PropertyTypeId]; + property.Id = xids[property.PropertyTypeId]; - //lastly, check if we are a creating a published version , then update the tags table + // if published, set tags accordingly if (entity.Published) UpdateEntityTags(entity, _tagRepository); // published => update published version infos, else leave it blank if (entity.Published) { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - VersionDate = dto.UpdateDate, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content) entity).PublishedVersionGuid = dto.VersionId; + // fixme anything else we should do? ((Content) entity).PublishedDate = dto.UpdateDate; } @@ -425,7 +322,7 @@ namespace Umbraco.Core.Persistence.Repositories var publishedState = content.PublishedState; var publishedStateChanged = publishedState == PublishedState.Publishing || publishedState == PublishedState.Unpublishing; - //check if we need to make any database changes at all + // check if we need to make any database changes at all if (entity.RequiresSaving(publishedState) == false) { entity.ResetDirtyProperties(); @@ -436,120 +333,110 @@ namespace Umbraco.Core.Persistence.Repositories var requiresNewVersion = entity.RequiresNewVersion(publishedState); if (requiresNewVersion) { - //Updates Modified date and Version Guid + // drop all draft infos for the current version, won't need it anymore + Database.Delete("WHERE nodeId=@nodeId AND versionId=@versionId AND published=@published", + new { nodeId = content.Id, versionId = content.Version, published = false }); + + // current version is not current anymore + Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.ContentVersion)} SET current=0 WHERE nodeId=@nodeId AND versionId=@versionId", + new { nodeId = content.Id, versionId = content.Version }); + + // resets identifiers ie get a new version id content.UpdatingEntity(); } else { - if (entity.IsPropertyDirty("UpdateDate") == false || entity.UpdateDate == default(DateTime)) + // just bump current version's update date + if (entity.IsPropertyDirty("UpdateDate") == false || entity.UpdateDate == default) entity.UpdateDate = DateTime.Now; } - //Ensure unique name on the same level + // ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - //Ensure that strings don't contain characters that are invalid in XML + // ensure that strings don't contain characters that are invalid in xml + // fixme - do we really want to keep doing this here? entity.SanitizeEntityPropertiesForXmlStorage(); - //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed + // if parent has changed, get path, level and sort order if (entity.IsPropertyDirty("ParentId")) { - var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetParentNode", tsql => + tsql.Select(x => x.NodeId).Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + ); + var parent = Database.Fetch(template.Sql(entity.ParentId)).First(); + entity.Path = string.Concat(parent.Path, ",", entity.Id); entity.Level = parent.Level + 1; - entity.SortOrder = NextChildSortOrder(entity.ParentId); - - //Question: If we move a node, should we update permissions to inherit from the new parent if the parent has permissions assigned? - // if we do that, then we'd need to propogate permissions all the way downward which might not be ideal for many people. - // Gonna just leave it as is for now, and not re-propogate permissions. + entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0); } - var factory = new ContentFactory(NodeObjectTypeId, entity.Id); - //Look up Content entry to get Primary for updating the DTO - var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { /*Id =*/ entity.Id }); - factory.SetPrimaryKey(contentDto.Id); - var dto = factory.BuildDto(entity); + // create the dto + var dto = ContentFactory.BuildDto(entity); - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + // update the node dto + var nodeDto = dto.ContentDto.NodeDto; nodeDto.ValidatePathWithException(); - var unused = Database.Update(nodeDto); + Database.Update(nodeDto); - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != entity.ContentTypeId) + // update the content dto - only if the content type has changed + var origContentDto = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.NodeId == entity.Id)).FirstOrDefault(); + if (origContentDto.ContentTypeId != entity.ContentTypeId) { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentVersionDto.ContentDto; - Database.Update(newContentDto); + dto.ContentDto.Id = origContentDto.Id; // fixme - annoying, needed? + Database.Update(dto.ContentDto); } //If Published state has changed then previous versions should have their publish state reset. //If state has been changed to unpublished the previous versions publish state should also be reset. //if (((ICanBeDirty)entity).IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished)) if (entity.RequiresClearPublishedFlag(publishedState, requiresNewVersion)) - ClearPublishedFlag(entity); + ClearPublishedFlag(entity); // FIXME OUCH NO //Look up (newest) entries by id in cmsDocument table to set newest = false - ClearNewestFlag(entity); + ClearNewestFlag(entity); // FIXME OUCH NO - var contentVersionDto = dto.ContentVersionDto; + // insert or update the content & document version dtos + var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto; // fixme version id etc? + contentVersionDto.Current = true; + var documentVersionDto = dto.DocumentVersionDto; if (requiresNewVersion) { - //Create a new version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set + // assumes a new version id and date (modified date) have been set Database.Insert(contentVersionDto); - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - Database.Insert(dto); + Database.Insert(documentVersionDto); } else { - //In order to update the ContentVersion we need to retrieve its primary key id - var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); - - if (contentVerDto != null) - { - contentVersionDto.Id = contentVerDto.Id; - Database.Update(contentVersionDto); - } - - Database.Update(dto); + // fixme this pk thing is annoying + var id = Database.ExecuteScalar(SqlContext.Sql().Select(x => x.Id).From().Where(x => x.VersionId == entity.Version)); + contentVersionDto.Id = id; + Database.Update(contentVersionDto); + documentVersionDto.Id = id; + Database.Update(documentVersionDto); } - //Create the PropertyData for this version - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); + // update the document dto + Database.Update(dto); - //Add Properties + // update the property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.Id, entity.Version, entity.Properties).ToArray(); foreach (var propertyDataDto in propertyDataDtos) { if (requiresNewVersion == false && propertyDataDto.Id > 0) - { Database.Update(propertyDataDto); - } else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } + Database.Insert(propertyDataDto); } - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in entity.Properties) - { - if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; + // assign ids to properties, using propertyTypeId as a key + var xids = propertyDataDtos.ToDictionary(x => x.PropertyTypeId, x => x.Id); + foreach (var property in entity.Properties) + property.Id = xids[property.PropertyTypeId]; - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - // tags: - if (HasTagProperty(entity)) + // update tags + if (HasTagProperty(entity)) // fixme what-if it had and now has not? { - // if path-published, update tags, else clear tags switch (content.PublishedState) { case PublishedState.Publishing: @@ -575,54 +462,36 @@ namespace Umbraco.Core.Persistence.Repositories } } - // published => update published version infos, - // else if unpublished then clear published version infos - // else leave unchanged - if (entity.Published) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - VersionDate = dto.UpdateDate, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - content.PublishedVersionGuid = dto.VersionId; - content.PublishedDate = dto.UpdateDate; - } - else if (publishedStateChanged) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = default(Guid), - VersionDate = default (DateTime), - Newest = false, - NodeId = dto.NodeId, - Published = false - }; - content.PublishedVersionGuid = default(Guid); - content.PublishedDate = dto.UpdateDate; - } - + content.PublishedDate = dto.UpdateDate; OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); entity.ResetDirtyProperties(); } - private int NextChildSortOrder(int parentId) + protected override void PersistDeletedItem(IContent entity) { - var maxSortOrder = - Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = parentId, NodeObjectType = NodeObjectTypeId }); - return maxSortOrder + 1; + // raise event first else potential FK issues + OnUowRemovingEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); + + //We need to clear out all access rules but we need to do this in a manual way since + // nothing in that table is joined to a content id + var subQuery = SqlContext.Sql() + .Select(x => x.AccessId) + .From() + .InnerJoin() + .On(left => left.AccessId, right => right.Id) + .Where(dto => dto.NodeId == entity.Id); + Database.Execute(SqlContext.SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); + + //now let the normal delete clauses take care of everything else + base.PersistDeletedItem(entity); } #endregion - #region Implementation of IContentRepository + #region Content Repository + // fixme could we at least document what's that supposed to do? public IEnumerable GetByPublishedVersion(IQuery query) { // we WANT to return contents in top-down order, ie parents should come before children @@ -635,7 +504,7 @@ namespace Umbraco.Core.Persistence.Repositories .OrderBy(x => x.Level) .OrderBy(x => x.SortOrder); - return MapQueryDtos(Database.Fetch(sql), true, many: true); + return MapDtosToContent(Database.Fetch(sql), true); } public int CountPublished(string contentTypeAlias = null) @@ -710,6 +579,7 @@ namespace Umbraco.Core.Persistence.Repositories PermissionRepository.AddOrUpdate(permission); } + // fixme must refactor entirely, will not work! /// /// Gets paged content results /// @@ -736,7 +606,7 @@ namespace Umbraco.Core.Persistence.Repositories } return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - x => MapQueryDtos(x, many: true), + x => MapDtosToContent(x), orderBy, orderDirection, orderBySystemField, "cmsDocument", filterSql); } @@ -765,13 +635,14 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region IRecycleBinRepository members + #region Recycle Bin protected override int RecycleBinId => Constants.System.RecycleBinContent; #endregion - #region Read Repository implementation for GUID keys + #region Read Repository implementation for Guid keys + public IContent Get(Guid id) { return _contentByGuidReadRepository.Get(id); @@ -804,72 +675,63 @@ namespace Umbraco.Core.Persistence.Repositories _outerRepo = outerRepo; } + protected override Guid NodeObjectTypeId => _outerRepo.NodeObjectTypeId; + protected override IContent PerformGet(Guid id) { var sql = _outerRepo.GetBaseQuery(QueryType.Single) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate); + .Where(x => x.UniqueId == id); var dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); if (dto == null) return null; - var content = _outerRepo.CreateContentFromDto(dto, dto.ContentVersionDto.VersionId); + var content = _outerRepo.MapDtoToContent(dto); return content; } protected override IEnumerable PerformGetAll(params Guid[] ids) { - Sql Translate(Sql s) - { - if (ids.Any()) - { - s.Where("umbracoNode.uniqueID in (@ids)", new { ids }); - } - //we only want the newest ones with this method - s.Where(x => x.Newest); - return s; - } + var sql = _outerRepo.GetBaseQuery(QueryType.Many); + if (ids.Length > 0) + sql.WhereIn(x => x.UniqueId, ids); - var sql = Translate(GetBaseQuery(false)); - return _outerRepo.MapQueryDtos(Database.Fetch(sql), many: true); + return _outerRepo.MapDtosToContent(Database.Fetch(sql)); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new WontImplementException(); + } + + protected override IEnumerable GetDeleteClauses() + { + throw new WontImplementException(); + } + + protected override void PersistNewItem(IContent entity) + { + throw new WontImplementException(); + } + + protected override void PersistUpdatedItem(IContent entity) + { + throw new WontImplementException(); } protected override Sql GetBaseQuery(bool isCount) { - return _outerRepo.GetBaseQuery(isCount); + throw new WontImplementException(); } protected override string GetBaseWhereClause() { - return "umbracoNode.uniqueID = @Id"; + throw new WontImplementException(); } - - protected override Guid NodeObjectTypeId => _outerRepo.NodeObjectTypeId; - - #region Not needed to implement - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - protected override void PersistNewItem(IContent entity) - { - throw new NotImplementedException(); - } - protected override void PersistUpdatedItem(IContent entity) - { - throw new NotImplementedException(); - } - #endregion } + #endregion protected override string GetDatabaseFieldNameForOrderBy(string orderBy) @@ -881,10 +743,11 @@ namespace Umbraco.Core.Persistence.Repositories switch (orderBy.ToUpperInvariant()) { case "UPDATER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return GetDatabaseFieldNameForOrderBy("cmsDocument", "documentUser"); + // fixme This isn't going to work very nicely because it's going to order by ID, not by letter + return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "writerUserId"); case "PUBLISHED": - return GetDatabaseFieldNameForOrderBy("cmsDocument", "published"); + // fixme is this still relevant? + return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "published"); case "CONTENTTYPEALIAS": throw new NotSupportedException("Don't know how to support ContentTypeAlias."); } @@ -892,160 +755,92 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetDatabaseFieldNameForOrderBy(orderBy); } - // "many" corresponds to 7.6 "includeAllVersions" - // fixme - we are not implementing the double-query thing for pagination from 7.6? - // - private IEnumerable MapQueryDtos(List dtos, bool withCache = false, bool many = false, bool allVersions = false) + // fixme - we are not implementing the double-query thing for pagination from 7.6, need to do it differently! + + private IEnumerable MapDtosToContent(List dtos, bool withCache = false) { - var temps = new List(); + var temps = new List>(); var contentTypes = new Dictionary(); var templateIds = new List(); - var newest = new Dictionary(); - var remove = allVersions ? null : new List(); - foreach (var dto in dtos) - { - if (dto.Newest == false) continue; - if (newest.TryGetValue(dto.NodeId, out var newestDto) == false) - { - newest[dto.NodeId] = dto; - continue; - } - if (dto.UpdateDate < newestDto.UpdateDate) - { - if (allVersions) dto.Newest = false; - else remove.Add(dto); - } - else - { - newest[dto.NodeId] = dto; - if (allVersions) newestDto.Newest = false; - else remove.Add(newestDto); - } - } - if (remove != null) - foreach (var removeDto in remove) - dtos.Remove(removeDto); - - // populate published data - in case of 'many' it's not there yet - if (many) - { - var roDtos = Database.FetchByGroups(dtos.Select(x => x.NodeId), 2000, batch - => SqlContext.Sql() - .Select() - .From() - .WhereIn(x => x.NodeId, batch) - .Where(x => x.Published)); - - // in case of data corruption we may have more than 1 "published" - cleanup - keep most recent - var publishedDtoIndex = new Dictionary(); - foreach (var roDto in roDtos) - { - if (publishedDtoIndex.TryGetValue(roDto.NodeId, out var ixDto) == false || ixDto.VersionDate < roDto.VersionDate) - publishedDtoIndex[roDto.NodeId] = roDto; - } - - // assign - foreach (var dto in dtos) - { - dto.DocumentPublishedReadOnlyDto = publishedDtoIndex.TryGetValue(dto.NodeId, out var d) - ? d - : new DocumentPublishedReadOnlyDto(); - } - } - - var content = new IContent[dtos.Count]; + var content = new Content[dtos.Count]; for (var i = 0; i < dtos.Count; i++) { var dto = dtos[i]; + var versionId = dto.DocumentVersionDto.ContentVersionDto.VersionId; if (withCache) { // if the cache contains the (proper version of the) item, use it var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - if (cached != null && cached.Version == dto.ContentVersionDto.VersionId) + if (cached != null && cached.Version == versionId) { - content[i] = cached; + content[i] = (Content) cached; // fixme should we just cache Content not IContent? continue; } } - // else, need to fetch from the database + // else, need to build it // get the content type - the repository is full cache *but* still deep-clones // whatever comes out of it, so use our own local index here to avoid this - if (contentTypes.TryGetValue(dto.ContentVersionDto.ContentDto.ContentTypeId, out var contentType) == false) - contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + var contentTypeId = dto.ContentDto.ContentTypeId; + if (contentTypes.TryGetValue(contentTypeId, out var contentType) == false) + contentTypes[contentTypeId] = contentType = _contentTypeRepository.Get(contentTypeId); - var c = content[i] = ContentFactory.BuildEntity(dto, contentType, dto.DocumentPublishedReadOnlyDto); + var c = content[i] = ContentFactory.BuildEntity(dto, contentType); // need template - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - templateIds.Add(dto.TemplateId.Value); + var templateId = dto.DocumentVersionDto.TemplateId; + if (templateId.HasValue && templateId.Value > 0) + templateIds.Add(templateId.Value); // need properties - temps.Add(new TempContent( - dto.NodeId, - dto.VersionId, - dto.ContentVersionDto.VersionDate, - dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - contentType, - c - ) { TemplateId = dto.TemplateId }); + temps.Add(new TempContent(dto.NodeId, versionId, contentType, c) + { + TemplateId = dto.DocumentVersionDto.TemplateId + }); } - // load all required templates in 1 query and index + // load all required templates in 1 query, and index var templates = _templateRepository.GetAll(templateIds.ToArray()) .ToDictionary(x => x.Id, x => x); - // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(temps); + // load all properties for all documents from database in 1 query - indexed by version id + var properties = GetPropertyCollections(temps); - // assign + // assign templates and properties foreach (var temp in temps) { // complete the item if (temp.TemplateId.HasValue && templates.TryGetValue(temp.TemplateId.Value, out var template)) - ((Content) temp.Content).Template = template; - temp.Content.Properties = propertyData[temp.Version]; + temp.Content.Template = template; + temp.Content.Properties = properties[temp.VersionId]; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity) temp.Content).ResetDirtyProperties(false); + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); } return content; } - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, Guid versionId) + private IContent MapDtoToContent(DocumentDto dto) { - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + var contentType = _contentTypeRepository.Get(dto.ContentDto.ContentTypeId); + var content = ContentFactory.BuildEntity(dto, contentType); - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); + // get template + if (dto.DocumentVersionDto.TemplateId.HasValue && dto.DocumentVersionDto.TemplateId.Value > 0) + content.Template = _templateRepository.Get(dto.DocumentVersionDto.TemplateId.Value); - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - { - content.Template = _templateRepository.Get(dto.TemplateId.Value); - } + // get properties - indexed by version id + var temp = new TempContent(dto.NodeId, dto.DocumentVersionDto.ContentVersionDto.VersionId, contentType); + var properties = GetPropertyCollections(new List> { temp }); + content.Properties = properties[dto.DocumentVersionDto.ContentVersionDto.VersionId]; - var docDef = new TempContent(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); - - var properties = GetPropertyCollection(new List { docDef }); - - content.Properties = properties[versionId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); + // clear dirty props on init - U4-1943 + content.ResetDirtyProperties(false); return content; } @@ -1054,10 +849,23 @@ namespace Umbraco.Core.Persistence.Repositories if (EnsureUniqueNaming == false) return nodeName; - var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", - new { objectType = NodeObjectTypeId, parentId }); + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.EnsureUniqueNodeName", tsql => tsql + .Select(x => x.NodeId, x => x.Text) + .From() + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))); + + var sql = template.Sql(NodeObjectTypeId, parentId); + var names = Database.Fetch(sql); return SimilarNodeName.GetUniqueName(names, id, nodeName); } + + private int GetNewChildSortOrder(int parentId, int first) + { + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetSortOrder", tsql => + tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From().Where(x => x.NodeId == SqlTemplate.Arg("parentId") && x.NodeObjectType == NodeObjectTypeId) + ); + return Database.ExecuteScalar(template.Sql(parentId)) + 1; // fixme can we mix named & non-named? + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs index b412088ff8..9c83b929f2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs @@ -30,9 +30,9 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Gets a list of all versions for an ordered so latest is first /// - /// Id of the to retrieve versions from + /// Id of the to retrieve versions from /// An enumerable list of the same object with different versions - IEnumerable GetAllVersions(int id); + IEnumerable GetAllVersions(int nodeId); /// /// Gets a list of all version Ids for the given content item @@ -58,9 +58,9 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Deletes versions from an object prior to a specific date. /// - /// Id of the object to delete versions from + /// Id of the object to delete versions from /// Latest version date - void DeleteVersions(int id, DateTime versionDate); + void DeleteVersions(int nodeId, DateTime versionDate); /// /// Gets paged content results diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index cc5c1444e7..ce7c37c677 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -37,95 +38,101 @@ namespace Umbraco.Core.Persistence.Repositories protected override MediaRepository This => this; - public bool EnsureUniqueNaming { get; } + public bool EnsureUniqueNaming { get; set; } - #region Overrides of RepositoryBase + #region Repository Base + + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Media; protected override IMedia PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + var sql = GetBaseQuery(QueryType.Single) + .Where(x => x.NodeId == id) + .SelectTop(1); - var dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateMediaFromDto(dto, dto.VersionId); - - return content; + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null + ? null + : MapDtoToContent(dto); } protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); - } + var sql = GetBaseQuery(QueryType.Many); - return MapQueryDtos(Database.Fetch(sql)); + if (ids.Any()) + sql.WhereIn(x => x.NodeId, ids); + + return MapDtosToContent(Database.Fetch(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(false); + var sqlClause = GetBaseQuery(QueryType.Many); + var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .OrderBy(x => x.SortOrder); + var sql = translator.Translate(); - return MapQueryDtos(Database.Fetch(sql)); - } + sql // fixme why? + .OrderBy(x => x.Level) + .OrderBy(x => x.SortOrder); - #endregion - - #region Overrides of NPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - return GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); + return MapDtosToContent(Database.Fetch(sql)); } protected override Sql GetBaseQuery(QueryType queryType) { - var sql = Sql(); + return GetBaseQuery(queryType, true); + } - switch (queryType) + protected virtual Sql GetBaseQuery(QueryType queryType, bool current) + { + var sql = SqlContext.Sql(); + + switch (queryType) // FIXME pretend we still need these queries for now { case QueryType.Count: sql = sql.SelectCount(); break; case QueryType.Ids: - sql = sql.Select(x => x.NodeId); + sql = sql.Select(x => x.NodeId); break; - case QueryType.Many: case QueryType.Single: - sql = sql.Select(r => - r.Select(x => x.ContentDto, r1 => - r1.Select(x => x.NodeDto))); + case QueryType.Many: + sql = sql.Select(r => + r.Select(x => x.NodeDto) + .Select(x => x.ContentVersionDto)); break; } sql - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId); + + + sql.Where(x => x.NodeObjectType == NodeObjectTypeId); + + if (current) + sql.Where(x => x.Current); // always get the current version return sql; } - protected override string GetBaseWhereClause() + // fixme - move that one up to Versionable! or better: kill it! + protected override Sql GetBaseQuery(bool isCount) + { + return GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); + } + + protected override string GetBaseWhereClause() // fixme - can we kill / refactor this? { return "umbracoNode.id = @Id"; } protected override IEnumerable GetDeleteClauses() { - var list = new List + var list = new List // fixme table names, escape everything, etc { "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", @@ -135,42 +142,36 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM umbracoRelation WHERE parentId = @Id", "DELETE FROM umbracoRelation WHERE childId = @Id", "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsDocument WHERE nodeId = @Id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Document + " WHERE nodeId = @Id", "DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE ContentId = @Id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; return list; } - protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Media; - #endregion - #region Overrides of VersionableRepositoryBase + #region Versions - public override IEnumerable GetAllVersions(int id) + public override IEnumerable GetAllVersions(int nodeId) { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Id = id }) - .OrderByDescending(x => x.VersionDate); + var sql = GetBaseQuery(QueryType.Many, false) + .Where(x => x.NodeId == nodeId) + .OrderByDescending(x => x.Current) + .AndByDescending(x => x.VersionDate); - return MapQueryDtos(Database.Fetch(sql), true); + return MapDtosToContent(Database.Fetch(sql), true); } public override IMedia GetByVersion(Guid versionId) { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); + var sql = GetBaseQuery(QueryType.Single) + .Where(x => x.VersionId == versionId); - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - return CreateMediaFromDto(dto, versionId); + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? null : MapDtoToContent(dto); } public IMedia GetMediaByPath(string mediaPath) @@ -223,93 +224,89 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Unit of Work Implementation + #region Persist protected override void PersistNewItem(IMedia entity) { - ((Models.Media)entity).AddingEntity(); + ((Models.Media) entity).AddingEntity(); - //Ensure unique name on the same level + // ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); - //Ensure that strings don't contain characters that are invalid in XML + // ensure that strings don't contain characters that are invalid in xml + // fixme - do we really want to keep doing this here? entity.SanitizeEntityPropertiesForXmlStorage(); - var factory = new MediaFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); + // create the dto + var dto = MediaFactory.BuildDto(entity); - //NOTE Should the logic below have some kind of fallback for empty parent ids ? - //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); + // derive path and level from parent + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetParentNode", tsql => + tsql.Select(x => x.NodeId).Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + ); + var parent = Database.Fetch(template.Sql(entity.ParentId)).First(); var level = parent.Level + 1; - var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentId = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - var sortOrder = maxSortOrder + 1; - //Create the (base) node data - umbracoNode - var nodeDto = dto.ContentDto.NodeDto; + // get sort order + var sortOrder = GetNewChildSortOrder(entity.ParentId, 0); + + // persist the node dto + var nodeDto = dto.NodeDto; nodeDto.Path = parent.Path; - nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); + nodeDto.Level = Convert.ToInt16(level); nodeDto.SortOrder = sortOrder; - // note: - // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, - // but I cannot figure out what was the point, as the node should obviously be new if - // we reach that point - removed. - // see if there's a reserved identifier for this unique id - var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservation); - var id = Database.ExecuteScalar(sql); + // and then either update or insert the node dto + template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetReservedId", tsql => + tsql.Select(x => x.NodeId).Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) + ); + var id = Database.ExecuteScalar(template.Sql(nodeDto.UniqueId)); // fixme can we mix named & non-named? if (id > 0) { nodeDto.NodeId = id; + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); Database.Update(nodeDto); } else { Database.Insert(nodeDto); + + // update path, now that we have an id + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); + Database.Update(nodeDto); } - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - nodeDto.ValidatePathWithException(); - Database.Update(nodeDto); - - //Update entity with correct values - entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set + // update entity + entity.Id = nodeDto.NodeId; entity.Path = nodeDto.Path; entity.SortOrder = sortOrder; entity.Level = level; - //Create the Content specific data - cmsContent - var contentDto = dto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set + // persist the content dto dto.NodeId = nodeDto.NodeId; Database.Insert(dto); - //Create the PropertyData for this version - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); + // persist the content version dto + // assumes a new version id and version date (modified date) has been set + var contentVersionDto = dto.ContentVersionDto; // fixme version id etc? + contentVersionDto.NodeId = nodeDto.NodeId; + contentVersionDto.Current = true; + Database.Insert(contentVersionDto); - //Add Properties + // persist the property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.Id, entity.Version, entity.Properties).ToArray(); foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } + Database.Insert(propertyDataDto); - //Update Properties with its newly set Id + // assign ids to properties, using propertyTypeId as a key + var xids = propertyDataDtos.ToDictionary(x => x.PropertyTypeId, x => x.Id); foreach (var property in entity.Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } + property.Id = xids[property.PropertyTypeId]; + // set tags UpdateEntityTags(entity, _tagRepository); OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); @@ -322,79 +319,64 @@ namespace Umbraco.Core.Persistence.Repositories //Updates Modified date ((Models.Media)entity).UpdatingEntity(); - //Ensure unique name on the same level + // ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - //Ensure that strings don't contain characters that are invalid in XML + // ensure that strings don't contain characters that are invalid in xml + // fixme - do we really want to keep doing this here? entity.SanitizeEntityPropertiesForXmlStorage(); - //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed + // if parent has changed, get path, level and sort order if (entity.IsPropertyDirty("ParentId")) { - var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetParentNode", tsql => + tsql.Select(x => x.NodeId).Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + ); + var parent = Database.Fetch(template.Sql(entity.ParentId)).First(); + entity.Path = string.Concat(parent.Path, ",", entity.Id); entity.Level = parent.Level + 1; - var maxSortOrder = - Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - entity.SortOrder = maxSortOrder + 1; + entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0); } - var factory = new MediaFactory(NodeObjectTypeId, entity.Id); - //Look up Content entry to get Primary for updating the DTO - var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { /*Id =*/ entity.Id }); - factory.SetPrimaryKey(contentDto.Id); - var dto = factory.BuildDto(entity); + // create the dto + var dto = MediaFactory.BuildDto(entity); - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentDto.NodeDto; + // update the node dto + var nodeDto = dto.NodeDto; nodeDto.ValidatePathWithException(); - var unused = Database.Update(nodeDto); + Database.Update(nodeDto); - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != entity.ContentTypeId) + // update the content dto - only if the content type has changed + var origContentDto = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.NodeId == entity.Id)).FirstOrDefault(); + if (origContentDto.ContentTypeId != entity.ContentTypeId) { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentDto; - Database.Update(newContentDto); + dto.Id = origContentDto.Id; // fixme - annoying, needed? + Database.Update(dto); } - //In order to update the ContentVersion we need to retrieve its primary key id - var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); - dto.Id = contentVerDto.Id; - //Updates the current version - cmsContentVersion - //Assumes a Version guid exists and Version date (modified date) has been set/updated - Database.Update(dto); + // update the content version dto + var contentVersionDto = dto.ContentVersionDto; // fixme version id etc? + contentVersionDto.Current = true; + // fixme this pk thing is annoying + var id = Database.ExecuteScalar(SqlContext.Sql().Select(x => x.Id).From().Where(x => x.VersionId == entity.Version)); + contentVersionDto.Id = id; + Database.Update(contentVersionDto); - //Create the PropertyData for this version - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties + // update the property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.Id, entity.Version, entity.Properties).ToArray(); foreach (var propertyDataDto in propertyDataDtos) { if (propertyDataDto.Id > 0) - { Database.Update(propertyDataDto); - } else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } + Database.Insert(propertyDataDto); } - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in entity.Properties) - { - if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; - property.Id = keyDictionary[property.PropertyTypeId]; - } - } + // assign ids to properties, using propertyTypeId as a key + var xids = propertyDataDtos.ToDictionary(x => x.PropertyTypeId, x => x.Id); + foreach (var property in entity.Properties) + property.Id = xids[property.PropertyTypeId]; UpdateEntityTags(entity, _tagRepository); @@ -412,13 +394,14 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region IRecycleBinRepository members + #region Recycle Bin protected override int RecycleBinId => Constants.System.RecycleBinMedia; #endregion - #region Read Repository implementation for GUID keys + #region Read Repository implementation for Guid keys + public IMedia Get(Guid id) { return _mediaByGuidReadRepository.Get(id); @@ -445,72 +428,69 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly MediaRepository _outerRepo; - public MediaByGuidReadRepository(MediaRepository outerRepo, - IScopeUnitOfWork work, CacheHelper cache, ILogger logger) + public MediaByGuidReadRepository(MediaRepository outerRepo, IScopeUnitOfWork work, CacheHelper cache, ILogger logger) : base(work, cache, logger) { _outerRepo = outerRepo; } + protected override Guid NodeObjectTypeId => _outerRepo.NodeObjectTypeId; + protected override IMedia PerformGet(Guid id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + var sql = _outerRepo.GetBaseQuery(QueryType.Single) + .Where(x => x.UniqueId == id); - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + var dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); if (dto == null) return null; - var content = _outerRepo.CreateMediaFromDto(dto, dto.VersionId); + var content = _outerRepo.MapDtoToContent(dto); return content; } protected override IEnumerable PerformGetAll(params Guid[] ids) { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.uniqueID in (@ids)", new { ids = ids }); - } + var sql = _outerRepo.GetBaseQuery(QueryType.Many); + if (ids.Length > 0) + sql.WhereIn(x => x.UniqueId, ids); - return _outerRepo.MapQueryDtos(Database.Fetch(sql)); + return _outerRepo.MapDtosToContent(Database.Fetch(sql)); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new WontImplementException(); + } + + protected override IEnumerable GetDeleteClauses() + { + throw new WontImplementException(); + } + + protected override void PersistNewItem(IMedia entity) + { + throw new WontImplementException(); + } + + protected override void PersistUpdatedItem(IMedia entity) + { + throw new WontImplementException(); } protected override Sql GetBaseQuery(bool isCount) { - return _outerRepo.GetBaseQuery(isCount); + throw new WontImplementException(); } protected override string GetBaseWhereClause() { - return "umbracoNode.uniqueID = @Id"; + throw new WontImplementException(); } - - protected override Guid NodeObjectTypeId => _outerRepo.NodeObjectTypeId; - - #region Not needed to implement - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - protected override void PersistNewItem(IMedia entity) - { - throw new NotImplementedException(); - } - protected override void PersistUpdatedItem(IMedia entity) - { - throw new NotImplementedException(); - } - #endregion } + #endregion /// @@ -538,85 +518,74 @@ namespace Umbraco.Core.Persistence.Repositories } } - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - x => MapQueryDtos(x), orderBy, orderDirection, orderBySystemField, "cmsContentVersion", + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + x => MapDtosToContent(x), orderBy, orderDirection, orderBySystemField, "cmsContentVersion", filterSql); } - private IEnumerable MapQueryDtos(List dtos, bool withCache = false) + private IEnumerable MapDtosToContent(List dtos, bool withCache = false) { - var content = new IMedia[dtos.Count]; - var temps = new List(); + var temps = new List>(); var contentTypes = new Dictionary(); + var content = new Models.Media[dtos.Count]; for (var i = 0; i < dtos.Count; i++) { var dto = dtos[i]; + var versionId = dto.ContentVersionDto.VersionId; if (withCache) { // if the cache contains the (proper version of the) item, use it var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - if (cached != null) + if (cached != null && cached.Version == versionId) { - content[i] = cached; + content[i] = (Models.Media) cached; // fixme should we just cache Media not IMedia? continue; } } - // else, need to fetch from the database + // else, need to build it // get the content type - the repository is full cache *but* still deep-clones // whatever comes out of it, so use our own local index here to avoid this - if (contentTypes.TryGetValue(dto.ContentDto.ContentTypeId, out IMediaType contentType) == false) - contentTypes[dto.ContentDto.ContentTypeId] = contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + var contentTypeId = dto.ContentTypeId; + if (contentTypes.TryGetValue(contentTypeId, out IMediaType contentType) == false) + contentTypes[contentTypeId] = contentType = _mediaTypeRepository.Get(contentTypeId); var c = content[i] = MediaFactory.BuildEntity(dto, contentType); // need properties - temps.Add(new TempContent( - dto.NodeId, - dto.VersionId, - dto.VersionDate, - dto.ContentDto.NodeDto.CreateDate, - contentType, - c - )); + temps.Add(new TempContent(dto.NodeId, versionId, contentType, c)); } - // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(temps); + // load all properties for all documents from database in 1 query - indexed by version id + var properties = GetPropertyCollections(temps); - // assign + // assign properties foreach (var temp in temps) { - temp.Content.Properties = propertyData[temp.Version]; + temp.Content.Properties = properties[temp.VersionId]; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity) temp.Content).ResetDirtyProperties(false); + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); } return content; } - /// - /// Private method to create a media object from a ContentDto - /// - /// - /// - /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId) + private IMedia MapDtoToContent(ContentDto dto) { - var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + var contentType = _mediaTypeRepository.Get(dto.ContentTypeId); var media = MediaFactory.BuildEntity(dto, contentType); - var temp = new TempContent(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); - var properties = GetPropertyCollection(new List { temp }); - media.Properties = properties[versionId]; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); + // get properties - indexed by version id + var temp = new TempContent(dto.NodeId, dto.ContentVersionDto.VersionId, contentType); + var properties = GetPropertyCollections(new List> { temp }); + media.Properties = properties[dto.ContentVersionDto.VersionId]; + + // clear dirty props on init - U4-1943 + media.ResetDirtyProperties(false); return media; } @@ -625,10 +594,23 @@ namespace Umbraco.Core.Persistence.Repositories if (EnsureUniqueNaming == false) return nodeName; - var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", - new { objectType = NodeObjectTypeId, parentId }); + var template = SqlContext.Templates.Get("Umbraco.Core.MediaRepository.EnsureUniqueNodeName", tsql => tsql + .Select(x => x.NodeId, x => x.Text) + .From() + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))); + + var sql = template.Sql(NodeObjectTypeId, parentId); + var names = Database.Fetch(sql); return SimilarNodeName.GetUniqueName(names, id, nodeName); } + + private int GetNewChildSortOrder(int parentId, int first) + { + var template = SqlContext.Templates.Get("Umbraco.Core.MediaRepository.GetSortOrder", tsql => + tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From().Where(x => x.NodeId == SqlTemplate.Arg("parentId") && x.NodeObjectType == NodeObjectTypeId) + ); + return Database.ExecuteScalar(template.Sql(parentId)) + 1; // fixme can we mix named & non-named? + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index a51e8eb174..adec5cc893 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -35,41 +35,37 @@ namespace Umbraco.Core.Persistence.Repositories protected override MemberRepository This => this; - #region Overrides of RepositoryBase + #region Repository Base + + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Member; protected override IMember PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId); - - return content; + var sql = GetBaseQuery(QueryType.Single) + .Where(x => x.NodeId == id) + .SelectTop(1); + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null + ? null + : MapDtoToContent(dto); } protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false); + var sql = GetBaseQuery(QueryType.Many); + if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); - } - - return MapQueryDtos(Database.Fetch(sql)); + sql.WhereIn(x => x.NodeId, ids); + return MapDtosToContent(Database.Fetch(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) { var baseQuery = GetBaseQuery(false); + // fixme why is this different from content/media?! //check if the query is based on properties or not var wheres = query.GetWhereClauses(); @@ -84,7 +80,7 @@ namespace Umbraco.Core.Persistence.Repositories baseQuery.Append("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments) .OrderBy(x => x.SortOrder); - return MapQueryDtos(Database.Fetch(baseQuery)); + return MapDtosToContent(Database.Fetch(baseQuery)); } else { @@ -92,63 +88,68 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate() .OrderBy(x => x.SortOrder); - return MapQueryDtos(Database.Fetch(sql)); + return MapDtosToContent(Database.Fetch(sql)); } } - #endregion - - #region Overrides of NPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - return GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); - } - protected override Sql GetBaseQuery(QueryType queryType) { - var sql = Sql(); + return GetBaseQuery(queryType, true); + } - switch (queryType) + protected virtual Sql GetBaseQuery(QueryType queryType, bool current) + { + var sql = SqlContext.Sql(); + + switch (queryType) // FIXME pretend we still need these queries for now { case QueryType.Count: sql = sql.SelectCount(); break; case QueryType.Ids: - sql = sql.Select("cmsMember.nodeId"); + sql = sql.Select(x => x.NodeId); break; - case QueryType.Many: case QueryType.Single: + case QueryType.Many: sql = sql.Select(r => - r.Select(x => x.ContentVersionDto, r1 => - r1.Select(x => x.ContentDto, r2 => - r2.Select(x => x.NodeDto)))); + r.Select(x => x.ContentVersionDto) + .Select(x => x.ContentDto, r1 => + r1.Select(x => x.NodeDto))); break; } sql .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + + // joining the type so we can do a query against the member type - not sure if this adds much overhead or not? // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content // types by default on the document and media repo's so we can query by content type there too. - .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId); + + sql.Where(x => x.NodeObjectType == NodeObjectTypeId); + + if (current) + sql.Where(x => x.Current); // always get the current version return sql; } - protected override string GetBaseWhereClause() + // fixme - move that one up to Versionable! or better: kill it! + protected override Sql GetBaseQuery(bool isCount) + { + return GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); + } + + protected override string GetBaseWhereClause() // fixme - can we kill / refactor this? { return "umbracoNode.id = @Id"; } + // fixme wtf? protected Sql GetNodeIdQueryWithPropertyData() { return Sql() @@ -178,7 +179,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE nodeId = @Id", "DELETE FROM cmsMember2MemberGroup WHERE Member = @Id", "DELETE FROM cmsMember WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE ContentId = @Id", "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" @@ -186,263 +187,27 @@ namespace Umbraco.Core.Persistence.Repositories return list; } - protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Member; - #endregion - #region Unit of Work Implementation + #region Versions - protected override void PersistNewItem(IMember entity) + public override IEnumerable GetAllVersions(int nodeId) { - ((Member)entity).AddingEntity(); + var sql = GetBaseQuery(QueryType.Many, false) + .Where(x => x.NodeId == nodeId) + .OrderByDescending(x => x.Current) + .AndByDescending(x => x.VersionDate); - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new MemberFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //NOTE Should the logic below have some kind of fallback for empty parent ids ? - //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); - var level = parent.Level + 1; - var sortOrder = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - - //Create the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - nodeDto.Path = parent.Path; - nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); - nodeDto.SortOrder = sortOrder; - var unused = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); - - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - Database.Update(nodeDto); - - //Update entity with correct values - entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set - entity.Path = nodeDto.Path; - entity.SortOrder = sortOrder; - entity.Level = level; - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentVersionDto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - dto.ContentVersionDto.NodeId = nodeDto.NodeId; - Database.Insert(dto.ContentVersionDto); - - //Create the first entry in cmsMember - dto.NodeId = nodeDto.NodeId; - - //if the password is empty, generate one with the special prefix - //this will hash the guid with a salt so should be nicely random - if (entity.RawPasswordValue.IsNullOrWhiteSpace()) - { - var aspHasher = new PasswordHasher(); - dto.Password = Constants.Security.EmptyPasswordPrefix + - aspHasher.HashPassword(Guid.NewGuid().ToString("N")); - //re-assign - entity.RawPasswordValue = dto.Password; - } - - Database.Insert(dto); - - //Create the PropertyData for this version - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - //Add Properties - // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type - // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. - var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); - var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //Update Properties with its newly set Id - foreach (var property in propsToPersist) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - - UpdateEntityTags(entity, _tagRepository); - - OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); - - ((Member)entity).ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IMember entity) - { - //Updates Modified date - ((Member)entity).UpdatingEntity(); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var dirtyEntity = (ICanBeDirty)entity; - - //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed - if (dirtyEntity.IsPropertyDirty("ParentId")) - { - var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); - entity.Path = string.Concat(parent.Path, ",", entity.Id); - entity.Level = parent.Level + 1; - var maxSortOrder = - Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - entity.SortOrder = maxSortOrder + 1; - } - - var factory = new MemberFactory(NodeObjectTypeId, entity.Id); - //Look up Content entry to get Primary for updating the DTO - var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { /*Id =*/ entity.Id }); - factory.SetPrimaryKey(contentDto.Id); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - var unused = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != ((Member)entity).ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentVersionDto.ContentDto; - Database.Update(newContentDto); - } - - //In order to update the ContentVersion we need to retrieve its primary key id - var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); - dto.ContentVersionDto.Id = contentVerDto.Id; - //Updates the current version - cmsContentVersion - //Assumes a Version guid exists and Version date (modified date) has been set/updated - Database.Update(dto.ContentVersionDto); - - //Updates the cmsMember entry if it has changed - - //NOTE: these cols are the REAL column names in the db - var changedCols = new List(); - - if (dirtyEntity.IsPropertyDirty("Email")) - { - changedCols.Add("Email"); - } - if (dirtyEntity.IsPropertyDirty("Username")) - { - changedCols.Add("LoginName"); - } - // DO NOT update the password if it has not changed or if it is null or empty - if (dirtyEntity.IsPropertyDirty("RawPasswordValue") && entity.RawPasswordValue.IsNullOrWhiteSpace() == false) - { - changedCols.Add("Password"); - } - //only update the changed cols - if (changedCols.Count > 0) - { - Database.Update(dto, changedCols); - } - - //TODO ContentType for the Member entity - - //Create the PropertyData for this version - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var keyDictionary = new Dictionary(); - - //Add Properties - // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type - // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. - var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); - - var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); - - foreach (var propertyDataDto in propertyDataDtos) - { - if (propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in ((Member)entity).Properties) - { - if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; - - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - UpdateEntityTags(entity, _tagRepository); - - OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); - - dirtyEntity.ResetDirtyProperties(); - } - - protected override void PersistDeletedItem(IMember entity) - { - // raise event first else potential FK issues - OnUowRemovingEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); - base.PersistDeletedItem(entity); - } - - #endregion - - #region Overrides of VersionableRepositoryBase - - public override IEnumerable GetAllVersions(int id) - { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Id = id }) - .OrderByDescending(x => x.VersionDate); - - return MapQueryDtos(Database.Fetch(sql), true); + return MapDtosToContent(Database.Fetch(sql), true); } public override IMember GetByVersion(Guid versionId) { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); + var sql = GetBaseQuery(QueryType.Single) + .Where(x => x.VersionId == versionId); var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - var member = MemberFactory.BuildEntity(dto, memberType); - - var properties = GetPropertyCollection(new List { new TempContent(dto.NodeId, dto.ContentVersionDto.VersionId, member.UpdateDate, member.CreateDate, memberType) }); - - member.Properties = properties[dto.ContentVersionDto.VersionId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); - return member; - + return dto == null ? null : MapDtoToContent(dto); } protected override void PerformDeleteVersion(int id, Guid versionId) @@ -456,6 +221,199 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + #region Persist + + protected override void PersistNewItem(IMember entity) + { + ((Member) entity).AddingEntity(); + + // ensure that strings don't contain characters that are invalid in xml + // fixme - do we really want to keep doing this here? + entity.SanitizeEntityPropertiesForXmlStorage(); + + // create the dto + var dto = MemberFactory.BuildDto(entity); + + // derive path and level from parent + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetParentNode", tsql => + tsql.Select(x => x.NodeId).Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + ); + var parent = Database.Fetch(template.Sql(entity.ParentId)).First(); + var level = parent.Level + 1; + + // get sort order + var sortOrder = GetNewChildSortOrder(entity.ParentId, 0); + + // persist the node dto + var nodeDto = dto.ContentDto.NodeDto; + nodeDto.Path = parent.Path; + nodeDto.Level = Convert.ToInt16(level); + nodeDto.SortOrder = sortOrder; + + // see if there's a reserved identifier for this unique id + // and then either update or insert the node dto + template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetReservedId", tsql => + tsql.Select(x => x.NodeId).Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) + ); + var id = Database.ExecuteScalar(template.Sql(nodeDto.UniqueId)); // fixme can we mix named & non-named? + if (id > 0) + { + nodeDto.NodeId = id; + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); + Database.Update(nodeDto); + } + else + { + Database.Insert(nodeDto); + + // update path, now that we have an id + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); + Database.Update(nodeDto); + } + + // update entity + entity.Id = nodeDto.NodeId; + entity.Path = nodeDto.Path; + entity.SortOrder = sortOrder; + entity.Level = level; + + // persist the content dto + var contentDto = dto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + // persist the content version dto + // assumes a new version id and version date (modified date) has been set + var contentVersionDto = dto.ContentVersionDto; // fixme version id etc? + contentVersionDto.NodeId = nodeDto.NodeId; + contentVersionDto.Current = true; + Database.Insert(contentVersionDto); + + // persist the member dto + dto.NodeId = nodeDto.NodeId; // fixme version id etc? + + // if the password is empty, generate one with the special prefix + // this will hash the guid with a salt so should be nicely random + if (entity.RawPasswordValue.IsNullOrWhiteSpace()) + { + var aspHasher = new PasswordHasher(); + dto.Password = Constants.Security.EmptyPasswordPrefix + aspHasher.HashPassword(Guid.NewGuid().ToString("N")); + entity.RawPasswordValue = dto.Password; + } + + Database.Insert(dto); + + // persist the property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.Id, entity.Version, entity.Properties).ToArray(); + foreach (var propertyDataDto in propertyDataDtos) + Database.Insert(propertyDataDto); + + // assign ids to properties, using propertyTypeId as a key + var xids = propertyDataDtos.ToDictionary(x => x.PropertyTypeId, x => x.Id); + foreach (var property in entity.Properties) + property.Id = xids[property.PropertyTypeId]; + + UpdateEntityTags(entity, _tagRepository); + + OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IMember entity) + { + //Updates Modified date + ((Member) entity).UpdatingEntity(); + + // ensure that strings don't contain characters that are invalid in xml + // fixme - do we really want to keep doing this here? + entity.SanitizeEntityPropertiesForXmlStorage(); + + // if parent has changed, get path, level and sort order + if (entity.IsPropertyDirty("ParentId")) + { + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetParentNode", tsql => + tsql.Select(x => x.NodeId).Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + ); + var parent = Database.Fetch(template.Sql(entity.ParentId)).First(); + + entity.Path = string.Concat(parent.Path, ",", entity.Id); + entity.Level = parent.Level + 1; + entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0); + } + + // create the dto + var dto = MemberFactory.BuildDto(entity); + + // update the node dto + var nodeDto = dto.ContentDto.NodeDto; + Database.Update(nodeDto); + + // update the content dto - only if the content type has changed + var origContentDto = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.NodeId == entity.Id)).FirstOrDefault(); + if (origContentDto.ContentTypeId != entity.ContentTypeId) + { + dto.ContentDto.Id = origContentDto.Id; // fixme - annoying, needed? + Database.Update(dto.ContentDto); + } + + // insert or update the content version dtos + var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); + dto.ContentVersionDto.Id = contentVerDto.Id; + //Updates the current version - cmsContentVersion + //Assumes a Version guid exists and Version date (modified date) has been set/updated + Database.Update(dto.ContentVersionDto); + + // update the member dto + // but only the changed columns, 'cos we cannot update password if empty + var changedCols = new List(); + + if (entity.IsPropertyDirty("Email")) + changedCols.Add("Email"); + + if (entity.IsPropertyDirty("Username")) + changedCols.Add("LoginName"); + + // do NOT update the password if it has not changed or if it is null or empty + if (entity.IsPropertyDirty("RawPasswordValue") && !string.IsNullOrWhiteSpace(entity.RawPasswordValue)) + changedCols.Add("Password"); + + if (changedCols.Count > 0) + Database.Update(dto, changedCols); + + // update the property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.Id, entity.Version, entity.Properties).ToArray(); + foreach (var propertyDataDto in propertyDataDtos) + { + if (propertyDataDto.Id > 0) + Database.Update(propertyDataDto); + else + Database.Insert(propertyDataDto); + } + + // assign ids to properties, using propertyTypeId as a key + var xids = propertyDataDtos.ToDictionary(x => x.PropertyTypeId, x => x.Id); + foreach (var property in entity.Properties) + property.Id = xids[property.PropertyTypeId]; + + UpdateEntityTags(entity, _tagRepository); + + OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); + + entity.ResetDirtyProperties(); + } + + protected override void PersistDeletedItem(IMember entity) + { + // raise event first else potential FK issues + OnUowRemovingEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity)); + base.PersistDeletedItem(entity); + } + + #endregion + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { //get the group id @@ -527,7 +485,7 @@ namespace Umbraco.Core.Persistence.Repositories .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); - return MapQueryDtos(Database.Fetch(sql)); + return MapDtosToContent(Database.Fetch(sql)); } @@ -585,7 +543,7 @@ namespace Umbraco.Core.Persistence.Repositories } return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - x => MapQueryDtos(x), orderBy, orderDirection, orderBySystemField, "cmsMember", + x => MapDtosToContent(x), orderBy, orderDirection, orderBySystemField, "cmsMember", filterSql); } @@ -619,15 +577,16 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetDatabaseFieldNameForOrderBy(orderBy); } - private IEnumerable MapQueryDtos(List dtos, bool withCache = false) + private IEnumerable MapDtosToContent(List dtos, bool withCache = false) { - var content = new IMember[dtos.Count]; - var temps = new List(); + var temps = new List>(); var contentTypes = new Dictionary(); + var content = new Member[dtos.Count]; for (var i = 0; i < dtos.Count; i++) { var dto = dtos[i]; + var versionId = dto.ContentVersionDto.VersionId; if (withCache) { @@ -635,76 +594,61 @@ namespace Umbraco.Core.Persistence.Repositories var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); if (cached != null && cached.Version == dto.ContentVersionDto.VersionId) { - content[i] = cached; + content[i] = (Member) cached; // fixme should we just cache Content not IContent? continue; } } - // else, need to fetch from the database + // else, need to build it // get the content type - the repository is full cache *but* still deep-clones // whatever comes out of it, so use our own local index here to avoid this - if (contentTypes.TryGetValue(dto.ContentVersionDto.ContentDto.ContentTypeId, out IMemberType contentType) == false) - contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - // fixme - // - // 7.6 ProcessQuery has an additional 'allVersions' flag that is false by default, meaning - // we should always get the latest version of each content item. meaning what what we - // are processing now is a more recent version than what we already processed, we need to - // replace - // but it has flaws: it's not dealing with what could be in the cache (should be the latest - // version, always) and it's not replacing the content that is already in the list... - // so considering it broken, not implementing now, MUST FIX + var contentTypeId = dto.ContentDto.ContentTypeId; + if (contentTypes.TryGetValue(contentTypeId, out var contentType) == false) + contentTypes[contentTypeId] = contentType = _memberTypeRepository.Get(contentTypeId); var c = content[i] = MemberFactory.BuildEntity(dto, contentType); // need properties - temps.Add(new TempContent( - dto.NodeId, - dto.ContentVersionDto.VersionId, - dto.ContentVersionDto.VersionDate, - dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - contentType, - c - )); + temps.Add(new TempContent(dto.NodeId, versionId, contentType, c)); } - // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(temps); + // load all properties for all documents from database in 1 query - indexed by version id + var properties = GetPropertyCollections(temps); - // assign + // assign properites foreach (var temp in temps) { - temp.Content.Properties = propertyData[temp.Version]; + temp.Content.Properties = properties[temp.VersionId]; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity) temp.Content).ResetDirtyProperties(false); + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); } return content; } - /// - /// Private method to create a member object from a MemberDto - /// - /// - /// - /// - private IMember CreateMemberFromDto(MemberDto dto, Guid versionId) + private IMember MapDtoToContent(MemberDto dto) { - var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + var memberType = _memberTypeRepository.Get(dto.ContentDto.ContentTypeId); var member = MemberFactory.BuildEntity(dto, memberType); - var temp = new TempContent(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); - var properties = GetPropertyCollection(new List { temp }); + // get properties - indexed by version id + var temp = new TempContent(dto.ContentDto.NodeId, dto.ContentVersionDto.VersionId, memberType); + var properties = GetPropertyCollections(new List> { temp }); member.Properties = properties[dto.ContentVersionDto.VersionId]; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); + // clear dirty props on init - U4-1943 + member.ResetDirtyProperties(false); return member; } + + private int GetNewChildSortOrder(int parentId, int first) + { + var template = SqlContext.Templates.Get("Umbraco.Core.ContentRepository.GetSortOrder", tsql => + tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From().Where(x => x.NodeId == SqlTemplate.Arg("parentId") && x.NodeObjectType == NodeObjectTypeId) + ); + return Database.ExecuteScalar(template.Sql(parentId)) + 1; // fixme can we mix named & non-named? + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/SimilarNodeName.cs b/src/Umbraco.Core/Persistence/Repositories/SimilarNodeName.cs index 8b8b2d3658..756137b5fd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimilarNodeName.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimilarNodeName.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Repositories private int _numPos = -2; public int Id { get; set; } - public string Name { get; set; } + public string Text { get; set; } // cached - reused public int NumPos @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories { if (_numPos != -2) return _numPos; - var name = Name; + var name = Text; if (name[name.Length - 1] != ')') return _numPos = -1; @@ -39,7 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories if (NumPos < 0) throw new InvalidOperationException(); int num; - if (int.TryParse(Name.Substring(NumPos + 1, Name.Length - 2 - NumPos), out num)) + if (int.TryParse(Text.Substring(NumPos + 1, Text.Length - 2 - NumPos), out num)) return num; return 0; } @@ -56,8 +56,8 @@ namespace Umbraco.Core.Persistence.Repositories var xpos = x.NumPos; var ypos = y.NumPos; - var xname = x.Name; - var yname = y.Name; + var xname = x.Text; + var yname = y.Text; if (xpos < 0 || ypos < 0 || xpos != ypos) return string.Compare(xname, yname, StringComparison.Ordinal); @@ -95,12 +95,12 @@ namespace Umbraco.Core.Persistence.Repositories if (uniqueing) { - if (name.NumPos > 0 && name.Name.StartsWith(nodeName) && name.NumVal == uniqueNumber) + if (name.NumPos > 0 && name.Text.StartsWith(nodeName) && name.NumVal == uniqueNumber) uniqueNumber++; else break; } - else if (name.Name.InvariantEquals(nodeName)) + else if (name.Text.InvariantEquals(nodeName)) { uniqueing = true; } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 3e198a6d5c..5d9bac40ed 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -11,7 +11,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; - using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -33,87 +32,79 @@ namespace Umbraco.Core.Persistence.Repositories where TEntity : class, IAggregateRoot where TRepository : class, IRepository { - //private readonly IContentSection _contentSection; - - protected VersionableRepositoryBase(IScopeUnitOfWork work, CacheHelper cache, ILogger logger /*, IContentSection contentSection*/) + protected VersionableRepositoryBase(IScopeUnitOfWork work, CacheHelper cache, ILogger logger) : base(work, cache, logger) - { - //_contentSection = contentSection; - } + { } protected abstract TRepository This { get; } - #region IRepositoryVersionable Implementation - - /// - /// Gets a list of all versions for an ordered so latest is first - /// - /// Id of the to retrieve versions from - /// An enumerable list of the same object with different versions - public abstract IEnumerable GetAllVersions(int id); - - /// - /// Gets a list of all version Ids for the given content item ordered so latest is first - /// - /// - /// The maximum number of rows to return - /// - public virtual IEnumerable GetVersionIds(int id, int maxRows) - { - var sql = SqlContext.Sql(); - sql.Select("cmsDocument.versionId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.NodeId == id) - .OrderByDescending(x => x.UpdateDate); - - return Database.Fetch(SqlContext.SqlSyntax.SelectTop(sql, maxRows)); - } - - public virtual void DeleteVersion(Guid versionId) - { - var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); - if(dto == null) return; - - //Ensure that the lastest version is not deleted - var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = dto.NodeId }); - if(latestVersionDto.VersionId == dto.VersionId) - return; - - PerformDeleteVersion(dto.NodeId, versionId); - } - - public virtual void DeleteVersions(int id, DateTime versionDate) - { - //Ensure that the latest version is not part of the versions being deleted - var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = id }); - var list = - Database.Fetch( - "WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)", - new { /*VersionId =*/ latestVersionDto.VersionId, Id = id, VersionDate = versionDate}); - if (list.Any() == false) return; - - foreach (var dto in list) - { - PerformDeleteVersion(id, dto.VersionId); - } - } + #region Versions + // gets a specific version public abstract TEntity GetByVersion(Guid versionId); - /// - /// Protected method to execute the delete statements for removing a single version for a TEntity item. - /// - /// Id of the to delete a version from - /// Guid id of the version to delete + // gets all versions, current first + public abstract IEnumerable GetAllVersions(int nodeId); + + // gets all version ids, current first + public virtual IEnumerable GetVersionIds(int nodeId, int maxRows) + { + var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersionIds", tsql => + tsql.Select(x => x.VersionId) + .From() + .Where(x => x.NodeId == SqlTemplate.Arg("nodeId")) + .OrderByDescending(x => x.Current) // current '1' comes before others '0' + .AndByDescending(x => x.VersionDate) // most recent first + ); + return Database.Fetch(SqlSyntax.SelectTop(template.Sql(nodeId), maxRows)); + } + + // deletes a specific version + public virtual void DeleteVersion(Guid versionId) + { + // fixme test object node type? + + // get the version we want to delete + var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersion", tsql => + tsql.Select().From().Where(x => x.VersionId == SqlTemplate.Arg("versionId")) + ); + var versionDto = Database.Fetch(template.Sql(versionId)).FirstOrDefault(); + + // nothing to delete + if (versionDto == null) + return; + + // don't delete the current version + if (versionDto.Current) + throw new InvalidOperationException("Cannot delete the current version."); + + PerformDeleteVersion(versionDto.NodeId, versionId); + } + + // deletes all version older than a date + public virtual void DeleteVersions(int nodeId, DateTime versionDate) + { + // fixme test object node type? + + // get the versions we want to delete, excluding the current one + var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersion", tsql => + tsql.Select().From().Where(x => x.NodeId == SqlTemplate.Arg("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg("date")) + ); + var versionDtos = Database.Fetch(template.Sql(nodeId, versionDate)); // fixme ok params? + foreach (var versionDto in versionDtos) + PerformDeleteVersion(versionDto.NodeId, versionDto.VersionId); + } + + // actually deletes a version protected abstract void PerformDeleteVersion(int id, Guid versionId); #endregion + #region Count + + /// + /// Count descendants of an item. + /// public int CountDescendants(int parentId, string contentTypeAlias = null) { var pathMatch = parentId == -1 @@ -145,6 +136,9 @@ namespace Umbraco.Core.Persistence.Repositories return Database.ExecuteScalar(sql); } + /// + /// Count children of an item. + /// public int CountChildren(int parentId, string contentTypeAlias = null) { var sql = SqlContext.Sql() @@ -173,10 +167,8 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// Get the total count of entities + /// Count items. /// - /// - /// public int Count(string contentTypeAlias = null) { var sql = SqlContext.Sql() @@ -202,21 +194,21 @@ namespace Umbraco.Core.Persistence.Repositories return Database.ExecuteScalar(sql); } + #endregion + + #region Tags + /// - /// This removes associated tags from the entity - used generally when an entity is recycled + /// Clears tags for an item. /// - /// - /// protected void ClearEntityTags(IContentBase entity, ITagRepository tagRepo) { tagRepo.ClearTagsFromEntity(entity.Id); } /// - /// Updates the tag repository with any tag enabled properties and their values + /// Updates tags for an item. /// - /// - /// protected void UpdateEntityTags(IContentBase entity, ITagRepository tagRepo) { foreach (var tagProp in entity.Properties.Where(x => x.TagSupport.Enable)) @@ -241,11 +233,18 @@ namespace Umbraco.Core.Persistence.Repositories } } + /// + /// Determines if an item has a property that supports tags. + /// + /// + /// protected bool HasTagProperty(IContentBase entity) { return entity.Properties.Any(x => x.TagSupport.Enable); } + #endregion + private Sql PrepareSqlForPagedResults(Sql sql, Sql filterSql, string orderBy, Direction orderDirection, bool orderBySystemField, string table) { if (filterSql == null && string.IsNullOrEmpty(orderBy)) return sql; @@ -430,14 +429,15 @@ namespace Umbraco.Core.Persistence.Repositories return mapper(pagedResult.Items); } - protected IDictionary GetPropertyCollection(List temps) + protected IDictionary GetPropertyCollections(List> temps) + where T : class, IContentBase { - var versions = temps.Select(x => x.Version).ToArray(); + var versions = temps.Select(x => x.VersionId).ToArray(); if (versions.Length == 0) return new Dictionary(); // get all PropertyDataDto for all definitions / versions var allPropertyDataDtos = Database.FetchByGroups(versions, 2000, batch => - SqlContext.Sql() + SqlContext.Sql() .Select() .From() .WhereIn(x => x.VersionId, batch)) @@ -472,10 +472,11 @@ namespace Umbraco.Core.Persistence.Repositories // - a lazy access to prevalues // and we need to build the proper property collections - return GetPropertyCollection(temps, allPropertyDataDtos, pre); + return GetPropertyCollections(temps, allPropertyDataDtos, pre); } - private IDictionary GetPropertyCollection(List temps, IEnumerable allPropertyDataDtos, Lazy> allPreValues) + private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos, Lazy> allPreValues) + where T : class, IContentBase { var result = new Dictionary(); var propertiesWithTagSupport = new Dictionary(); @@ -495,12 +496,12 @@ namespace Umbraco.Core.Persistence.Repositories { // compositionProperties is the property types for the entire composition // use an index for perfs - if (compositionPropertiesIndex.TryGetValue(temp.Composition.Id, out var compositionProperties) == false) - compositionPropertiesIndex[temp.Composition.Id] = compositionProperties = temp.Composition.CompositionPropertyTypes.ToArray(); + if (compositionPropertiesIndex.TryGetValue(temp.ContentType.Id, out var compositionProperties) == false) + compositionPropertiesIndex[temp.ContentType.Id] = compositionProperties = temp.ContentType.CompositionPropertyTypes.ToArray(); // map the list of PropertyDataDto to a list of Property - var properties = indexedPropertyDataDtos.TryGetValue(temp.Version, out var propertyDataDtos) - ? PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, temp.CreateDate, temp.VersionDate).ToList() + var properties = indexedPropertyDataDtos.TryGetValue(temp.VersionId, out var propertyDataDtos) + ? PropertyFactory.BuildEntities(propertyDataDtos, compositionProperties).ToList() : new List(); // deal with tags @@ -523,19 +524,20 @@ namespace Umbraco.Core.Persistence.Repositories var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); var preVals = new PreValueCollection(asDictionary); if (additionalData == null) additionalData = new Dictionary(); // reduce allocs - var contentPropData = new ContentPropertyData(property.Value, preVals, additionalData); - TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); + // fixme this is totally borked of course for variants + var contentPropData = new ContentPropertyData(property.GetValue(), preVals, additionalData); + TagExtractor.SetPropertyTags(property, contentPropData, property.GetValue(), tagSupport); } - if (result.ContainsKey(temp.Version)) + if (result.ContainsKey(temp.VersionId)) { - var msg = $"The query returned multiple property sets for content {temp.Id}, {temp.Composition.Name}"; + var msg = $"The query returned multiple property sets for content {temp.Id}, {temp.ContentType.Name}"; if (VersionableRepositoryBase.ThrowOnWarning) throw new InvalidOperationException(msg); Logger.Warn>(msg); } - result[temp.Version] = new PropertyCollection(properties); + result[temp.VersionId] = new PropertyCollection(properties); } //// iterate each definition grouped by it's content type, @@ -932,25 +934,47 @@ ORDER BY nodeId, versionId, propertytypeid protected class TempContent { - public TempContent(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition, IContentBase content = null) + public TempContent(int id, Guid versionId, IContentTypeComposition contentType) { Id = id; - Version = version; - VersionDate = versionDate; - CreateDate = createDate; - Composition = composition; + VersionId = versionId; + ContentType = contentType; + } + + /// + /// Gets or sets the identifier of the content. + /// + public int Id { get; set; } + + /// + /// Gets or sets the version identifier of the content. + /// + public Guid VersionId { get; set; } + + /// + /// Gets or sets the content type. + /// + public IContentTypeComposition ContentType { get; set; } + + /// + /// Gets or sets the identifier of the template of the content. + /// + public int? TemplateId { get; set; } + } + + protected class TempContent : TempContent + where T : class, IContentBase + { + public TempContent(int id, Guid versionId, IContentTypeComposition contentType, T content = null) + : base(id, versionId, contentType) + { Content = content; } - public int Id { get; set; } - public Guid Version { get; set; } - public DateTime VersionDate { get; set; } - public DateTime CreateDate { get; set; } - public IContentTypeComposition Composition { get; set; } - - public IContentBase Content { get; set; } - - public int? TemplateId { get; set; } + /// + /// Gets or sets the associated actual content. + /// + public T Content { get; set; } } // fixme copied from 7.6 diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs index 69658bf6e3..dbff31cc2c 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs @@ -273,7 +273,7 @@ namespace Umbraco.Core.PropertyEditors /// public virtual object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null) return string.Empty; + if (property.GetValue() == null) return string.Empty; switch (GetDatabaseType()) { @@ -281,7 +281,7 @@ namespace Umbraco.Core.PropertyEditors case DataTypeDatabaseType.Nvarchar: //if it is a string type, we will attempt to see if it is json stored data, if it is we'll try to convert //to a real json object so we can pass the true json object directly to angular! - var asString = property.Value.ToString(); + var asString = property.GetValue().ToString(); if (asString.DetectIsJson()) { try @@ -299,12 +299,12 @@ namespace Umbraco.Core.PropertyEditors case DataTypeDatabaseType.Decimal: //Decimals need to be formatted with invariant culture (dots, not commas) //Anything else falls back to ToString() - var decim = property.Value.TryConvertTo(); + var decim = property.GetValue().TryConvertTo(); return decim.Success ? decim.Result.ToString(NumberFormatInfo.InvariantInfo) - : property.Value.ToString(); + : property.GetValue().ToString(); case DataTypeDatabaseType.Date: - var date = property.Value.TryConvertTo(); + var date = property.GetValue().TryConvertTo(); if (date.Success == false || date.Result == null) { return string.Empty; @@ -334,7 +334,7 @@ namespace Umbraco.Core.PropertyEditors public virtual XNode ConvertDbToXml(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { //check for null or empty value, we don't want to return CDATA if that is the case - if (property.Value == null || property.Value.ToString().IsNullOrWhiteSpace()) + if (property.GetValue() == null || property.GetValue().ToString().IsNullOrWhiteSpace()) { return new XText(ConvertDbToString(property, propertyType, dataTypeService)); } @@ -363,25 +363,25 @@ namespace Umbraco.Core.PropertyEditors /// public virtual string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null) + if (property.GetValue() == null) return string.Empty; switch (GetDatabaseType()) { case DataTypeDatabaseType.Nvarchar: case DataTypeDatabaseType.Ntext: - property.Value.ToXmlString(); - return property.Value.ToXmlString(); + property.GetValue().ToXmlString(); + return property.GetValue().ToXmlString(); case DataTypeDatabaseType.Integer: case DataTypeDatabaseType.Decimal: - return property.Value.ToXmlString(property.Value.GetType()); + return property.GetValue().ToXmlString(property.GetValue().GetType()); case DataTypeDatabaseType.Date: //treat dates differently, output the format as xml format - if (property.Value == null) + if (property.GetValue() == null) { return string.Empty; } - var date = property.Value.TryConvertTo(); + var date = property.GetValue().TryConvertTo(); if (date.Success == false || date.Result == null) { return string.Empty; diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 42f604c9e6..12343621af 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2384,12 +2384,12 @@ namespace Umbraco.Core.Services } // check if the content is valid - if (content.IsValid() == false) + if (content.Validate() == false) { Logger.Info($"Content '{content.Name}' with Id '{content.Id}' could not be published because of invalid properties."); return Attempt.Fail(new PublishStatus(PublishStatusType.FailedContentInvalid, evtMsgs, content) { - InvalidProperties = ((ContentBase)content).LastInvalidProperties + InvalidProperties = ((ContentBase)content).InvalidProperties }); } @@ -2823,7 +2823,7 @@ namespace Umbraco.Core.Services content.WriterId = userId; foreach (var property in blueprint.Properties) - content.SetValue(property.Alias, property.Value); + content.SetValue(property.Alias, property.GetValue()); return content; } diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 66eb9e6b25..9c782b6bfb 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -510,7 +510,7 @@ namespace Umbraco.Core.Services new XAttribute("path", contentBase.Path), new XAttribute("isDoc", "")); - foreach (var property in contentBase.Properties.Where(p => p != null && p.Value != null && p.Value.ToString().IsNullOrWhiteSpace() == false)) + foreach (var property in contentBase.Properties.Where(p => p != null && p.GetValue() != null && p.GetValue().ToString().IsNullOrWhiteSpace() == false)) { xml.Add(Serialize(dataTypeService, property)); } diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 8ecc8d1781..a89cafc5bf 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -373,14 +373,14 @@ namespace Umbraco.Core.Services var props = content.Properties.ToArray(); foreach (var p in props) { - var newText = p.Value != null ? p.Value.ToString() : ""; + var newText = p.GetValue() != null ? p.GetValue().ToString() : ""; var oldText = newText; // check if something was changed and display the changes otherwise display the fields if (oldDoc.Properties.Contains(p.PropertyType.Alias)) { var oldProperty = oldDoc.Properties[p.PropertyType.Alias]; - oldText = oldProperty.Value != null ? oldProperty.Value.ToString() : ""; + oldText = oldProperty.GetValue() != null ? oldProperty.GetValue().ToString() : ""; // replace html with char equivalent ReplaceHtmlSymbols(ref oldText); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 487da72603..a21bb60dc5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -690,6 +690,7 @@ + diff --git a/src/Umbraco.Examine/UmbracoContentIndexer.cs b/src/Umbraco.Examine/UmbracoContentIndexer.cs index 4af5335856..91e695de41 100644 --- a/src/Umbraco.Examine/UmbracoContentIndexer.cs +++ b/src/Umbraco.Examine/UmbracoContentIndexer.cs @@ -332,9 +332,9 @@ namespace Umbraco.Examine {"template", new object[] {c.Template?.Id ?? 0}} }; - foreach (var property in c.Properties.Where(p => p?.Value != null && p.Value.ToString().IsNullOrWhiteSpace() == false)) + foreach (var property in c.Properties.Where(p => p?.GetValue() != null && p.GetValue().ToString().IsNullOrWhiteSpace() == false)) { - values.Add(property.Alias, new[] {property.Value}); + values.Add(property.Alias, new[] {property.GetValue() }); } var vs = new ValueSet(c.Id, IndexTypes.Content, c.ContentType.Alias, values); @@ -366,9 +366,9 @@ namespace Umbraco.Examine {"creatorName", new object[] {m.GetCreatorProfile(UserService).Name}} }; - foreach (var property in m.Properties.Where(p => p?.Value != null && p.Value.ToString().IsNullOrWhiteSpace() == false)) + foreach (var property in m.Properties.Where(p => p?.GetValue() != null && p.GetValue().ToString().IsNullOrWhiteSpace() == false)) { - values.Add(property.Alias, new[] { property.Value }); + values.Add(property.Alias, new[] { property.GetValue() }); } var vs = new ValueSet(m.Id, IndexTypes.Media, m.ContentType.Alias, values); diff --git a/src/Umbraco.Examine/UmbracoMemberIndexer.cs b/src/Umbraco.Examine/UmbracoMemberIndexer.cs index 188f3df9ed..1f4b7fa79a 100644 --- a/src/Umbraco.Examine/UmbracoMemberIndexer.cs +++ b/src/Umbraco.Examine/UmbracoMemberIndexer.cs @@ -193,9 +193,9 @@ namespace Umbraco.Examine {"email", new object[] {m.Email}}, }; - foreach (var property in m.Properties.Where(p => p != null && p.Value != null && p.Value.ToString().IsNullOrWhiteSpace() == false)) + foreach (var property in m.Properties.Where(p => p != null && p.GetValue() != null && p.GetValue().ToString().IsNullOrWhiteSpace() == false)) { - values.Add(property.Alias, new[] { property.Value }); + values.Add(property.Alias, new[] { property.GetValue() }); } var vs = new ValueSet(m.Id, IndexTypes.Content, m.ContentType.Alias, values); diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index ae39bb1b85..e5b916ee28 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -437,7 +437,7 @@ namespace Umbraco.Tests.Integration ServiceContext.ContentService.SaveAndPublishWithStatus(content); ResetEvents(); - content.Properties.First().Value = "changed"; + content.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content); Assert.AreEqual(2, _msgCount); @@ -449,7 +449,7 @@ namespace Umbraco.Tests.Integration Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshNode/{1}", m, content.Id), _events[i].ToString()); ResetEvents(); - content.Properties.First().Value = "again"; + content.Properties.First().SetValue("again"); ServiceContext.ContentService.Save(content); Assert.AreEqual(2, _msgCount); @@ -943,7 +943,7 @@ namespace Umbraco.Tests.Integration Assert.IsNotNull(content); ServiceContext.ContentService.PublishWithStatus(content); - content.Properties.First().Value = "changed"; + content.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content); ResetEvents(); @@ -1132,7 +1132,7 @@ namespace Umbraco.Tests.Integration var content = CreateContent(); Assert.IsNotNull(content); ServiceContext.ContentService.SaveAndPublishWithStatus(content); - content.Properties.First().Value = "changed"; + content.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content); ResetEvents(); @@ -1258,7 +1258,7 @@ namespace Umbraco.Tests.Integration var content1 = CreateContent(); Assert.IsNotNull(content1); ServiceContext.ContentService.PublishWithStatus(content1); - content1.Properties.First().Value = "changed"; + content1.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); @@ -1375,7 +1375,7 @@ namespace Umbraco.Tests.Integration var content1 = CreateContent(); Assert.IsNotNull(content1); ServiceContext.ContentService.PublishWithStatus(content1); - content1.Properties.First().Value = "changed"; + content1.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); @@ -1399,7 +1399,7 @@ namespace Umbraco.Tests.Integration var content1 = CreateContent(); Assert.IsNotNull(content1); ServiceContext.ContentService.PublishWithStatus(content1); - content1.Properties.First().Value = "changed"; + content1.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); @@ -1486,7 +1486,7 @@ namespace Umbraco.Tests.Integration var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); ServiceContext.ContentService.PublishWithStatus(content2); - content2.Properties.First().Value = "changed"; + content2.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content2); ServiceContext.ContentService.UnPublish(content1); var content3 = CreateContent(); @@ -1514,7 +1514,7 @@ namespace Umbraco.Tests.Integration var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); ServiceContext.ContentService.PublishWithStatus(content2); - content2.Properties.First().Value = "changed"; + content2.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content2); ServiceContext.ContentService.UnPublish(content1); var content3 = CreateContent(); @@ -1571,7 +1571,7 @@ namespace Umbraco.Tests.Integration var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); ServiceContext.ContentService.PublishWithStatus(content2); - content2.Properties.First().Value = "changed"; + content2.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content2); ServiceContext.ContentService.UnPublish(content1); var content3 = CreateContent(); @@ -2031,11 +2031,11 @@ namespace Umbraco.Tests.Integration ServiceContext.ContentService.PublishWithStatus(content); var v1 = content.Version; - content.Properties.First().Value = "changed"; + content.Properties.First().SetValue("changed"); ServiceContext.ContentService.PublishWithStatus(content); var v2 = content.Version; - content.Properties.First().Value = "again"; + content.Properties.First().SetValue("again"); ServiceContext.ContentService.PublishWithStatus(content); var v3 = content.Version; diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/SqlResources.Designer.cs b/src/Umbraco.Tests/Migrations/SqlScripts/SqlResources.Designer.cs index a60e2cb99a..d60ccffa50 100644 --- a/src/Umbraco.Tests/Migrations/SqlScripts/SqlResources.Designer.cs +++ b/src/Umbraco.Tests/Migrations/SqlScripts/SqlResources.Designer.cs @@ -132,7 +132,7 @@ namespace Umbraco.Tests.Migrations.SqlScripts { ///( ///[nodeId] [int] NOT NULL, ///[published] [bit] NOT NULL, - ///[documentUser] [int] NOT [rest of string was truncated]";. + ///[writerUserId] [int] NOT [rest of string was truncated]";. /// internal static string SqlCeTotal_480 { get { diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs deleted file mode 100644 index be91ebca1f..0000000000 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Data.Common; -using Moq; -using NPoco; -using NUnit.Framework; -using Semver; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Services; -using Umbraco.Web.Strategies.Migrations; - -namespace Umbraco.Tests.Migrations.Upgrades -{ - [TestFixture, NUnit.Framework.Ignore("fixme - ignored test")] - public class SqlCeDataUpgradeTest : BaseUpgradeTest - { - - [Test, NUnit.Framework.Ignore("fixme - ignored test")] - public override void Can_Upgrade_From_470_To_600() - { - var configuredVersion = new SemVersion(4, 11, 0); - var targetVersion = new SemVersion(6, 0, 0); - var db = GetConfiguredDatabase(); - - var fix = new PublishAfterUpgradeToVersionSixth(); - MigrationRunner.Migrated += fix.Migrated; - - //Setup the MigrationRunner - var migrationContext = new MigrationContext(db, Mock.Of()); - var migrationRunner = new MigrationRunner( - Mock.Of(), - Mock.Of(), - Mock.Of(), configuredVersion, targetVersion, Constants.System.UmbracoMigrationName); - - bool upgraded = migrationRunner.Execute(migrationContext /*, true*/); - - Assert.That(upgraded, Is.True); - - var schemaHelper = new DatabaseSchemaHelper(db, Mock.Of()); - - bool hasTabTable = schemaHelper.TableExist("cmsTab"); - bool hasPropertyTypeGroupTable = schemaHelper.TableExist("cmsPropertyTypeGroup"); - bool hasAppTreeTable = schemaHelper.TableExist("umbracoAppTree"); - - MigrationRunner.Migrated -= fix.Migrated; - - Assert.That(hasTabTable, Is.False); - Assert.That(hasPropertyTypeGroupTable, Is.True); - Assert.That(hasAppTreeTable, Is.False); - } - - public override void DatabaseSpecificSetUp() - { - } - - public override void DatabaseSpecificTearDown() - { - //legacy API database connection close - //SqlCeContextGuardian.CloseBackgroundConnection(); - } - - public override IUmbracoDatabase GetConfiguredDatabase() - { - var dbProviderFactory = DbProviderFactories.GetFactory(Constants.DbProviderNames.SqlCe); - var sqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, Mock.Of()); - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;", sqlContext, dbProviderFactory, Mock.Of()); - } - - public override string GetDatabaseSpecificSqlScript() - { - return SqlScripts.SqlResources.SqlCe_SchemaAndData_4110; - } - } -} diff --git a/src/Umbraco.Tests/Models/Collections/Item.cs b/src/Umbraco.Tests/Models/Collections/Item.cs index 7fc014838d..0f84fc9cc9 100644 --- a/src/Umbraco.Tests/Models/Collections/Item.cs +++ b/src/Umbraco.Tests/Models/Collections/Item.cs @@ -135,6 +135,11 @@ namespace Umbraco.Tests.Models.Collections return _propertyChangedInfo.Any(x => x.Key == propertyName); } + public virtual IEnumerable GetDirtyProperties() + { + return _propertyChangedInfo.Keys; + } + /// /// Indicates whether the current entity is dirty. /// diff --git a/src/Umbraco.Tests/Models/ContentExtensionsTests.cs b/src/Umbraco.Tests/Models/ContentExtensionsTests.cs index a555f0f3de..f308466f4f 100644 --- a/src/Umbraco.Tests/Models/ContentExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/ContentExtensionsTests.cs @@ -49,7 +49,7 @@ namespace Umbraco.Tests.Models content.ChangePublishedState(PublishedState.Publishing); content.ResetDirtyProperties(false); // => .Published - content.Properties.First().Value = "hello world"; // change data + content.Properties.First().SetValue("hello world"); // change data content.ChangePublishedState(PublishedState.Saving); // saving Assert.IsTrue(content.RequiresSaving()); @@ -119,7 +119,7 @@ namespace Umbraco.Tests.Models content.ResetDirtyProperties(false); - content.Properties.First().Value = "hello world"; // change data + content.Properties.First().SetValue("hello world"); // change data Assert.IsTrue(content.RequiresSaving()); } @@ -218,7 +218,7 @@ namespace Umbraco.Tests.Models content.ChangePublishedState(PublishedState.Publishing); content.ResetDirtyProperties(false); // => .Published - content.Properties.First().Value = "hello world"; // change data + content.Properties.First().SetValue("hello world"); // change data content.ChangePublishedState(PublishedState.Publishing); Assert.IsTrue(content.RequiresNewVersion()); @@ -262,7 +262,7 @@ namespace Umbraco.Tests.Models content.ChangePublishedState(PublishedState.Publishing); content.ResetDirtyProperties(false); // => .Published - content.Properties.First().Value = "hello world"; // change data + content.Properties.First().SetValue("hello world"); // change data content.ChangePublishedState(PublishedState.Saving); // saving Assert.IsTrue(content.RequiresNewVersion()); @@ -318,7 +318,7 @@ namespace Umbraco.Tests.Models content.ResetDirtyProperties(false); - content.Properties.First().Value = "hello world"; // change user property + content.Properties.First().SetValue("hello world"); // change user property Assert.IsFalse(content.RequiresNewVersion()); } @@ -357,7 +357,7 @@ namespace Umbraco.Tests.Models content.ResetDirtyProperties(false); - content.Properties.First().Value = "hello world"; // change user property + content.Properties.First().SetValue("hello world"); // change user property content.ChangePublishedState(PublishedState.Publishing); // publishing Assert.IsFalse(content.RequiresNewVersion()); @@ -516,17 +516,17 @@ namespace Umbraco.Tests.Models // if you assign a user property with its value it is not dirty // if you assign it with another value then back, it is dirty - prop.Value = "A"; + prop.SetValue("A"); content.ResetDirtyProperties(false); Assert.IsFalse(prop.IsDirty()); - prop.Value = "B"; + prop.SetValue("B"); Assert.IsTrue(prop.IsDirty()); content.ResetDirtyProperties(false); Assert.IsFalse(prop.IsDirty()); - prop.Value = "B"; + prop.SetValue("B"); Assert.IsFalse(prop.IsDirty()); - prop.Value = "A"; - prop.Value = "B"; + prop.SetValue("A"); + prop.SetValue("B"); Assert.IsTrue(prop.IsDirty()); } @@ -539,7 +539,7 @@ namespace Umbraco.Tests.Models content.ResetDirtyProperties(false); var d = content.UpdateDate; - prop.Value = "A"; + prop.SetValue("A"); Assert.IsTrue(content.IsAnyUserPropertyDirty()); Assert.IsFalse(content.IsEntityDirty()); Assert.AreEqual(d, content.UpdateDate); @@ -616,21 +616,21 @@ namespace Umbraco.Tests.Models content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); Assert.IsFalse(content.WasDirty()); - prop.Value = "a"; - prop.Value = "b"; + prop.SetValue("a"); + prop.SetValue("b"); Assert.IsTrue(content.IsDirty()); Assert.IsFalse(content.WasDirty()); content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); Assert.IsFalse(content.WasDirty()); - prop.Value = "a"; - prop.Value = "b"; + prop.SetValue("a"); + prop.SetValue("b"); content.ResetDirtyProperties(true); // what PersistUpdatedItem does Assert.IsFalse(content.IsDirty()); //Assert.IsFalse(content.WasDirty()); // not impacted by user properties Assert.IsTrue(content.WasDirty()); // now it is! - prop.Value = "a"; - prop.Value = "b"; + prop.SetValue("a"); + prop.SetValue("b"); content.ResetDirtyProperties(); // what PersistUpdatedItem does Assert.IsFalse(content.IsDirty()); //Assert.IsFalse(content.WasDirty()); // not impacted by user properties diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 29e9f791a8..398845833c 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -101,12 +101,12 @@ namespace Umbraco.Tests.Models var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act - content.Properties["title"].Value = "This is the new title"; + content.Properties["title"].SetValue("This is the new title"); // Assert Assert.That(content.Properties.Any(), Is.True); Assert.That(content.Properties["title"], Is.Not.Null); - Assert.That(content.Properties["title"].Value, Is.EqualTo("This is the new title")); + Assert.That(content.Properties["title"].GetValue(), Is.EqualTo("This is the new title")); } [Test] @@ -122,7 +122,7 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Any(), Is.True); Assert.That(content.Properties["title"], Is.Not.Null); - Assert.That(content.Properties["title"].Value, Is.EqualTo("This is the new title")); + Assert.That(content.Properties["title"].GetValue(), Is.EqualTo("This is the new title")); } [Test] @@ -149,7 +149,7 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Any(), Is.True); Assert.That(content.Properties["title"], Is.Not.Null); - Assert.That(content.Properties["title"].Value, Is.StringContaining("sample.txt")); + Assert.That(content.Properties["title"].GetValue(), Is.StringContaining("sample.txt")); } @@ -342,7 +342,11 @@ namespace Umbraco.Tests.Models var asDirty = (ICanBeDirty)clone; Assert.IsFalse(asDirty.IsPropertyDirty("Properties")); - clone.Properties.Add(new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Ntext, "blah"), "blah")); + var propertyType = new PropertyType("test", DataTypeDatabaseType.Ntext, "blah"); + var newProperty = new Property(1, propertyType); + newProperty.SetValue("blah"); + clone.Properties.Add(newProperty); + Assert.IsTrue(asDirty.IsPropertyDirty("Properties")); } @@ -423,8 +427,8 @@ namespace Umbraco.Tests.Models Assert.That(content.Properties.Any(), Is.True); Assert.That(content.Properties["title"], Is.Not.Null); Assert.That(content.Properties["title"].Alias, Is.EqualTo("title")); - Assert.That(content.Properties["title"].Value, Is.EqualTo("This is the new title")); - Assert.That(content.Properties["description"].Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(content.Properties["title"].GetValue(), Is.EqualTo("This is the new title")); + Assert.That(content.Properties["description"].GetValue(), Is.EqualTo("This is the meta description for a textpage")); } [Test] @@ -508,11 +512,13 @@ namespace Umbraco.Tests.Models Name = "Subtitle", Description = "Optional subtitle", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }; contentType.PropertyGroups["Content"].PropertyTypes.Add(propertyType); - content.Properties.Add(new Property(propertyType){Value = "This is a subtitle Test"}); + var newProperty = new Property(propertyType); + newProperty.SetValue("This is a subtitle text"); + content.Properties.Add(newProperty); // Assert Assert.That(content.Properties.Contains("subtitle"), Is.True); - Assert.That(content.Properties["subtitle"].Value, Is.EqualTo("This is a subtitle Test")); + Assert.That(content.Properties["subtitle"].GetValue(), Is.EqualTo("This is a subtitle Test")); } [Test] @@ -534,14 +540,16 @@ namespace Umbraco.Tests.Models var propertyGroup = new PropertyGroup {Name = "Test Group", SortOrder = 3}; propertyGroup.PropertyTypes.Add(propertyType); contentType.PropertyGroups.Add(propertyGroup); - content.Properties.Add(new Property(propertyType){ Value = "Subtitle Test"}); + var newProperty = new Property(propertyType); + newProperty.SetValue("Subtitle Test"); + content.Properties.Add(newProperty); // Assert Assert.That(content.Properties.Count, Is.EqualTo(5)); Assert.That(content.PropertyTypes.Count(), Is.EqualTo(5)); Assert.That(content.PropertyGroups.Count(), Is.EqualTo(3)); - Assert.That(content.Properties["subtitle"].Value, Is.EqualTo("Subtitle Test")); - Assert.That(content.Properties["title"].Value, Is.EqualTo("Textpage textpage")); + Assert.That(content.Properties["subtitle"].GetValue(), Is.EqualTo("Subtitle Test")); + Assert.That(content.Properties["title"].GetValue(), Is.EqualTo("Textpage textpage")); } [Test] @@ -561,7 +569,7 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Count, Is.EqualTo(4)); Assert.That(contentType.PropertyTypes.First(x => x.Alias == "title").SortOrder, Is.EqualTo(1)); - Assert.That(content.Properties["title"].Value, Is.EqualTo("Textpage textpage")); + Assert.That(content.Properties["title"].GetValue(), Is.EqualTo("Textpage textpage")); } [Test] @@ -597,7 +605,7 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Contains("author"), Is.True); - Assert.That(content.Properties["author"].Value, Is.EqualTo("John Doe")); + Assert.That(content.Properties["author"].GetValue(), Is.EqualTo("John Doe")); } [Test] @@ -615,8 +623,8 @@ namespace Umbraco.Tests.Models Assert.That(content.Properties.Contains("author"), Is.True); Assert.That(content.Properties.Contains("keywords"), Is.True); Assert.That(content.Properties.Contains("description"), Is.True); - Assert.That(content.Properties["keywords"].Value, Is.EqualTo("text,page,meta")); - Assert.That(content.Properties["description"].Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(content.Properties["keywords"].GetValue(), Is.EqualTo("text,page,meta")); + Assert.That(content.Properties["description"].GetValue(), Is.EqualTo("This is the meta description for a textpage")); } [Test] diff --git a/src/Umbraco.Tests/Models/ContentXmlTest.cs b/src/Umbraco.Tests/Models/ContentXmlTest.cs index e03b3becf7..3b8fae17f3 100644 --- a/src/Umbraco.Tests/Models/ContentXmlTest.cs +++ b/src/Umbraco.Tests/Models/ContentXmlTest.cs @@ -50,10 +50,10 @@ namespace Umbraco.Tests.Models Assert.AreEqual(content.WriterId.ToString(), (string)element.Attribute("writerID")); Assert.AreEqual(content.Template == null ? "0" : content.Template.Id.ToString(), (string)element.Attribute("template")); - Assert.AreEqual(content.Properties["title"].Value.ToString(), element.Elements("title").Single().Value); - Assert.AreEqual(content.Properties["bodyText"].Value.ToString(), element.Elements("bodyText").Single().Value); - Assert.AreEqual(content.Properties["keywords"].Value.ToString(), element.Elements("keywords").Single().Value); - Assert.AreEqual(content.Properties["description"].Value.ToString(), element.Elements("description").Single().Value); + Assert.AreEqual(content.Properties["title"].GetValue().ToString(), element.Elements("title").Single().Value); + Assert.AreEqual(content.Properties["bodyText"].GetValue().ToString(), element.Elements("bodyText").Single().Value); + Assert.AreEqual(content.Properties["keywords"].GetValue().ToString(), element.Elements("keywords").Single().Value); + Assert.AreEqual(content.Properties["description"].GetValue().ToString(), element.Elements("description").Single().Value); } } } diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 56305a5e0a..51ffd26e07 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -205,12 +205,12 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(p.Alias, pDto.Alias); Assert.AreEqual(p.Id, pDto.Id); - if (p.Value == null) + if (p.GetValue() == null) Assert.AreEqual(pDto.Value, string.Empty); - else if (p.Value is decimal) - Assert.AreEqual(pDto.Value, ((decimal) p.Value).ToString(NumberFormatInfo.InvariantInfo)); + else if (p.GetValue() is decimal) + Assert.AreEqual(pDto.Value, ((decimal) p.GetValue()).ToString(NumberFormatInfo.InvariantInfo)); else - Assert.AreEqual(pDto.Value, p.Value.ToString()); + Assert.AreEqual(pDto.Value, p.GetValue().ToString()); } private void AssertProperty(ContentItemBasic result, Property p) diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index 4d03430fe3..87992c2edb 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -65,11 +65,11 @@ namespace Umbraco.Tests.Models Assert.AreEqual(media.Version.ToString(), (string)element.Attribute("version")); Assert.AreEqual("0", (string)element.Attribute("template")); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.File].Value.ToString(), element.Elements(Constants.Conventions.Media.File).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Width].Value.ToString(), element.Elements(Constants.Conventions.Media.Width).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Height].Value.ToString(), element.Elements(Constants.Conventions.Media.Height).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Bytes].Value.ToString(), element.Elements(Constants.Conventions.Media.Bytes).Single().Value); - Assert.AreEqual(media.Properties[Constants.Conventions.Media.Extension].Value.ToString(), element.Elements(Constants.Conventions.Media.Extension).Single().Value); + Assert.AreEqual(media.Properties[Constants.Conventions.Media.File].GetValue().ToString(), element.Elements(Constants.Conventions.Media.File).Single().Value); + Assert.AreEqual(media.Properties[Constants.Conventions.Media.Width].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Width).Single().Value); + Assert.AreEqual(media.Properties[Constants.Conventions.Media.Height].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Height).Single().Value); + Assert.AreEqual(media.Properties[Constants.Conventions.Media.Bytes].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Bytes).Single().Value); + Assert.AreEqual(media.Properties[Constants.Conventions.Media.Extension].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Extension).Single().Value); } } } diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs index 1d7e08f428..f05948d9c8 100644 --- a/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs @@ -193,14 +193,14 @@ namespace Umbraco.Tests.Persistence.NPocoTests { var expected = Sql(); expected.SelectAll() - .From("[cmsDocument]") + .From("[cmsDocumentVersion]") .InnerJoin("[cmsContentVersion]") - .On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]"); + .On("[cmsDocumentVersion].[id] = [cmsContentVersion].[id]"); var sql = Sql(); - sql.SelectAll().From() + sql.SelectAll().From() .InnerJoin() - .On(left => left.VersionId, right => right.VersionId); + .On(left => left.Id, right => right.Id); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index 5aae245ff2..87f034ee84 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs @@ -28,8 +28,9 @@ namespace Umbraco.Tests.Persistence.Querying var sql = Sql(); sql.SelectAll() .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) + // fixme DocumentDto does not have VersionId anymore + //.InnerJoin() + //.On(left => left.VersionId, right => right.VersionId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .InnerJoin() @@ -64,8 +65,9 @@ namespace Umbraco.Tests.Persistence.Querying var sql = Sql(); sql.SelectAll() .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) + // fixme DocumentDto does not have VersionId anymore + //.InnerJoin() + //.On(left => left.VersionId, right => right.VersionId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .InnerJoin() @@ -104,8 +106,9 @@ namespace Umbraco.Tests.Persistence.Querying var sql = Sql(); sql.SelectAll() .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) + // fixme DocumentDto does not have VersionId anymore + //.InnerJoin() + //.On(left => left.VersionId, right => right.VersionId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .InnerJoin() diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs index eacd194bd7..ae63eaa1ea 100644 --- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs @@ -103,8 +103,9 @@ namespace Umbraco.Tests.Persistence.Querying var sql = Sql(); sql.SelectAll() .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) + // fixme DocumentDto does not have VersionId anymore + //.InnerJoin() + //.On(left => left.VersionId, right => right.VersionId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .InnerJoin() diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index d5cd4acfa6..6b526c6ced 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -204,14 +204,15 @@ namespace Umbraco.Tests.Persistence.Repositories versionDtos.Add(versionDto); unitOfWork.Database.Insert(new DocumentDto { - Newest = true, + // fixme DocumentDto has changed! + //Newest = true, NodeId = content1.Id, Published = true, - Text = content1.Name, - VersionId = version, + //Text = content1.Name, + //VersionId = version, WriterUserId = 0, UpdateDate = versionDate, - TemplateId = content1.Template == null || content1.Template.Id <= 0 ? null : (int?)content1.Template.Id + //TemplateId = content1.Template == null || content1.Template.Id <= 0 ? null : (int?)content1.Template.Id }); var content = repository.GetByQuery(unitOfWork.SqlContext.Query().Where(c => c.Id == content1.Id)).ToArray(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 2aca3d073e..5cf3c09535 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -845,7 +845,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(3)); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "keywords"), Is.False); - Assert.That(subpage.Properties.First(x => x.Alias == "description").Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(subpage.Properties.First(x => x.Alias == "description").GetValue(), Is.EqualTo("This is the meta description for a textpage")); } } @@ -943,7 +943,7 @@ namespace Umbraco.Tests.Persistence.Repositories //Assert var updated = contentRepository.Get(subpage.Id); Assert.That(updated.GetValue("metaAuthor").ToString(), Is.EqualTo("John Doe")); - Assert.That(updated.Properties.First(x => x.Alias == "description").Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(updated.Properties.First(x => x.Alias == "description").GetValue(), Is.EqualTo("This is the meta description for a textpage")); Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(4)); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "metaAuthor"), Is.True); diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index 2b9e3272ef..2449729182 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var comparer = new SimilarNodeName.Comparer(); - var result = comparer.Compare(new SimilarNodeName { Name = name1 }, new SimilarNodeName { Name = name2 }); + var result = comparer.Compare(new SimilarNodeName { Text = name1 }, new SimilarNodeName { Text = name2 }); if (expected == 0) Assert.AreEqual(0, result); else if (expected < 0) @@ -38,16 +38,16 @@ namespace Umbraco.Tests.Persistence.Repositories { var names = new[] { - new SimilarNodeName { Id = 1, Name = "Alpha (2)" }, - new SimilarNodeName { Id = 2, Name = "Alpha" }, - new SimilarNodeName { Id = 3, Name = "Golf" }, - new SimilarNodeName { Id = 4, Name = "Zulu" }, - new SimilarNodeName { Id = 5, Name = "Mike" }, - new SimilarNodeName { Id = 6, Name = "Kilo (1)" }, - new SimilarNodeName { Id = 7, Name = "Yankee" }, - new SimilarNodeName { Id = 8, Name = "Kilo" }, - new SimilarNodeName { Id = 9, Name = "Golf (2)" }, - new SimilarNodeName { Id = 10, Name = "Alpha (1)" }, + new SimilarNodeName { Id = 1, Text = "Alpha (2)" }, + new SimilarNodeName { Id = 2, Text = "Alpha" }, + new SimilarNodeName { Id = 3, Text = "Golf" }, + new SimilarNodeName { Id = 4, Text = "Zulu" }, + new SimilarNodeName { Id = 5, Text = "Mike" }, + new SimilarNodeName { Id = 6, Text = "Kilo (1)" }, + new SimilarNodeName { Id = 7, Text = "Yankee" }, + new SimilarNodeName { Id = 8, Text = "Kilo" }, + new SimilarNodeName { Id = 9, Text = "Golf (2)" }, + new SimilarNodeName { Id = 10, Text = "Alpha (1)" }, }; var ordered = names.OrderBy(x => x, new SimilarNodeName.Comparer()).ToArray(); @@ -76,16 +76,16 @@ namespace Umbraco.Tests.Persistence.Repositories { var names = new[] { - new SimilarNodeName { Id = 1, Name = "Alpha (2)" }, - new SimilarNodeName { Id = 2, Name = "Alpha" }, - new SimilarNodeName { Id = 3, Name = "Golf" }, - new SimilarNodeName { Id = 4, Name = "Zulu" }, - new SimilarNodeName { Id = 5, Name = "Mike" }, - new SimilarNodeName { Id = 6, Name = "Kilo (1)" }, - new SimilarNodeName { Id = 7, Name = "Yankee" }, - new SimilarNodeName { Id = 8, Name = "Kilo" }, - new SimilarNodeName { Id = 9, Name = "Golf (2)" }, - new SimilarNodeName { Id = 10, Name = "Alpha (1)" }, + new SimilarNodeName { Id = 1, Text = "Alpha (2)" }, + new SimilarNodeName { Id = 2, Text = "Alpha" }, + new SimilarNodeName { Id = 3, Text = "Golf" }, + new SimilarNodeName { Id = 4, Text = "Zulu" }, + new SimilarNodeName { Id = 5, Text = "Mike" }, + new SimilarNodeName { Id = 6, Text = "Kilo (1)" }, + new SimilarNodeName { Id = 7, Text = "Yankee" }, + new SimilarNodeName { Id = 8, Text = "Kilo" }, + new SimilarNodeName { Id = 9, Text = "Golf (2)" }, + new SimilarNodeName { Id = 10, Text = "Alpha (1)" }, }; Assert.AreEqual(expected, SimilarNodeName.GetUniqueName(names, nodeId, nodeName)); diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 4e2cfc9b3e..a9286ca3f1 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -32,9 +32,8 @@ namespace Umbraco.Tests.PropertyEditors var dataTypeService = dataTypeServiceMock.Object; var editor = new PublishValuesMultipleValueEditor(true, dataTypeService, Mock.Of(), new PropertyValueEditor()); - var prop = new Property(1, Guid.NewGuid(), - new PropertyType(new DataTypeDefinition(1, "Test.TestEditor")), - "1234,4567,8910"); + var prop = new Property(1, new PropertyType(new DataTypeDefinition(1, "Test.TestEditor"))); + prop.SetValue("1234,4567,8910"); var result = editor.ConvertDbToString(prop, prop.PropertyType, new Mock().Object); @@ -58,9 +57,8 @@ namespace Umbraco.Tests.PropertyEditors var dataTypeService = dataTypeServiceMock.Object; var editor = new PublishValuesMultipleValueEditor(false, dataTypeService, Mock.Of(), new PropertyValueEditor()); - var prop = new Property(1, Guid.NewGuid(), - new PropertyType(new DataTypeDefinition(1, "Test.TestEditor")), - "1234,4567,8910"); + var prop = new Property(1, new PropertyType(new DataTypeDefinition(1, "Test.TestEditor"))); + prop.SetValue("1234,4567,8910"); var result = editor.ConvertDbToString(prop, prop.PropertyType, new Mock().Object); @@ -83,9 +81,8 @@ namespace Umbraco.Tests.PropertyEditors var dataTypeService = dataTypeServiceMock.Object; var editor = new PublishValueValueEditor(dataTypeService, new PropertyValueEditor(), Mock.Of()); - var prop = new Property(1, Guid.NewGuid(), - new PropertyType(new DataTypeDefinition(1, "Test.TestEditor")), - "1234"); + var prop = new Property(1, new PropertyType(new DataTypeDefinition(1, "Test.TestEditor"))); + prop.SetValue("1234"); var result = editor.ConvertDbToString(prop, prop.PropertyType, new Mock().Object); diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index 7fcfc98d9c..819bf2ad90 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -40,7 +40,8 @@ namespace Umbraco.Tests.PropertyEditors [TestCase("hello world", false)] public void Value_Editor_Can_Convert_To_Json_Object_For_Editor(string value, bool isOk) { - var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Nvarchar), value); + var prop = new Property(1, new PropertyType("test", DataTypeDatabaseType.Nvarchar)); + prop.SetValue(value); var valueEditor = new PropertyValueEditor { @@ -131,7 +132,8 @@ namespace Umbraco.Tests.PropertyEditors [TestCase(PropertyEditorValueTypes.DateTime, "", "")] //test empty string for date public void Value_Editor_Can_Serialize_Value(string valueType, object val, string expected) { - var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Nvarchar), val); + var prop = new Property(1, new PropertyType("test", DataTypeDatabaseType.Nvarchar)); + prop.SetValue(val); var valueEditor = new PropertyValueEditor { @@ -151,7 +153,8 @@ namespace Umbraco.Tests.PropertyEditors ValueType = PropertyEditorValueTypes.Decimal }; - var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Decimal), value); + var prop = new Property(1, new PropertyType("test", DataTypeDatabaseType.Decimal)); + prop.SetValue(value); var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); Assert.AreEqual("12.34", result); @@ -165,7 +168,8 @@ namespace Umbraco.Tests.PropertyEditors ValueType = PropertyEditorValueTypes.Decimal }; - var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Decimal), string.Empty); + var prop = new Property(1, new PropertyType("test", DataTypeDatabaseType.Decimal)); + prop.SetValue(string.Empty); var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); Assert.AreEqual(string.Empty, result); @@ -180,7 +184,8 @@ namespace Umbraco.Tests.PropertyEditors ValueType = PropertyEditorValueTypes.Date }; - var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Date), now); + var prop = new Property(1, new PropertyType("test", DataTypeDatabaseType.Date)); + prop.SetValue(now); var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); Assert.AreEqual(now.ToIsoString(), result); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index 10fb8bfc1e..178f4e1b84 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -88,7 +88,7 @@ namespace Umbraco.Tests.PublishedContent }); ServiceContext.MediaTypeService.Save(mType); var media = MockedMedia.CreateMediaImage(mType, -1); - media.Properties["content"].Value = "
This is some content
"; + media.Properties["content"].SetValue("
This is some content
"); ServiceContext.MediaService.Save(media); var publishedMedia = GetNode(media.Id); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 90222cdd5b..a71c782d83 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -123,10 +123,10 @@ namespace Umbraco.Tests.Services contentService.Save(fromBlueprint); Assert.IsTrue(fromBlueprint.HasIdentity); - Assert.AreEqual("blueprint 1", fromBlueprint.Properties["title"].Value); - Assert.AreEqual("blueprint 2", fromBlueprint.Properties["bodyText"].Value); - Assert.AreEqual("blueprint 3", fromBlueprint.Properties["keywords"].Value); - Assert.AreEqual("blueprint 4", fromBlueprint.Properties["description"].Value); + Assert.AreEqual("blueprint 1", fromBlueprint.Properties["title"].GetValue()); + Assert.AreEqual("blueprint 2", fromBlueprint.Properties["bodyText"].GetValue()); + Assert.AreEqual("blueprint 3", fromBlueprint.Properties["keywords"].GetValue()); + Assert.AreEqual("blueprint 4", fromBlueprint.Properties["description"].GetValue()); } [Test] @@ -729,7 +729,7 @@ namespace Umbraco.Tests.Services // Assert //the value will have changed but the tags db table will not have - Assert.AreEqual(5, content.Properties["tags"].Value.ToString().Split(',').Distinct().Count()); + Assert.AreEqual(5, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count()); var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (var scope = ScopeProvider.CreateScope()) { @@ -763,7 +763,7 @@ namespace Umbraco.Tests.Services contentService.Publish(content); // Assert - Assert.AreEqual(4, content.Properties["tags"].Value.ToString().Split(',').Distinct().Count()); + Assert.AreEqual(4, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count()); var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (var scope = ScopeProvider.CreateScope()) { @@ -797,7 +797,7 @@ namespace Umbraco.Tests.Services contentService.PublishWithStatus(content); // Assert - Assert.AreEqual(5, content.Properties["tags"].Value.ToString().Split(',').Distinct().Count()); + Assert.AreEqual(5, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count()); var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (var scope = ScopeProvider.CreateScope()) { @@ -831,7 +831,7 @@ namespace Umbraco.Tests.Services contentService.PublishWithStatus(content); // Assert - Assert.AreEqual(2, content.Properties["tags"].Value.ToString().Split(',').Distinct().Count()); + Assert.AreEqual(2, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count()); var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (var scope = ScopeProvider.CreateScope()) { @@ -1221,7 +1221,7 @@ namespace Umbraco.Tests.Services // Assert Assert.That(parentPublished, Is.True); Assert.That(published, Is.False); - Assert.That(content.IsValid(), Is.False); + Assert.That(content.Validate(), Is.False); Assert.That(parent.Published, Is.True); Assert.That(content.Published, Is.False); } @@ -1377,11 +1377,11 @@ namespace Umbraco.Tests.Services var rootPublished = contentService.Publish(root); var content = contentService.GetById(NodeDto.NodeIdSeed + 3); - content.Properties["title"].Value = content.Properties["title"].Value + " Published"; + content.Properties["title"].SetValue(content.Properties["title"].GetValue() + " Published"); var contentPublished = contentService.SaveAndPublish(content); var publishedVersion = content.Version; - content.Properties["title"].Value = content.Properties["title"].Value + " Saved"; + content.Properties["title"].SetValue(content.Properties["title"].GetValue() + " Saved"); contentService.Save(content); var savedVersion = content.Version; @@ -1401,17 +1401,17 @@ namespace Umbraco.Tests.Services //Ensure that the published content version has the correct property value and is marked as published var publishedContentVersion = publishedDescendants.First(x => x.Version == publishedVersion); Assert.That(publishedContentVersion.Published, Is.True); - Assert.That(publishedContentVersion.Properties["title"].Value, Contains.Substring("Published")); + Assert.That(publishedContentVersion.Properties["title"].GetValue(), Contains.Substring("Published")); //Ensure that the saved content version has the correct property value and is not marked as published var savedContentVersion = contentService.GetByVersion(savedVersion); Assert.That(savedContentVersion.Published, Is.False); - Assert.That(savedContentVersion.Properties["title"].Value, Contains.Substring("Saved")); + Assert.That(savedContentVersion.Properties["title"].GetValue(), Contains.Substring("Saved")); //Ensure that the latest version of the content is the saved and not-yet-published one var currentContent = contentService.GetById(NodeDto.NodeIdSeed + 3); Assert.That(currentContent.Published, Is.False); - Assert.That(currentContent.Properties["title"].Value, Contains.Substring("Saved")); + Assert.That(currentContent.Properties["title"].GetValue(), Contains.Substring("Saved")); Assert.That(currentContent.Version, Is.EqualTo(savedVersion)); } @@ -1759,7 +1759,7 @@ namespace Umbraco.Tests.Services foreach (var property in copy.Properties) { Assert.AreNotEqual(property.Id, content.Properties[property.Alias].Id); - Assert.AreEqual(property.Value, content.Properties[property.Alias].Value); + Assert.AreEqual(property.GetValue(), content.Properties[property.Alias].GetValue()); } //Assert.AreNotEqual(content.Name, copy.Name); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d482a3c8d8..7c28c01ed2 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -458,7 +458,6 @@ - diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index a298efb0fb..e0def34357 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -104,7 +104,7 @@ namespace Umbraco.Web.Editors //don't persist any bound value if the editor is readonly if (valueEditor.IsReadOnly == false) { - var propVal = property.PropertyEditor.ValueEditor.ConvertEditorToDb(data, dboProperty.Value); + var propVal = property.PropertyEditor.ValueEditor.ConvertEditorToDb(data, dboProperty.GetValue()); var supportTagsAttribute = TagExtractor.GetAttribute(property.PropertyEditor); if (supportTagsAttribute != null) { @@ -112,7 +112,7 @@ namespace Umbraco.Web.Editors } else { - dboProperty.Value = propVal; + dboProperty.SetValue(propVal); } } diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index 861a157cd8..ee4d4658cd 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -40,7 +40,7 @@ namespace Umbraco.Web.Editors if (imageProp == null) return Request.CreateResponse(HttpStatusCode.NotFound); - var imagePath = imageProp.Value.ToString(); + var imagePath = imageProp.GetValue().ToString(); return GetBigThumbnail(imagePath); } @@ -78,7 +78,7 @@ namespace Umbraco.Web.Editors if (imageProp == null) return new HttpResponseMessage(HttpStatusCode.NotFound); - var imagePath = imageProp.Value.ToString(); + var imagePath = imageProp.GetValue().ToString(); return GetResized(imagePath, width); } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 1546f6c4a6..890a52d108 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -529,7 +529,7 @@ namespace Umbraco.Web.Editors var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias); if (builtInAliases.Contains(p.Alias) == false && valueMapped != null) { - p.Value = valueMapped.Value; + p.SetValue(valueMapped.GetValue()); p.TagSupport.Behavior = valueMapped.TagSupport.Behavior; p.TagSupport.Enable = valueMapped.TagSupport.Enable; p.TagSupport.Tags = valueMapped.TagSupport.Tags; diff --git a/src/Umbraco.Web/Models/PublishedProperty.cs b/src/Umbraco.Web/Models/PublishedProperty.cs index 2ab288984a..8067e56532 100644 --- a/src/Umbraco.Web/Models/PublishedProperty.cs +++ b/src/Umbraco.Web/Models/PublishedProperty.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.Models return propertyTypes.Select(x => { var p = properties.SingleOrDefault(xx => xx.Alias == x.PropertyTypeAlias); - var v = p == null || p.Value == null ? null : p.Value; + var v = p == null || p.GetValue() == null ? null : p.GetValue(); if (v != null) { var e = propertyEditors[x.PropertyEditorAlias]; diff --git a/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs index 74227b5272..6f9ed2c036 100644 --- a/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - var date = property.Value.TryConvertTo(); + var date = property.GetValue().TryConvertTo(); if (date.Success == false || date.Result == null) { return string.Empty; diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index e63d2a5e50..8a36ee34b5 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.PropertyEditors return false; if (ensureValue == false) return true; - var stringValue = property.Value as string; + var stringValue = property.GetValue() as string; return string.IsNullOrWhiteSpace(stringValue) == false; } @@ -72,7 +72,7 @@ namespace Umbraco.Web.PropertyEditors { return allPropertyData.SelectMany(x => x.Value) .Where (x => IsUploadField(x, true)) - .Select(x => _mediaFileSystem.GetRelativePath((string)x.Value)) + .Select(x => _mediaFileSystem.GetRelativePath((string)x.GetValue())) .ToList(); } @@ -84,7 +84,7 @@ namespace Umbraco.Web.PropertyEditors { return deletedEntities.SelectMany(x => x.Properties) .Where(x => IsUploadField(x, true)) - .Select(x => _mediaFileSystem.GetRelativePath((string) x.Value)) + .Select(x => _mediaFileSystem.GetRelativePath((string) x.GetValue())) .ToList(); } @@ -102,7 +102,7 @@ namespace Umbraco.Web.PropertyEditors var isUpdated = false; foreach (var property in properties) { - var sourcePath = _mediaFileSystem.GetRelativePath((string) property.Value); + var sourcePath = _mediaFileSystem.GetRelativePath((string) property.GetValue()); var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath)); isUpdated = true; @@ -158,7 +158,7 @@ namespace Umbraco.Web.PropertyEditors var autoFillConfig = _mediaFileSystem.UploadAutoFillProperties.GetConfig(property.Alias); if (autoFillConfig == null) continue; - var svalue = property.Value as string; + var svalue = property.GetValue() as string; if (string.IsNullOrWhiteSpace(svalue)) _mediaFileSystem.UploadAutoFillProperties.Reset(model, autoFillConfig); else diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index cc3c16dd6c..ab1db28fe8 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -83,7 +83,7 @@ namespace Umbraco.Web.PropertyEditors return false; if (ensureValue == false) return true; - return property.Value is string && string.IsNullOrWhiteSpace((string)property.Value) == false; + return property.GetValue() is string && string.IsNullOrWhiteSpace((string) property.GetValue()) == false; } /// @@ -119,7 +119,7 @@ namespace Umbraco.Web.PropertyEditors return allPropertyData.SelectMany(x => x.Value) .Where(x => IsCropperField(x, true)).Select(x => { - var jo = GetJObject((string) x.Value, true); + var jo = GetJObject((string) x.GetValue(), true); if (jo?["src"] == null) return null; var src = jo["src"].Value(); return string.IsNullOrWhiteSpace(src) ? null : _mediaFileSystem.GetRelativePath(src); @@ -135,7 +135,7 @@ namespace Umbraco.Web.PropertyEditors return deletedEntities.SelectMany(x => x.Properties) .Where(x => IsCropperField(x, true)).Select(x => { - var jo = GetJObject((string) x.Value, true); + var jo = GetJObject((string) x.GetValue(), true); if (jo?["src"] == null) return null; var src = jo["src"].Value(); return string.IsNullOrWhiteSpace(src) ? null : _mediaFileSystem.GetRelativePath(src); @@ -156,7 +156,7 @@ namespace Umbraco.Web.PropertyEditors var isUpdated = false; foreach (var property in properties) { - var jo = GetJObject((string) property.Value, true); + var jo = GetJObject((string) property.GetValue(), true); if (jo == null || jo["src"] == null) continue; var src = jo["src"].Value(); @@ -218,7 +218,7 @@ namespace Umbraco.Web.PropertyEditors var autoFillConfig = _autoFillProperties.GetConfig(property.Alias); if (autoFillConfig == null) continue; - var svalue = property.Value as string; + var svalue = property.GetValue() as string; if (string.IsNullOrWhiteSpace(svalue)) { _autoFillProperties.Reset(model, autoFillConfig); @@ -238,7 +238,7 @@ namespace Umbraco.Web.PropertyEditors .GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId).FirstOrDefault(); var crops = string.IsNullOrWhiteSpace(config) ? "[]" : config; src = svalue; - property.Value = "{\"src\": \"" + svalue + "\", \"crops\": " + crops + "}"; + property.SetValue("{\"src\": \"" + svalue + "\", \"crops\": " + crops + "}"); } else { diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 0cd999e30b..a08cf42061 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -162,11 +162,11 @@ namespace Umbraco.Web.PropertyEditors public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null || string.IsNullOrEmpty(property.Value.ToString())) + if (property.GetValue() == null || string.IsNullOrEmpty(property.GetValue().ToString())) return null; // if we dont have a json structure, we will get it from the property type - var val = property.Value.ToString(); + var val = property.GetValue().ToString(); if (val.DetectIsJson()) return val; diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index e59712f011..dda8dcf857 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -182,9 +182,9 @@ namespace Umbraco.Web.PropertyEditors /// public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - return property.Value == null + return property.GetValue() == null ? new JObject[] {} - : property.Value.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) + : property.GetValue().ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select(x => JObject.FromObject(new {value = x})); diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 1b76f98a26..41fb070795 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -168,10 +168,10 @@ namespace Umbraco.Web.PropertyEditors public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { // Convert / validate value - if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString())) + if (property.GetValue() == null || string.IsNullOrWhiteSpace(property.GetValue().ToString())) return string.Empty; - var value = JsonConvert.DeserializeObject>(property.Value.ToString()); + var value = JsonConvert.DeserializeObject>(property.GetValue().ToString()); if (value == null) return string.Empty; @@ -206,7 +206,8 @@ namespace Umbraco.Web.PropertyEditors try { // Create a fake property using the property abd stored value - var prop = new Property(propType, propValues[propKey] == null ? null : propValues[propKey].ToString()); + var prop = new Property(propType); + prop.SetValue(propValues[propKey] == null ? null : propValues[propKey].ToString()); // Lookup the property editor var propEditor = _propertyEditors[propType.PropertyEditorAlias]; @@ -228,7 +229,7 @@ namespace Umbraco.Web.PropertyEditors } // Update the value on the property - property.Value = JsonConvert.SerializeObject(value); + property.SetValue(JsonConvert.SerializeObject(value)); // Pass the call down return base.ConvertDbToString(property, propertyType, dataTypeService); @@ -240,10 +241,10 @@ namespace Umbraco.Web.PropertyEditors public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString())) + if (property.GetValue() == null || string.IsNullOrWhiteSpace(property.GetValue().ToString())) return string.Empty; - var value = JsonConvert.DeserializeObject>(property.Value.ToString()); + var value = JsonConvert.DeserializeObject>(property.GetValue().ToString()); if (value == null) return string.Empty; @@ -278,7 +279,8 @@ namespace Umbraco.Web.PropertyEditors try { // Create a fake property using the property and stored value - var prop = new Property(propType, propValues[propKey] == null ? null : propValues[propKey].ToString()); + var prop = new Property(propType); + prop.SetValue(propValues[propKey] == null ? null : propValues[propKey].ToString()); // Lookup the property editor var propEditor = _propertyEditors[propType.PropertyEditorAlias]; @@ -303,7 +305,7 @@ namespace Umbraco.Web.PropertyEditors } // Update the value on the property - property.Value = JsonConvert.SerializeObject(value); + property.SetValue(JsonConvert.SerializeObject(value)); // Pass the call down return base.ConvertDbToEditor(property, propertyType, dataTypeService); diff --git a/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs index ab532bd7a3..79af2d61d9 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs @@ -42,10 +42,10 @@ namespace Umbraco.Web.PropertyEditors /// public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null) + if (property.GetValue() == null) return null; - var idAttempt = property.Value.TryConvertTo(); + var idAttempt = property.GetValue().TryConvertTo(); if (idAttempt.Success) { var preValId = idAttempt.Result; diff --git a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs index 23519d6a99..eca7e170e1 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs @@ -40,17 +40,17 @@ namespace Umbraco.Web.PropertyEditors /// public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null) + if (property.GetValue() == null) return null; //publishing ids, so just need to return the value as-is if (_publishIds) { - return property.Value.ToString(); + return property.GetValue().ToString(); } //get the multiple ids - var selectedIds = property.Value.ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + var selectedIds = property.GetValue().ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); if (selectedIds.Any() == false) { //nothing there diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index e6bba13bb9..966935dca5 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -70,10 +70,10 @@ namespace Umbraco.Web.PropertyEditors /// public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null) + if (property.GetValue() == null) return null; - var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(property.Value.ToString(), new Dictionary()); + var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(property.GetValue().ToString(), new Dictionary()); return parsed; } diff --git a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs index 935f045092..fb1b709871 100644 --- a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs @@ -27,13 +27,13 @@ namespace Umbraco.Web.PropertyEditors /// public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { - if (property.Value == null) return string.Empty; + if (property.GetValue() == null) return string.Empty; switch (GetDatabaseType()) { case DataTypeDatabaseType.Ntext: case DataTypeDatabaseType.Nvarchar: - return property.Value.ToString(); + return property.GetValue().ToString(); case DataTypeDatabaseType.Integer: case DataTypeDatabaseType.Decimal: case DataTypeDatabaseType.Date: diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs index 186741f4ef..f816a5b067 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs @@ -21,9 +21,9 @@ n.id Id, n.uniqueId Uid, uContent.contentTypeId ContentTypeId, n.level Level, n.path Path, n.sortOrder SortOrder, n.parentId ParentId, n.createDate CreateDate, n.nodeUser CreatorId, -docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.documentUser DraftWriterId, docDraft.templateId DraftTemplateId, +docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.writerUserId DraftWriterId, docDraft.templateId DraftTemplateId, nuDraft.data DraftData, -docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.documentUser PubWriterId, docPub.templateId PubTemplateId, +docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.writerUserId PubWriterId, docPub.templateId PubTemplateId, nuPub.data PubData FROM umbracoNode n JOIN uContent ON (uContent.nodeId=n.id) @@ -65,9 +65,9 @@ n.id Id, n.uniqueId Uid, uContent.contentTypeId ContentTypeId, n.level Level, n.path Path, n.sortOrder SortOrder, n.parentId ParentId, n.createDate CreateDate, n.nodeUser CreatorId, -docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.documentUser DraftWriterId, docDraft.templateId DraftTemplateId, +docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.writerUserId DraftWriterId, docDraft.templateId DraftTemplateId, nuDraft.data DraftData, -docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.documentUser PubWriterId, docPub.templateId PubTemplateId, +docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.writerUserId PubWriterId, docPub.templateId PubTemplateId, nuPub.data PubData FROM umbracoNode n JOIN uContent ON (uContent.nodeId=n.id) @@ -107,9 +107,9 @@ n.id Id, n.uniqueId Uid, uContent.contentTypeId ContentTypeId, n.level Level, n.path Path, n.sortOrder SortOrder, n.parentId ParentId, n.createDate CreateDate, n.nodeUser CreatorId, -docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.documentUser DraftWriterId, docDraft.templateId DraftTemplateId, +docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.writerUserId DraftWriterId, docDraft.templateId DraftTemplateId, nuDraft.data DraftData, -docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.documentUser PubWriterId, docPub.templateId PubTemplateId, +docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.writerUserId PubWriterId, docPub.templateId PubTemplateId, nuPub.data PubData FROM umbracoNode n JOIN umbracoNode x ON (n.id=x.id OR n.path LIKE " + uow.SqlContext.SqlSyntax.GetConcat("x.path", "',%'") + @") @@ -151,9 +151,9 @@ n.id Id, n.uniqueId Uid, uContent.contentTypeId ContentTypeId, n.level Level, n.path Path, n.sortOrder SortOrder, n.parentId ParentId, n.createDate CreateDate, n.nodeUser CreatorId, -docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.documentUser DraftWriterId, docDraft.templateId DraftTemplateId, +docDraft.text DraftName, docDraft.versionId DraftVersion, docDraft.updateDate DraftVersionDate, docDraft.writerUserId DraftWriterId, docDraft.templateId DraftTemplateId, nuDraft.data DraftData, -docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.documentUser PubWriterId, docPub.templateId PubTemplateId, +docPub.text PubName, docPub.versionId PubVersion, docPub.updateDate PubVersionDate, docPub.writerUserId PubWriterId, docPub.templateId PubTemplateId, nuPub.data PubData FROM umbracoNode n JOIN uContent ON (uContent.nodeId=n.id) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs index 9b325331b9..8bdce0fb26 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // return new KeyValuePair(property.Alias, v); //}) //.ToDictionary(x => x.Key, x => x.Value); - .ToDictionary(x => x.Alias, x => x.Value, StringComparer.OrdinalIgnoreCase); + .ToDictionary(x => x.Alias, x => x.GetValue(), StringComparer.OrdinalIgnoreCase); // see also PublishedContentType AddIf(contentType, properties, "Email", member.Email); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 9e74feee10..6acaf3772b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1182,7 +1182,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var data = new Dictionary(); foreach (var prop in content.Properties) { - var value = prop.Value; + var value = prop.GetValue(); //if (value != null) //{ // var e = propertyEditorResolver.GetByAlias(prop.PropertyType.PropertyEditorAlias); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 8e11ef5487..016e25b8eb 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -450,7 +450,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // add the user props foreach (var prop in media.Properties) - values[prop.Alias] = prop.Value?.ToString(); + values[prop.Alias] = prop.GetValue()?.ToString(); return new CacheValues { diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index c783119483..b5eda20d33 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -225,7 +225,7 @@ namespace Umbraco.Web.Security //needs to be editable .Where(p => member.ContentType.MemberCanEditProperty(p.Alias))) { - member.Properties[property.Alias].Value = property.Value; + member.Properties[property.Alias].SetValue(property.Value); } } @@ -272,7 +272,7 @@ namespace Umbraco.Web.Security foreach (var property in model.MemberProperties.Where(p => p.Value != null) .Where(property => member.Properties.Contains(property.Alias))) { - member.Properties[property.Alias].Value = property.Value; + member.Properties[property.Alias].SetValue(property.Value); } } @@ -509,9 +509,9 @@ namespace Umbraco.Web.Security if (member != null) { var propValue = member.Properties[prop.Alias]; - if (propValue != null && propValue.Value != null) + if (propValue != null && propValue.GetValue() != null) { - value = propValue.Value.ToString(); + value = propValue.GetValue().ToString(); } } diff --git a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs deleted file mode 100644 index b0e7e18394..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// This event ensures that upgrades from (configured) versions lower then 6.0.0 - /// have their publish state updated after the database schema has been migrated. - /// - public class PublishAfterUpgradeToVersionSixth : IPostMigration - { - public void Migrated(MigrationRunner sender, MigrationEventArgs args) - { - if (args.ProductName != Constants.System.UmbracoMigrationName) return; - - var target = new Version(6, 0, 0); - if (args.ConfiguredVersion < target) - { - var sql = args.MigrationContext.SqlContext.Sql() - .SelectAll() - .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document) - .Where(x => x.Path.StartsWith("-1")); - - var dtos = args.MigrationContext.Database.Fetch(sql); - var toUpdate = new List(); - var versionGroup = dtos.GroupBy(x => x.NodeId); - foreach (var grp in versionGroup) - { - var published = grp.FirstOrDefault(x => x.Published); - var newest = grp.FirstOrDefault(x => x.Newest); - - if (newest != null) - { - double timeDiff = new TimeSpan(newest.UpdateDate.Ticks - newest.ContentVersionDto.VersionDate.Ticks).TotalMilliseconds; - var hasPendingChanges = timeDiff > 2000; - - if (hasPendingChanges == false && published != null) - { - published.Published = false; - toUpdate.Add(published); - newest.Published = true; - toUpdate.Add(newest); - } - } - } - - //Commit the updated entries for the cmsDocument table - using (var transaction = args.MigrationContext.Database.GetTransaction()) - { - //Loop through the toUpdate - foreach (var dto in toUpdate) - { - args.MigrationContext.Database.Update(dto); - } - - transaction.Complete(); - } - } - } - - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c4291b57d3..42335c0133 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1025,7 +1025,6 @@ - diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index d30c23e42f..efe80824ab 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -372,7 +372,7 @@ namespace umbraco public PagePublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, Umbraco.Core.Models.Property property) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { - _sourceValue = property.Value; + _sourceValue = property.GetValue(); _content = content; }