diff --git a/build/Build.bat b/build/Build.bat index 0154866703..107edbf7ea 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -1,5 +1,9 @@ @ECHO OFF -SET release=7.1.2 +IF NOT EXIST UmbracoVersion.txt ( + ECHO UmbracoVersion.txt missing! + GOTO :showerror +) +SET /p release= - diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat index 99958075f9..e323abbb0d 100644 --- a/build/BuildBelle.bat +++ b/build/BuildBelle.bat @@ -1,4 +1,5 @@ @ECHO OFF +SET release=%1 ECHO Installing Npm NuGet Package SET nuGetFolder=%CD%\..\src\packages\ @@ -26,7 +27,7 @@ ECHO Do npm install and the grunt build of Belle call npm install call npm install -g grunt-cli call npm install -g bower -call grunt build --buildversion=7.1.2 +call grunt build --buildversion=%release% ECHO Reset path to what it was before path=%oldPath% diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index bb0746e8bc..b770d8cfdc 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -67,5 +67,6 @@ + \ No newline at end of file diff --git a/build/NuSpecs/build/UmbracoCms.props b/build/NuSpecs/build/UmbracoCms.props index 5e11945a9d..1422a4cd76 100644 --- a/build/NuSpecs/build/UmbracoCms.props +++ b/build/NuSpecs/build/UmbracoCms.props @@ -1,12 +1,11 @@ - + AddUmbracoFilesToOutput; $(CopyAllFilesToSingleFolderForPackageDependsOn); - - + AddUmbracoFilesToOutput; $(CopyAllFilesToSingleFolderForPackageDependsOn); diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets index 721f40c976..024d8af7ad 100644 --- a/build/NuSpecs/build/UmbracoCms.targets +++ b/build/NuSpecs/build/UmbracoCms.targets @@ -1,11 +1,8 @@  - - 7.1.2 - - ..\packages\UmbracoCms.$(UmbracoVersion)\UmbracoFiles\ + $(MSBuildThisFileDirectory)..\UmbracoFiles\ @@ -33,6 +30,9 @@ Config\Splashes + + data + umbraco diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 new file mode 100644 index 0000000000..c712b363bc --- /dev/null +++ b/build/NuSpecs/tools/install.core.ps1 @@ -0,0 +1,24 @@ +param($rootPath, $toolsPath, $package, $project) + +if ($project) { + $dateTime = Get-Date -Format yyyyMMdd-HHmmss + $backupPath = Join-Path (Split-Path $project.FullName -Parent) "\App_Data\NuGetBackup\$dateTime" + $copyLogsPath = Join-Path $backupPath "CopyLogs" + $projectDestinationPath = Split-Path $project.FullName -Parent + + # Create backup folder and logs folder if it doesn't exist yet + New-Item -ItemType Directory -Force -Path $backupPath + New-Item -ItemType Directory -Force -Path $copyLogsPath + + # After backing up, remove all dlls from bin folder in case dll files are included in the VS project + # See: http://issues.umbraco.org/issue/U4-4930 + $umbracoBinFolder = Join-Path $projectDestinationPath "bin" + if(Test-Path $umbracoBinFolder) { + $umbracoBinBackupPath = Join-Path $backupPath "bin" + + New-Item -ItemType Directory -Force -Path $umbracoBinBackupPath + + robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log + Remove-Item $umbracoBinFolder\*.dll -Force -Confirm:$false + } +} \ No newline at end of file diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt new file mode 100644 index 0000000000..ad955e95b4 --- /dev/null +++ b/build/UmbracoVersion.txt @@ -0,0 +1 @@ +7.1.3 \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index ebaca4ed81..3c15b52de9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.1.2"); + private static readonly Version Version = new Version("7.1.3"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 1cf7fc9684..ec763d07fa 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -133,29 +133,6 @@ namespace Umbraco.Core.Models Key = Guid.NewGuid(); } - /// - /// Method to call when Entity is being updated - /// - /// Modified Date is set and a new Version guid is set - internal override void UpdatingEntity() - { - base.UpdatingEntity(); - } - - public override object DeepClone() - { - var clone = (ContentType)base.DeepClone(); - var propertyGroups = PropertyGroups.Select(x => (PropertyGroup)x.DeepClone()).ToList(); - clone.PropertyGroups = new PropertyGroupCollection(propertyGroups); - //set the property types that are not part of a group - clone.PropertyTypes = PropertyTypeCollection - .Where(x => x.PropertyGroupId == null) - .Select(x => (PropertyType)x.DeepClone()).ToList(); - - clone.ResetDirtyProperties(false); - - return clone; - } /// /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 00e9c1e30c..5c09deadb8 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -331,11 +331,6 @@ namespace Umbraco.Core.Models get { return _additionalData; } } - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - public IDictionary AdditionalData { get; private set; } - /// /// Gets or sets a list of integer Ids for allowed ContentTypes /// @@ -356,7 +351,9 @@ namespace Umbraco.Core.Models /// /// List of PropertyGroups available on this ContentType /// - /// A PropertyGroup corresponds to a Tab in the UI + /// + /// A PropertyGroup corresponds to a Tab in the UI + /// [DataMember] public virtual PropertyGroupCollection PropertyGroups { @@ -372,7 +369,13 @@ namespace Umbraco.Core.Models /// List of PropertyTypes available on this ContentType. /// This list aggregates PropertyTypes across the PropertyGroups. /// + /// + /// Marked as DoNotClone because the result of this property is not the natural result of the data, it is + /// a union of data so when auto-cloning if the setter is used it will be setting the unnatural result of the + /// data. We manually clone this instead. + /// [IgnoreDataMember] + [DoNotClone] public virtual IEnumerable PropertyTypes { get @@ -388,6 +391,14 @@ namespace Umbraco.Core.Models } /// + /// Returns the property type collection containing types that are non-groups - used for tests + /// + internal IEnumerable NonGroupedPropertyTypes + { + get { return _propertyTypes; } + } + + /// /// A boolean flag indicating if a property type has been removed from this instance. /// /// @@ -584,5 +595,19 @@ namespace Umbraco.Core.Models propertyType.ResetDirtyProperties(); } } + + public override object DeepClone() + { + var clone = (ContentTypeBase)base.DeepClone(); + + //need to manually wire up the event handlers for the property type collections - we've ensured + // its ignored from the auto-clone process because its return values are unions, not raw and + // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 + + clone._propertyTypes = (PropertyTypeCollection)_propertyTypes.DeepClone(); + clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged; + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index aaee03f963..d8ef10751b 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -2,9 +2,29 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace Umbraco.Core.Models { + /// + /// Used to attribute properties that have a setter and are a reference type + /// that should be ignored for cloning when using the DeepCloneHelper + /// + /// + /// + /// This attribute must be used: + /// * when the property is backed by a field but the result of the property is the un-natural data stored in the field + /// + /// This attribute should not be used: + /// * when the property is virtual + /// * when the setter performs additional required logic other than just setting the underlying field + /// + /// + internal class DoNotCloneAttribute : Attribute + { + + } + public static class DeepCloneHelper { /// @@ -25,8 +45,10 @@ namespace Umbraco.Core.Models var refProperties = inputType.GetProperties() .Where(x => + //is not attributed with the ignore clone attribute + x.GetCustomAttribute() == null //reference type but not string - x.PropertyType.IsValueType == false && x.PropertyType != typeof (string) + && x.PropertyType.IsValueType == false && x.PropertyType != typeof (string) //settable && x.CanWrite //non-indexed diff --git a/src/Umbraco.Core/Models/IMacroProperty.cs b/src/Umbraco.Core/Models/IMacroProperty.cs index c39e777a0e..a6c1d9ca5f 100644 --- a/src/Umbraco.Core/Models/IMacroProperty.cs +++ b/src/Umbraco.Core/Models/IMacroProperty.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Models /// /// Defines a Property for a Macro /// - public interface IMacroProperty : IValueObject, IDeepCloneable + public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty { [DataMember] int Id { get; set; } diff --git a/src/Umbraco.Core/Models/MacroPropertyCollection.cs b/src/Umbraco.Core/Models/MacroPropertyCollection.cs index 7496b582b4..de23d60e7c 100644 --- a/src/Umbraco.Core/Models/MacroPropertyCollection.cs +++ b/src/Umbraco.Core/Models/MacroPropertyCollection.cs @@ -24,6 +24,44 @@ namespace Umbraco.Core.Models } return clone; } + + /// + /// Used to update an existing macro property + /// + /// + /// + /// + /// + /// The existing property alias + /// + /// + public void UpdateProperty(string currentAlias, string name = null, int? sortOrder = null, string editorAlias = null, string newAlias = null) + { + var prop = this[currentAlias]; + if (prop == null) + { + throw new InvalidOperationException("No property exists with alias " + currentAlias); + } + + if (name.IsNullOrWhiteSpace() == false) + { + prop.Name = name; + } + if (sortOrder.HasValue) + { + prop.SortOrder = sortOrder.Value; + } + if (name.IsNullOrWhiteSpace() == false) + { + prop.EditorAlias = editorAlias; + } + + if (newAlias.IsNullOrWhiteSpace() == false && currentAlias != newAlias) + { + prop.Alias = newAlias; + ChangeKey(currentAlias, newAlias); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PreValue.cs b/src/Umbraco.Core/Models/PreValue.cs index b12312130d..61d127f50a 100644 --- a/src/Umbraco.Core/Models/PreValue.cs +++ b/src/Umbraco.Core/Models/PreValue.cs @@ -3,7 +3,7 @@ /// /// Represents a stored pre-value field value /// - public class PreValue + public class PreValue : IDeepCloneable { public PreValue(int id, string value, int sortOrder) { @@ -37,5 +37,12 @@ /// The sort order stored for the pre-value field value /// public int SortOrder { get; private set; } + + public virtual object DeepClone() + { + //Memberwise clone on PreValue will work since it doesn't have any deep elements + var clone = (PreValue)MemberwiseClone(); + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PreValueCollection.cs b/src/Umbraco.Core/Models/PreValueCollection.cs index e6d864cd91..449eaaa3b7 100644 --- a/src/Umbraco.Core/Models/PreValueCollection.cs +++ b/src/Umbraco.Core/Models/PreValueCollection.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Models /// Most legacy property editors won't support the dictionary format but new property editors should always use the dictionary format. /// In order to get overrideable pre-values working we need a dictionary since we'll have to reference a pre-value by a key. /// - public class PreValueCollection + public class PreValueCollection : IDeepCloneable { private IDictionary _preValuesAsDictionary; private IEnumerable _preValuesAsArray; @@ -82,5 +82,21 @@ namespace Umbraco.Core.Models } return result; } + + public object DeepClone() + { + var clone = (PreValueCollection) MemberwiseClone(); + if (_preValuesAsArray != null) + { + clone._preValuesAsArray = _preValuesAsArray.Select(x => (PreValue)x.DeepClone()).ToArray(); + } + if (_preValuesAsDictionary != null) + { + clone._preValuesAsDictionary = _preValuesAsDictionary.ToDictionary(x => x.Key, x => (PreValue)x.Value.DeepClone()); + } + + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 676eb05ef0..a0fa7932b5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -44,6 +44,11 @@ namespace Umbraco.Core.Models.PublishedContent InitializeIndexes(); } + // create detached content type - ie does not match anything in the DB + internal PublishedContentType(string alias, IEnumerable propertyTypes) + : this (0, alias, propertyTypes) + { } + private void InitializeIndexes() { for (var i = 0; i < _propertyTypes.Length; i++) diff --git a/src/Umbraco.Core/ObservableDictionary.cs b/src/Umbraco.Core/ObservableDictionary.cs index b665071386..7e988e2b05 100644 --- a/src/Umbraco.Core/ObservableDictionary.cs +++ b/src/Umbraco.Core/ObservableDictionary.cs @@ -135,6 +135,28 @@ namespace Umbraco.Core } + /// + /// Allows us to change the key of an item + /// + /// + /// + public virtual void ChangeKey(TKey currentKey, TKey newKey) + { + if (!Indecies.ContainsKey(currentKey)) + { + throw new InvalidOperationException("No item with the key " + currentKey + "was found in the collection"); + } + if (ContainsKey(newKey)) + { + throw new DuplicateKeyException(newKey.ToString()); + } + + var currentIndex = Indecies[currentKey]; + + Indecies.Remove(currentKey); + Indecies.Add(newKey, currentIndex); + } + internal class DuplicateKeyException : Exception { diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs index c2ca53a240..acacadb0cc 100644 --- a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs @@ -128,9 +128,9 @@ namespace Umbraco.Core.Persistence.Caching public void Save(Type type, IEntity entity) { //IMPORTANT: we must clone to store, see: http://issues.umbraco.org/issue/U4-4259 - entity = (IEntity)entity.DeepClone(); + var clone = (IEntity)entity.DeepClone(); - var key = GetCompositeId(type, entity.Id); + var key = GetCompositeId(type, clone.Id); _keyTracker.TryAdd(key); @@ -139,11 +139,11 @@ namespace Umbraco.Core.Persistence.Caching if (_memoryCache != null) { - _memoryCache.Set(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); + _memoryCache.Set(key, clone, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); } else { - HttpRuntime.Cache.Insert(key, entity, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5)); + HttpRuntime.Cache.Insert(key, clone, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 9484121ad7..0e8ae8c3bc 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -295,8 +295,8 @@ AND umbracoNode.id <> @id", var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); if (cached != null && cached.Any()) { - //return from the cache - return cached.First(); + //return from the cache, ensure it's a cloned result + return (PreValueCollection)cached.First().DeepClone(); } l.UpgradeToWriteLock(); diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 02f64aa0cf..d0b81f1b8e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -493,14 +493,17 @@ namespace Umbraco.Core.Persistence.Repositories // Is this the same UmbracoEntity as the current one we're processing if (Current != null && Current.Key == a.uniqueID) { - // Add this UmbracoProperty to the current additional data - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty + if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; + // Add this UmbracoProperty to the current additional data + Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty + { + PropertyEditorAlias = p.PropertyEditorAlias, + Value = p.NTextValue.IsNullOrWhiteSpace() + ? p.NVarcharValue + : p.NTextValue.ConvertToJsonIfPossible() + }; + } // Return null to indicate we're not done with this UmbracoEntity yet return null; @@ -516,14 +519,17 @@ namespace Umbraco.Core.Persistence.Repositories Current = _factory.BuildEntityFromDynamic(a); - //add the property/create the prop list if null - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty + if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; + //add the property/create the prop list if null + Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty + { + PropertyEditorAlias = p.PropertyEditorAlias, + Value = p.NTextValue.IsNullOrWhiteSpace() + ? p.NVarcharValue + : p.NTextValue.ConvertToJsonIfPossible() + }; + } // Return the now populated previous UmbracoEntity (or null if first time through) return prev; diff --git a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs index 4fa6215477..c8ffcbe68a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs @@ -165,9 +165,9 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(dto); - //update the sections if they've changed + //update the properties if they've changed var macro = (Macro)entity; - if (macro.IsPropertyDirty("Properties")) + if (macro.IsPropertyDirty("Properties") || macro.Properties.Any(x => x.IsDirty())) { //now we need to delete any props that have been removed foreach (var propAlias in macro.RemovedProperties) @@ -188,7 +188,11 @@ namespace Umbraco.Core.Persistence.Repositories } else { - Database.Update(propDto); + //only update if it's dirty + if (macro.Properties[propDto.Alias].IsDirty()) + { + Database.Update(propDto); + } } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index d1b20cce30..723ff2efc9 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -213,7 +213,7 @@ namespace Umbraco.Core //this is from SqlMetal and just makes it a bit of fun to allow pluralisation public static string MakePluralName(this string name) { - if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("ss", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase))) + if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("s", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase))) { name = name + "es"; return name; diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index 3a0d974185..bb61a84d82 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -152,6 +152,7 @@ namespace Umbraco.Tests.Models } Assert.AreNotSame(clone.PropertyTypes, contentType.PropertyTypes); Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + Assert.AreEqual(0, ((ContentTypeBase)clone).NonGroupedPropertyTypes.Count()); for (var index = 0; index < contentType.PropertyTypes.Count(); index++) { Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); diff --git a/src/Umbraco.Tests/Models/PreValueCollectionTests.cs b/src/Umbraco.Tests/Models/PreValueCollectionTests.cs new file mode 100644 index 0000000000..49c9ab16bb --- /dev/null +++ b/src/Umbraco.Tests/Models/PreValueCollectionTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class PreValueCollectionTests + { + [Test] + public void Can_Deep_Clone() + { + var d = new PreValueCollection(new Dictionary + { + {"blah1", new PreValue(1, "test1", 1)}, + {"blah2", new PreValue(2, "test1", 3)}, + {"blah3", new PreValue(3, "test1", 2)} + }); + + var a = new PreValueCollection(new[] + { + new PreValue(1, "test1", 1), + new PreValue(2, "test1", 3), + new PreValue(3, "test1", 2) + }); + + var clone1 = (PreValueCollection)d.DeepClone(); + var clone2 = (PreValueCollection)a.DeepClone(); + + Action assert = (orig, clone) => + { + Assert.AreNotSame(orig, clone); + var oDic = orig.FormatAsDictionary(); + var cDic = clone.FormatAsDictionary(); + Assert.AreEqual(oDic.Keys.Count(), cDic.Keys.Count()); + foreach (var k in oDic.Keys) + { + Assert.AreNotSame(oDic[k], cDic[k]); + Assert.AreEqual(oDic[k].Id, cDic[k].Id); + Assert.AreEqual(oDic[k].SortOrder, cDic[k].SortOrder); + Assert.AreEqual(oDic[k].Value, cDic[k].Value); + } + }; + + assert(d, clone1); + assert(a, clone2); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyGroupTests.cs b/src/Umbraco.Tests/Models/PropertyGroupTests.cs index f56bd2fbc2..413e94bfee 100644 --- a/src/Umbraco.Tests/Models/PropertyGroupTests.cs +++ b/src/Umbraco.Tests/Models/PropertyGroupTests.cs @@ -2,11 +2,12 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Models { [TestFixture] - public class PropertyGroupTests + public class PropertyGroupTests : BaseUmbracoConfigurationTest { [Test] public void Can_Deep_Clone() @@ -37,7 +38,7 @@ namespace Umbraco.Tests.Models Alias = "test2", CreateDate = DateTime.Now, DataTypeDefinitionId = 6, - DataTypeId = Guid.NewGuid(), + PropertyEditorAlias = "propTest", Description = "testing2", Key = Guid.NewGuid(), Mandatory = true, @@ -118,7 +119,7 @@ namespace Umbraco.Tests.Models Alias = "test2", CreateDate = DateTime.Now, DataTypeDefinitionId = 6, - DataTypeId = Guid.NewGuid(), + PropertyEditorAlias = "propTest", Description = "testing2", Key = Guid.NewGuid(), Mandatory = true, diff --git a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs index 5907b6c6be..377b9e57e7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs @@ -364,7 +364,58 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Update_Property_For_Macro() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = new MacroRepository(unitOfWork, NullCacheProvider.Current)) + { + var macro = repository.Get(1); + macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); + repository.AddOrUpdate(macro); + unitOfWork.Commit(); + //Act + macro = repository.Get(1); + macro.Properties["new1"].Name = "this is a new name"; + repository.AddOrUpdate(macro); + unitOfWork.Commit(); + + + // Assert + var result = repository.Get(1); + Assert.AreEqual("new1", result.Properties.First().Alias); + Assert.AreEqual("this is a new name", result.Properties.First().Name); + + } + } + + [Test] + public void Can_Update_Macro_Property_Alias() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = new MacroRepository(unitOfWork, NullCacheProvider.Current)) + { + var macro = repository.Get(1); + macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); + repository.AddOrUpdate(macro); + unitOfWork.Commit(); + + //Act + macro = repository.Get(1); + macro.Properties.UpdateProperty("new1", newAlias: "newAlias"); + repository.AddOrUpdate(macro); + unitOfWork.Commit(); + + // Assert + var result = repository.Get(1); + Assert.AreEqual("newAlias", result.Properties.First().Alias); + } + } [TearDown] public override void TearDown() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index bfe782f095..3bba4d5bce 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -192,6 +192,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 5366da0db2..ed7c2e726a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -81,7 +81,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "dataTypeApiBaseUrl", "GetAll")), - 'Failed to retreive data'); + 'Failed to retrieve data'); }, /** @@ -118,7 +118,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "dataTypeApiBaseUrl", "GetEmpty")), - 'Failed to retreive data for empty datatype'); + 'Failed to retrieve data for empty datatype'); }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 17f1b87b5b..eeb81f2d67 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -146,7 +146,7 @@ function entityResource($q, $http, umbRequestHelper) { "entityApiBaseUrl", "GetByIds", query)), - 'Failed to retreive entity data for ids ' + ids); + 'Failed to retrieve entity data for ids ' + ids); }, /** @@ -192,7 +192,7 @@ function entityResource($q, $http, umbRequestHelper) { "entityApiBaseUrl", "GetAll", query)), - 'Failed to retreive entity data for type ' + type); + 'Failed to retrieve entity data for type ' + type); }, /** @@ -215,7 +215,7 @@ function entityResource($q, $http, umbRequestHelper) { "entityApiBaseUrl", "GetAncestors", [{id: id}, {type: type}])), - 'Failed to retreive ancestor data for id ' + id); + 'Failed to retrieve ancestor data for id ' + id); }, /** @@ -238,7 +238,7 @@ function entityResource($q, $http, umbRequestHelper) { "entityApiBaseUrl", "GetChildren", [{ id: id }, { type: type }])), - 'Failed to retreive child data for id ' + id); + 'Failed to retrieve child data for id ' + id); }, /** @@ -271,7 +271,7 @@ function entityResource($q, $http, umbRequestHelper) { "entityApiBaseUrl", "Search", [{ query: query }, {type: type}])), - 'Failed to retreive entity data for query ' + query); + 'Failed to retrieve entity data for query ' + query); }, @@ -304,7 +304,7 @@ function entityResource($q, $http, umbRequestHelper) { "entityApiBaseUrl", "SearchAll", [{ query: query }])), - 'Failed to retreive entity data for query ' + query); + 'Failed to retrieve entity data for query ' + query); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js index 206c06ad4a..8059975fc1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js @@ -99,7 +99,7 @@ function logResource($q, $http, umbRequestHelper) { "logApiBaseUrl", "GetLog", [{ logtype: type, sinceDate: since }])), - 'Failed to retreive user data for id ' + id); + 'Failed to retrieve user data for id ' + id); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index 2eb104129b..65a53d233d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -133,7 +133,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "memberApiBaseUrl", "GetEmpty")), - 'Failed to retreive data for empty member item type ' + alias); + 'Failed to retrieve data for empty member item type ' + alias); } }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/stylesheet.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/stylesheet.resource.js index 3ff29a5c9d..f5eab4e902 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/stylesheet.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/stylesheet.resource.js @@ -93,7 +93,7 @@ function stylesheetResource($q, $http, umbRequestHelper) { "stylesheetApiBaseUrl", "GetRulesByName", [{ name: name }])), - 'Failed to retreive stylesheets '); + 'Failed to retrieve stylesheets '); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js index 52f8f07842..ea4bee718f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js @@ -72,7 +72,7 @@ function treeResource($q, $http, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.get(getTreeNodesUrl(options.node)), - 'Failed to retreive data for child nodes ' + options.node.nodeId); + 'Failed to retrieve data for child nodes ' + options.node.nodeId); } }; } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a6a7481249..742e35cdf1 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2530,9 +2530,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7120 + 7130 / - http://localhost:7120 + http://localhost:7130 False False @@ -2564,7 +2564,10 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" - + + + + diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config index 8e46fd134b..fe58a8fa84 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config @@ -15,7 +15,7 @@ - + xml diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.config index ae25ac80d5..86f00b1ef4 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.config @@ -15,7 +15,7 @@ - + xml diff --git a/src/Umbraco.Web/Models/DetachedContent.cs b/src/Umbraco.Web/Models/DetachedContent.cs index e572de656c..df4effa078 100644 --- a/src/Umbraco.Web/Models/DetachedContent.cs +++ b/src/Umbraco.Web/Models/DetachedContent.cs @@ -10,21 +10,33 @@ namespace Umbraco.Web.Models { private readonly Dictionary _properties; + /// + /// Initialized a new instance of the class with properties. + /// + /// The properties + /// Properties must be detached or nested properties ie their property type must be detached or nested. + /// Such a detached content can be part of a published property value. public DetachedContent(IEnumerable properties) { _properties = properties.ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); } + // don't uncomment until you know what you are doing + /* public DetachedContent(IPublishedContent content) { _properties = content.Properties.ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); } + */ + // don't uncomment until you know what you are doing + // at the moment, I don't fully + /* public DetachedContent(IContent content, bool isPreviewing) { var publishedContentType = PublishedContentType.Get(PublishedItemType.Content, content.ContentType.Alias); _properties = PublishedProperty.MapProperties(publishedContentType.PropertyTypes, content.Properties, - (t, v) => PublishedProperty.GetDetached(t, v, isPreviewing)) + (t, v) => PublishedProperty.GetDetached(t.Detached(), v, isPreviewing)) .ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); } @@ -32,7 +44,7 @@ namespace Umbraco.Web.Models { var publishedContentType = PublishedContentType.Get(PublishedItemType.Media, media.ContentType.Alias); _properties = PublishedProperty.MapProperties(publishedContentType.PropertyTypes, media.Properties, - (t, v) => PublishedProperty.GetDetached(t, v, isPreviewing)) + (t, v) => PublishedProperty.GetDetached(t.Detached(), v, isPreviewing)) .ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); } @@ -40,9 +52,10 @@ namespace Umbraco.Web.Models { var publishedContentType = PublishedContentType.Get(PublishedItemType.Member, member.ContentType.Alias); _properties = PublishedProperty.MapProperties(publishedContentType.PropertyTypes, member.Properties, - (t, v) => PublishedProperty.GetDetached(t, v, isPreviewing)) + (t, v) => PublishedProperty.GetDetached(t.Detached(), v, isPreviewing)) .ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); } + */ public ICollection Properties { diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index bfa57d5ab3..a0cff38414 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -437,8 +437,9 @@ namespace Umbraco.Web //currently assigned node. The PublishedContentRequest will be null if: // * we are rendering a partial view or child action // * we are rendering a view from a custom route - if (UmbracoContext.PublishedContentRequest == null + if ((UmbracoContext.PublishedContentRequest == null || UmbracoContext.PublishedContentRequest.PublishedContent.Id != currentPage.Id) + && currentPage.Id > 0) // in case we're rendering a detached content (id == 0) { item.NodeId = currentPage.Id.ToString(); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs index a50c2970de..50fb8b912e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs @@ -322,8 +322,6 @@ namespace umbraco.cms.presentation.developer void Save_Click(object sender, EventArgs e) { - //disable the add validators - macroProperties. Page.Validate(); @@ -351,10 +349,14 @@ namespace umbraco.cms.presentation.developer var macroElementType = (DropDownList)item.FindControl("macroPropertyType"); var prop = _macro.Properties.Single(x => x.Id == int.Parse(macroPropertyId.Value)); - prop.Alias = macroElementAlias.Text.Trim(); - prop.Name = macroElementName.Text.Trim(); - prop.EditorAlias = macroElementType.SelectedValue; - prop.SortOrder = sort; + + _macro.Properties.UpdateProperty( + prop.Alias, + macroElementName.Text.Trim(), + sort, + macroElementType.SelectedValue, + macroElementAlias.Text.Trim()); + sort++; } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs index 050c8cf224..bb7144de75 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs @@ -887,7 +887,7 @@ namespace umbraco.MacroEngines //this is from SqlMetal and just makes it a bit of fun to allow pluralisation private static string MakePluralName(string name) { - if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("ss", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase))) + if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("s", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase))) { name = name + "es"; return name;