diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index d71ccb04b9..3719bb0750 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -10,6 +10,18 @@ namespace Umbraco.Core /// public static class EnumerableExtensions { + internal static bool HasDuplicates(this IEnumerable items, bool includeNull) + { + var hs = new HashSet(); + foreach (var item in items) + { + if ((item != null || includeNull) && !hs.Add(item)) + return true; + } + return false; + } + + /// /// Wraps this object instance into an IEnumerable{T} consisting of a single item. /// @@ -100,7 +112,7 @@ namespace Umbraco.Core } } - + /// /// Returns true if all items in the other collection exist in this collection /// diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index f0e6dd2e5b..1de983636b 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -226,6 +226,8 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index d4e35cccdf..04bcb7424a 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -95,6 +95,21 @@ namespace Umbraco.Core.Models protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { + //enable this to detect duplicate property aliases. We do want this, however making this change in a + //patch release might be a little dangerous + + ////detect if there are any duplicate aliases - this cannot be allowed + //if (e.Action == NotifyCollectionChangedAction.Add + // || e.Action == NotifyCollectionChangedAction.Replace) + //{ + // var allAliases = _noGroupPropertyTypes.Concat(PropertyGroups.SelectMany(x => x.PropertyTypes)).Select(x => x.Alias); + // if (allAliases.HasDuplicates(false)) + // { + // var newAliases = string.Join(", ", e.NewItems.Cast().Select(x => x.Alias)); + // throw new InvalidOperationException($"Other property types already exist with the aliases: {newAliases}"); + // } + //} + OnPropertyChanged(nameof(PropertyTypes)); } @@ -388,15 +403,16 @@ namespace Umbraco.Core.Models var group = PropertyGroups[propertyGroupName]; if (group == null) return; - // re-assign the group's properties to no group + // first remove the group + PropertyGroups.RemoveItem(propertyGroupName); + + // Then re-assign the group's properties to no group foreach (var property in group.PropertyTypes) { property.PropertyGroupId = null; _noGroupPropertyTypes.Add(property); } - // actually remove the group - PropertyGroups.RemoveItem(propertyGroupName); OnPropertyChanged(nameof(PropertyGroups)); } diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 977600a2f7..c587a45424 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Models public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { private readonly object _addLocker = new object(); - internal Action OnAdd; + internal Func AdditionValidator { get; set; } /// @@ -49,10 +49,12 @@ namespace Umbraco.Core.Models /// internal void Reset(IEnumerable properties) { + //collection events will be raised in each of these calls Clear(); + + //collection events will be raised in each of these calls foreach (var property in properties) Add(property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// @@ -60,8 +62,9 @@ namespace Umbraco.Core.Models /// protected override void SetItem(int index, Property property) { + var oldItem = index >= 0 ? this[index] : property; base.SetItem(index, property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property, index)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, property, oldItem)); } /// @@ -120,10 +123,8 @@ namespace Umbraco.Core.Models } } + //collection events will be raised in InsertItem with Add base.Add(property); - - OnAdd?.Invoke(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); } } diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 26e0fef178..5422dfb792 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -19,9 +19,6 @@ namespace Umbraco.Core.Models { private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - // TODO: this doesn't seem to be used anywhere - internal Action OnAdd; - internal PropertyGroupCollection() { } @@ -37,16 +34,19 @@ namespace Umbraco.Core.Models /// internal void Reset(IEnumerable groups) { + //collection events will be raised in each of these calls Clear(); + + //collection events will be raised in each of these calls foreach (var group in groups) Add(group); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } protected override void SetItem(int index, PropertyGroup item) { + var oldItem = index >= 0 ? this[index] : item; base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); } protected override void RemoveItem(int index) @@ -84,6 +84,7 @@ namespace Umbraco.Core.Models if (keyExists) throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates"); + //collection events will be raised in SetItem SetItem(IndexOfKey(item.Id), item); return; } @@ -96,16 +97,14 @@ namespace Umbraco.Core.Models var exists = Contains(key); if (exists) { + //collection events will be raised in SetItem SetItem(IndexOfKey(key), item); return; } } } - + //collection events will be raised in InsertItem base.Add(item); - OnAdd?.Invoke(); - - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } finally { diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 6181ee078b..6e41f0d12b 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -19,9 +19,6 @@ namespace Umbraco.Core.Models [IgnoreDataMember] private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - // TODO: This doesn't seem to be used - [IgnoreDataMember] - internal Action OnAdd; internal PropertyTypeCollection(bool supportsPublishing) { @@ -43,36 +40,44 @@ namespace Umbraco.Core.Models /// internal void Reset(IEnumerable properties) { + //collection events will be raised in each of these calls Clear(); + + //collection events will be raised in each of these calls foreach (var property in properties) - Add(property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Add(property); } protected override void SetItem(int index, PropertyType item) { item.SupportsPublishing = SupportsPublishing; - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + var oldItem = index >= 0 ? this[index] : item; + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); + item.PropertyChanged += Item_PropertyChanged; } protected override void RemoveItem(int index) { var removed = this[index]; base.RemoveItem(index); + removed.PropertyChanged -= Item_PropertyChanged; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } protected override void InsertItem(int index, PropertyType item) { item.SupportsPublishing = SupportsPublishing; - base.InsertItem(index, item); + base.InsertItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + item.PropertyChanged += Item_PropertyChanged; } protected override void ClearItems() { base.ClearItems(); + foreach (var item in this) + item.PropertyChanged -= Item_PropertyChanged; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } @@ -91,6 +96,7 @@ namespace Umbraco.Core.Models var exists = Contains(key); if (exists) { + //collection events will be raised in SetItem SetItem(IndexOfKey(key), item); return; } @@ -103,10 +109,8 @@ namespace Umbraco.Core.Models item.SortOrder = this.Max(x => x.SortOrder) + 1; } + //collection events will be raised in InsertItem base.Add(item); - OnAdd?.Invoke(); - - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } finally { @@ -115,6 +119,17 @@ namespace Umbraco.Core.Models } } + /// + /// Occurs when a property changes on a PropertyType that exists in this collection + /// + /// + /// + private void Item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + var propType = (PropertyType)sender; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, propType, propType)); + } + /// /// Determines whether this collection contains a whose alias matches the specified PropertyType. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 194a00b7f2..645ab9f924 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -240,6 +240,25 @@ namespace Umbraco.Core.Persistence.Repositories.Implement propertyIx++; } contentType.NoGroupPropertyTypes = noGroupPropertyTypes; + + // ensure builtin properties + if (contentType is MemberType memberType) + { + // ensure that the group exists (ok if it already exists) + memberType.AddPropertyGroup(Constants.Conventions.Member.StandardPropertiesGroupName); + + // ensure that property types exist (ok if they already exist) + foreach (var (alias, propertyType) in builtinProperties) + { + var added = memberType.AddPropertyType(propertyType, Constants.Conventions.Member.StandardPropertiesGroupName); + + if (added) + { + var access = new MemberTypePropertyProfileAccess(false, false, false); + memberType.MemberTypePropertyTypes[alias] = access; + } + } + } } } @@ -264,7 +283,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (contentType is MemberType memberType) { var access = new MemberTypePropertyProfileAccess(dto.ViewOnProfile, dto.CanEdit, dto.IsSensitive); - memberType.MemberTypePropertyTypes.Add(dto.Alias, access); + memberType.MemberTypePropertyTypes[dto.Alias] = access; } return new PropertyType(dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, dto.Alias) diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 7b7574ce47..7459ae848b 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,10 +27,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - _typeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of())); - - foreach (var file in Directory.GetFiles(IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "TypesCache"))) - File.Delete(file); + _typeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()), false); // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver // TODO: Should probably update this so it only searches this assembly and add custom types to be found diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index d9e65ba6c6..9c3b976bf3 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -15,13 +15,47 @@ namespace Umbraco.Tests.Models [TestFixture] public class ContentTypeTests : UmbracoTestBase { + [Test] + [Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")] + public void Cannot_Add_Duplicate_Property_Aliases() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.PropertyGroups.Add(new PropertyGroup(new PropertyTypeCollection(false, new[] + { + new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar){ Alias = "myPropertyType" } + }))); + + Assert.Throws(() => + contentType.PropertyTypeCollection.Add( + new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar) { Alias = "myPropertyType" })); + + } + + [Test] + [Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")] + public void Cannot_Update_Duplicate_Property_Aliases() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + + contentType.PropertyGroups.Add(new PropertyGroup(new PropertyTypeCollection(false, new[] + { + new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar){ Alias = "myPropertyType" } + }))); + + contentType.PropertyTypeCollection.Add(new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar) { Alias = "myPropertyType2" }); + + var toUpdate = contentType.PropertyTypeCollection["myPropertyType2"]; + + Assert.Throws(() => toUpdate.Alias = "myPropertyType"); + + } [Test] public void Can_Deep_Clone_Content_Type_Sort() { var contentType = new ContentTypeSort(new Lazy(() => 3), 4, "test"); - var clone = (ContentTypeSort) contentType.DeepClone(); + var clone = (ContentTypeSort)contentType.DeepClone(); Assert.AreNotSame(clone, contentType); Assert.AreEqual(clone, contentType); Assert.AreEqual(clone.Id.Value, contentType.Id.Value); @@ -54,7 +88,7 @@ namespace Umbraco.Tests.Models contentType.Id = 10; contentType.CreateDate = DateTime.Now; contentType.CreatorId = 22; - contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate") + contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate") { Id = 88 }); @@ -117,12 +151,12 @@ namespace Umbraco.Tests.Models { group.Id = ++i; } - contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } }; + contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } }; contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2") }; contentType.Id = 10; contentType.CreateDate = DateTime.Now; contentType.CreatorId = 22; - contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate") + contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate") { Id = 88 }); @@ -167,12 +201,12 @@ namespace Umbraco.Tests.Models { group.Id = ++i; } - contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } }; - contentType.AllowedContentTypes = new[] {new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2")}; + contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } }; + contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2") }; contentType.Id = 10; contentType.CreateDate = DateTime.Now; contentType.CreatorId = 22; - contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate") + contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate") { Id = 88 }); @@ -264,12 +298,12 @@ namespace Umbraco.Tests.Models { propertyType.Id = ++i; } - contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } }; + contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } }; contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2") }; contentType.Id = 10; contentType.CreateDate = DateTime.Now; contentType.CreatorId = 22; - contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate") + contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate") { Id = 88 }); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 2b3ab50a22..79e8e43804 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -171,7 +171,6 @@ namespace Umbraco.Tests.Persistence.Repositories } } - //NOTE: This tests for left join logic (rev 7b14e8eacc65f82d4f184ef46c23340c09569052) [Test] public void Can_Get_All_Members_When_No_Properties_Assigned() @@ -200,7 +199,6 @@ namespace Umbraco.Tests.Persistence.Repositories } } - [Test] public void Can_Get_Member_Type_By_Id() { @@ -233,22 +231,114 @@ namespace Umbraco.Tests.Persistence.Repositories } } + // See: https://github.com/umbraco/Umbraco-CMS/issues/4963#issuecomment-483516698 + [Test] + public void Bug_Changing_Built_In_Member_Type_Property_Type_Aliases_Results_In_Exception() + { + var stubs = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + + var provider = TestObjects.GetScopeProvider(Logger); + using (provider.CreateScope()) + { + var repository = CreateRepository(provider); + + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType("mtype"); + + // created without the stub properties + Assert.AreEqual(1, memberType.PropertyGroups.Count); + Assert.AreEqual(3, memberType.PropertyTypes.Count()); + + // saving *new* member type adds the stub properties + repository.Save(memberType); + + // saving has added (and saved) the stub properties + Assert.AreEqual(2, memberType.PropertyGroups.Count); + Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count()); + + foreach (var stub in stubs) + { + var prop = memberType.PropertyTypes.First(x => x.Alias == stub.Key); + prop.Alias = prop.Alias + "__0000"; + } + + // saving *existing* member type does *not* ensure stub properties + repository.Save(memberType); + + // therefore, nothing has changed + Assert.AreEqual(2, memberType.PropertyGroups.Count); + Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count()); + + // fetching ensures that the stub properties are there + memberType = repository.Get("mtype"); + Assert.IsNotNull(memberType); + + Assert.AreEqual(2, memberType.PropertyGroups.Count); + Assert.AreEqual(3 + stubs.Count * 2, memberType.PropertyTypes.Count()); + } + } + [Test] public void Built_In_Member_Type_Properties_Are_Automatically_Added_When_Creating() { + var stubs = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (provider.CreateScope()) { var repository = CreateRepository(provider); IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + + // created without the stub properties + Assert.AreEqual(1, memberType.PropertyGroups.Count); + Assert.AreEqual(3, memberType.PropertyTypes.Count()); + + // saving *new* member type adds the stub properties repository.Save(memberType); + // saving has added (and saved) the stub properties + Assert.AreEqual(2, memberType.PropertyGroups.Count); + Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count()); + // getting with stub properties memberType = repository.Get(memberType.Id); - Assert.That(memberType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); - Assert.That(memberType.PropertyGroups.Count(), Is.EqualTo(2)); + Assert.AreEqual(2, memberType.PropertyGroups.Count); + Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count()); + } + } + + [Test] + public void Built_In_Member_Type_Properties_Missing_Are_Automatically_Added_When_Creating() + { + var stubs = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + + var provider = TestObjects.GetScopeProvider(Logger); + using (provider.CreateScope()) + { + var repository = CreateRepository(provider); + + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + + // created without the stub properties + Assert.AreEqual(1, memberType.PropertyGroups.Count); + Assert.AreEqual(3, memberType.PropertyTypes.Count()); + + // add one stub property, others are still missing + memberType.AddPropertyType(stubs.First().Value, Constants.Conventions.Member.StandardPropertiesGroupName); + + // saving *new* member type adds the (missing) stub properties + repository.Save(memberType); + + // saving has added (and saved) the (missing) stub properties + Assert.AreEqual(2, memberType.PropertyGroups.Count); + Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count()); + + // getting with stub properties + memberType = repository.Get(memberType.Id); + + Assert.AreEqual(2, memberType.PropertyGroups.Count); + Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count()); } } diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 51f87d09dd..52a573d8c4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -52,7 +52,7 @@ bottom: 0px; left: 0px; right: 0px; - position: absolute; + position: absolute; } .--notInFront .umb-modalcolumn::after { @@ -67,7 +67,7 @@ /* re-align loader */ .umb-modal .umb-loader-wrapper, .umb-modalcolumn .umb-loader-wrapper, .umb-dialog .umb-loader-wrapper{ - position:relative; + position:relative; margin: 20px -20px; } @@ -106,7 +106,7 @@ .umb-dialog-footer{ position: absolute; overflow:auto; - text-align: right; + text-align: right; height: 32px; left: 0px; right: 0px; @@ -126,7 +126,7 @@ .umbracoDialog form{height: 100%;} /*ensures dialogs doesnt have side-by-side labels*/ -.umbracoDialog .controls-row, +.umbracoDialog .controls-row, .umb-modal .controls-row{margin-left: 0px !important;} /* modal and umb-modal are used for right.hand dialogs */ @@ -174,7 +174,7 @@ padding: 20px; background: @white; border: none; - height: auto; + height: auto; } .umb-modal .umb-panel-body{ padding: 0px 20px 0px 20px; @@ -186,7 +186,7 @@ } .umb-modal i { font-size: 20px; -} +} .umb-modal .breadcrumb { background: none; padding: 0 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index b13f55bf7a..78fe096bd1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1159,7 +1159,7 @@ To manage your website, simply open the Umbraco back office and start adding con Downloads Likes Compatibility - This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100% + This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100% External sources Author Documentation diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 818e8ecf77..6596fee245 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; using Umbraco.Web.Composing; +using System.ComponentModel; namespace Umbraco.Web { @@ -26,6 +27,13 @@ namespace Umbraco.Web { private readonly IUmbracoDatabaseFactory _databaseFactory; + [Obsolete("This overload should not be used, enableDistCalls has no effect")] + [EditorBrowsable(EditorBrowsableState.Never)] + public BatchedDatabaseServerMessenger( + IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, bool enableDistCalls, DatabaseServerMessengerOptions options) + : this(runtime, databaseFactory, scopeProvider, sqlContext, proflog, globalSettings, options) + { } + public BatchedDatabaseServerMessenger( IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, DatabaseServerMessengerOptions options) : base(runtime, scopeProvider, sqlContext, proflog, globalSettings, true, options)