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);