diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index 05b78fe5dc..339cb25e05 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -420,7 +420,17 @@ namespace Umbraco.Core.Models
/// Creates a deep clone of the current entity with its identity and it's property identities reset
///
///
+ [Obsolete("Use DeepCloneWithResetIdentities instead")]
public IContent Clone()
+ {
+ return DeepCloneWithResetIdentities();
+ }
+
+ ///
+ /// Creates a deep clone of the current entity with its identity and it's property identities reset
+ ///
+ ///
+ public IContent DeepCloneWithResetIdentities()
{
var clone = (Content)DeepClone();
clone.Key = Guid.Empty;
diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs
index 5fb1fa3100..ff61f0f9a4 100644
--- a/src/Umbraco.Core/Models/ContentType.cs
+++ b/src/Umbraco.Core/Models/ContentType.cs
@@ -120,7 +120,7 @@ namespace Umbraco.Core.Models
return result;
}
-
+
///
/// Method to call when Entity is being saved
///
@@ -142,18 +142,48 @@ namespace Umbraco.Core.Models
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();
+ return clone;
+ }
+
///
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
///
///
+ [Obsolete("Use DeepCloneWithResetIdentities instead")]
public IContentType Clone(string alias)
+ {
+ return DeepCloneWithResetIdentities(alias);
+ }
+
+ ///
+ /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
+ ///
+ ///
+ public IContentType DeepCloneWithResetIdentities(string alias)
{
var clone = (ContentType)DeepClone();
clone.Alias = alias;
clone.Key = Guid.Empty;
- var propertyGroups = PropertyGroups.Select(x => x.Clone()).ToList();
- clone.PropertyGroups = new PropertyGroupCollection(propertyGroups);
- clone.PropertyTypes = PropertyTypeCollection.Select(x => x.Clone()).ToList();
+ foreach (var propertyGroup in clone.PropertyGroups)
+ {
+ propertyGroup.ResetIdentity();
+ propertyGroup.ResetDirtyProperties(false);
+ }
+ foreach (var propertyType in clone.PropertyTypes)
+ {
+ propertyType.ResetIdentity();
+ propertyType.ResetDirtyProperties(false);
+ }
+
clone.ResetIdentity();
clone.ResetDirtyProperties(false);
return clone;
diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs
index c77ff8bbdb..3121326f60 100644
--- a/src/Umbraco.Core/Models/IContent.cs
+++ b/src/Umbraco.Core/Models/IContent.cs
@@ -72,5 +72,11 @@ namespace Umbraco.Core.Models
/// Changes the Published state of the content object
///
void ChangePublishedState(PublishedState state);
+
+ ///
+ /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
+ ///
+ ///
+ IContent DeepCloneWithResetIdentities();
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs
index 3e61b98510..766a8eec81 100644
--- a/src/Umbraco.Core/Models/IContentType.cs
+++ b/src/Umbraco.Core/Models/IContentType.cs
@@ -29,5 +29,12 @@ namespace Umbraco.Core.Models
/// to remove
/// True if template was removed, otherwise False
bool RemoveTemplate(ITemplate template);
+
+ ///
+ /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
+ ///
+ ///
+ ///
+ IContentType DeepCloneWithResetIdentities(string newAlias);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs
index 4de4efb4e1..4dae794b5c 100644
--- a/src/Umbraco.Core/Models/PropertyGroup.cs
+++ b/src/Umbraco.Core/Models/PropertyGroup.cs
@@ -141,24 +141,5 @@ namespace Umbraco.Core.Models
return hashName ^ hashId;
}
- ///
- /// Creates a deep clone of the current entity with its identity and it's property identities reset
- ///
- ///
- internal PropertyGroup Clone()
- {
- var clone = (PropertyGroup)DeepClone();
- var collection = new PropertyTypeCollection();
- foreach (var propertyType in PropertyTypes)
- {
- var property = propertyType.Clone();
- collection.Add(property);
- }
- clone.PropertyTypes = collection;
- clone.ResetIdentity();
- clone.ResetDirtyProperties(false);
- return clone;
- }
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs
index e86b6df75e..2168edba2f 100644
--- a/src/Umbraco.Core/Models/PropertyType.cs
+++ b/src/Umbraco.Core/Models/PropertyType.cs
@@ -426,18 +426,6 @@ namespace Umbraco.Core.Models
return hashName ^ hashAlias;
}
- ///
- /// Creates a deep clone of the current entity with its identity and it's property identities reset
- ///
- ///
- internal PropertyType Clone()
- {
- var clone = (PropertyType)DeepClone();
- clone.ResetIdentity();
- clone.ResetDirtyProperties(false);
- return clone;
- }
-
public override object DeepClone()
{
var clone = (PropertyType)base.DeepClone();
@@ -445,8 +433,7 @@ namespace Umbraco.Core.Models
//need to manually assign the Lazy value as it will not be automatically mapped
if (PropertyGroupId != null)
{
- var propGroupId = PropertyGroupId.Value;
- clone._propertyGroupId = new Lazy(() => propGroupId);
+ clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value);
}
clone.ResetDirtyProperties(false);
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index c90c0b5029..e9d347c970 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1147,7 +1147,7 @@ namespace Umbraco.Core.Services
{
using (new WriteLock(Locker))
{
- var copy = ((Content)content).Clone();
+ var copy = content.DeepCloneWithResetIdentities();
copy.ParentId = parentId;
// A copy should never be set to published automatically even if the original was.
diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs
index e9b6c02e77..277f366279 100644
--- a/src/Umbraco.Core/Services/ContentTypeService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeService.cs
@@ -46,6 +46,89 @@ namespace Umbraco.Core.Services
_mediaService = mediaService;
}
+ ///
+ /// Copies a content type as a child under the specified parent if specified (otherwise to the root)
+ ///
+ ///
+ /// The content type to copy
+ ///
+ ///
+ /// The new alias of the content type
+ ///
+ ///
+ /// The new name of the content type
+ ///
+ ///
+ /// The parent to copy the content type to, default is -1 (root)
+ ///
+ ///
+ public IContentType Copy(IContentType original, string alias, string name, int parentId = -1)
+ {
+ IContentType parent = null;
+ if (parentId > 0)
+ {
+ parent = GetContentType(parentId);
+ if (parent == null)
+ {
+ throw new InvalidOperationException("Could not find content type with id " + parentId);
+ }
+ }
+ return Copy(original, alias, name, parent);
+ }
+
+ ///
+ /// Copies a content type as a child under the specified parent if specified (otherwise to the root)
+ ///
+ ///
+ /// The content type to copy
+ ///
+ ///
+ /// The new alias of the content type
+ ///
+ ///
+ /// The new name of the content type
+ ///
+ ///
+ /// The parent to copy the content type to, default is null (root)
+ ///
+ ///
+ public IContentType Copy(IContentType original, string alias, string name, IContentType parent)
+ {
+ Mandate.ParameterNotNull(original, "original");
+ Mandate.ParameterNotNullOrEmpty(alias, "alias");
+ if (parent != null)
+ {
+ Mandate.That(parent.HasIdentity, () => new InvalidOperationException("The parent content type must have an identity"));
+ }
+
+ var clone = original.DeepCloneWithResetIdentities(alias);
+
+ clone.Name = name;
+
+ var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList();
+ //remove all composition that is not it's current alias
+ foreach (var a in compositionAliases)
+ {
+ clone.RemoveContentType(a);
+ }
+
+ //if a parent is specified set it's composition and parent
+ if (parent != null)
+ {
+ //add a new parent composition
+ clone.AddContentType(parent);
+ clone.ParentId = parent.Id;
+ }
+ else
+ {
+ //set to root
+ clone.ParentId = -1;
+ }
+
+ Save(clone);
+ return clone;
+ }
+
///
/// Gets an object by its Id
///
diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs
index 9e7849e33c..91382be1e7 100644
--- a/src/Umbraco.Core/Services/IContentTypeService.cs
+++ b/src/Umbraco.Core/Services/IContentTypeService.cs
@@ -9,6 +9,42 @@ namespace Umbraco.Core.Services
///
public interface IContentTypeService : IService
{
+ ///
+ /// Copies a content type as a child under the specified parent if specified (otherwise to the root)
+ ///
+ ///
+ /// The content type to copy
+ ///
+ ///
+ /// The new alias of the content type
+ ///
+ ///
+ /// The new name of the content type
+ ///
+ ///
+ /// The parent to copy the content type to, default is -1 (root)
+ ///
+ ///
+ IContentType Copy(IContentType original, string alias, string name, int parentId = -1);
+
+ ///
+ /// Copies a content type as a child under the specified parent if specified (otherwise to the root)
+ ///
+ ///
+ /// The content type to copy
+ ///
+ ///
+ /// The new alias of the content type
+ ///
+ ///
+ /// The new name of the content type
+ ///
+ ///
+ /// The parent to copy the content type to
+ ///
+ ///
+ IContentType Copy(IContentType original, string alias, string name, IContentType parent);
+
///
/// Gets an object by its Id
///
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
index 467bb35250..34d8d7e759 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
@@ -300,6 +300,140 @@ namespace Umbraco.Tests.Services
}
+ [Test]
+ public void Can_Copy_ContentType_To_New_Parent_By_Performing_Clone()
+ {
+ // Arrange
+ var service = ServiceContext.ContentTypeService;
+
+ var parentContentType1 = MockedContentTypes.CreateSimpleContentType("parent1", "Parent1");
+ service.Save(parentContentType1);
+ var parentContentType2 = MockedContentTypes.CreateSimpleContentType("parent2", "Parent2");
+ service.Save(parentContentType2);
+
+ var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1);
+ service.Save(simpleContentType);
+
+ // Act
+ var clone = simpleContentType.Clone("newcategory");
+ clone.RemoveContentType("parent1");
+ clone.AddContentType(parentContentType2);
+ clone.ParentId = parentContentType2.Id;
+ service.Save(clone);
+
+ // Assert
+ Assert.That(clone.HasIdentity, Is.True);
+
+ var clonedContentType = service.GetContentType(clone.Id);
+ var originalContentType = service.GetContentType(simpleContentType.Id);
+
+ Assert.That(clonedContentType.CompositionAliases().Any(x => x.Equals("parent2")), Is.True);
+ Assert.That(clonedContentType.CompositionAliases().Any(x => x.Equals("parent1")), Is.False);
+
+ Assert.AreEqual(clonedContentType.Path, "-1," + parentContentType2.Id + "," + clonedContentType.Id);
+ Assert.AreEqual(clonedContentType.PropertyTypes.Count(), originalContentType.PropertyTypes.Count());
+
+ Assert.AreNotEqual(clonedContentType.ParentId, originalContentType.ParentId);
+ Assert.AreEqual(clonedContentType.ParentId, parentContentType2.Id);
+
+ Assert.AreNotEqual(clonedContentType.Id, originalContentType.Id);
+ Assert.AreNotEqual(clonedContentType.Key, originalContentType.Key);
+ Assert.AreNotEqual(clonedContentType.Path, originalContentType.Path);
+
+ Assert.AreNotEqual(clonedContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id, originalContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id);
+ Assert.AreNotEqual(clonedContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id, originalContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id);
+
+ }
+
+ [Test]
+ public void Can_Copy_ContentType_With_Service_To_Root()
+ {
+ // Arrange
+ var service = ServiceContext.ContentTypeService;
+ var metaContentType = MockedContentTypes.CreateMetaContentType();
+ service.Save(metaContentType);
+
+ var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", metaContentType);
+ service.Save(simpleContentType);
+ var categoryId = simpleContentType.Id;
+
+ // Act
+ var clone = service.Copy(simpleContentType, "newcategory", "new category");
+
+ // Assert
+ Assert.That(clone.HasIdentity, Is.True);
+
+ var cloned = service.GetContentType(clone.Id);
+ var original = service.GetContentType(categoryId);
+
+
+ Assert.That(cloned.CompositionAliases().Any(x => x.Equals("meta")), Is.False); //it's been copied to root
+ Assert.AreEqual(cloned.ParentId, -1);
+ Assert.AreEqual(cloned.Level, 1);
+ Assert.AreEqual(cloned.PropertyTypes.Count(), original.PropertyTypes.Count());
+ Assert.AreEqual(cloned.PropertyGroups.Count(), original.PropertyGroups.Count());
+ for (int i = 0; i < cloned.PropertyGroups.Count; i++)
+ {
+ Assert.AreEqual(cloned.PropertyGroups[i].PropertyTypes.Count, original.PropertyGroups[i].PropertyTypes.Count);
+ foreach (var propertyType in cloned.PropertyGroups[i].PropertyTypes)
+ {
+ Assert.IsTrue(propertyType.HasIdentity);
+ }
+ }
+ foreach (var propertyType in cloned.PropertyTypes)
+ {
+ Assert.IsTrue(propertyType.HasIdentity);
+ }
+ Assert.AreNotEqual(cloned.Id, original.Id);
+ Assert.AreNotEqual(cloned.Key, original.Key);
+ Assert.AreNotEqual(cloned.Path, original.Path);
+ Assert.AreNotEqual(cloned.SortOrder, original.SortOrder);
+ Assert.AreNotEqual(cloned.PropertyTypes.First(x => x.Alias.Equals("title")).Id, original.PropertyTypes.First(x => x.Alias.Equals("title")).Id);
+ Assert.AreNotEqual(cloned.PropertyGroups.First(x => x.Name.Equals("Content")).Id, original.PropertyGroups.First(x => x.Name.Equals("Content")).Id);
+
+ }
+
+ [Test]
+ public void Can_Copy_ContentType_To_New_Parent_With_Service()
+ {
+ // Arrange
+ var service = ServiceContext.ContentTypeService;
+
+ var parentContentType1 = MockedContentTypes.CreateSimpleContentType("parent1", "Parent1");
+ service.Save(parentContentType1);
+ var parentContentType2 = MockedContentTypes.CreateSimpleContentType("parent2", "Parent2");
+ service.Save(parentContentType2);
+
+ var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1);
+ service.Save(simpleContentType);
+
+ // Act
+ var clone = service.Copy(simpleContentType, "newAlias", "new alias", parentContentType2);
+
+ // Assert
+ Assert.That(clone.HasIdentity, Is.True);
+
+ var clonedContentType = service.GetContentType(clone.Id);
+ var originalContentType = service.GetContentType(simpleContentType.Id);
+
+ Assert.That(clonedContentType.CompositionAliases().Any(x => x.Equals("parent2")), Is.True);
+ Assert.That(clonedContentType.CompositionAliases().Any(x => x.Equals("parent1")), Is.False);
+
+ Assert.AreEqual(clonedContentType.Path, "-1," + parentContentType2.Id + "," + clonedContentType.Id);
+ Assert.AreEqual(clonedContentType.PropertyTypes.Count(), originalContentType.PropertyTypes.Count());
+
+ Assert.AreNotEqual(clonedContentType.ParentId, originalContentType.ParentId);
+ Assert.AreEqual(clonedContentType.ParentId, parentContentType2.Id);
+
+ Assert.AreNotEqual(clonedContentType.Id, originalContentType.Id);
+ Assert.AreNotEqual(clonedContentType.Key, originalContentType.Key);
+ Assert.AreNotEqual(clonedContentType.Path, originalContentType.Path);
+
+ Assert.AreNotEqual(clonedContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id, originalContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id);
+ Assert.AreNotEqual(clonedContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id, originalContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id);
+
+ }
+
private ContentType CreateComponent()
{
var component = new ContentType(-1)
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs
index 42bc8b9bc9..0ba2716258 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs
@@ -147,31 +147,17 @@ namespace umbraco.dialogs
private void HandleDocumentTypeCopy()
{
-
- //TODO: This should be a method on the service!!!
-
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
var contentType = contentTypeService.GetContentType(
int.Parse(Request.GetItemAsString("id")));
- var alias = rename.Text.Trim().Replace("'", "''");
- var clone = ((Umbraco.Core.Models.ContentType) contentType).Clone(alias);
- clone.Name = rename.Text.Trim();
-
//set the master
//http://issues.umbraco.org/issue/U4-2843
//http://issues.umbraco.org/issue/U4-3552
- var parent = int.Parse(masterType.SelectedValue);
- if (parent > 0)
- {
- clone.ParentId = parent;
- }
- else
- {
- clone.ParentId = -1;
- }
-
- contentTypeService.Save(clone);
+ var parentId = int.Parse(masterType.SelectedValue);
+
+ var alias = rename.Text.Trim().Replace("'", "''");
+ var clone = contentTypeService.Copy(contentType, alias, rename.Text.Trim(), parentId);
var returnUrl = string.Format("{0}/settings/editNodeTypeNew.aspx?id={1}", SystemDirectories.Umbraco, clone.Id);