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;