diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 951934ddf6..aefce9192d 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -171,131 +171,84 @@ namespace Umbraco.Core public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; - internal static Dictionary - StandardPropertyTypeStubs = new Dictionary - { - { - Comments, - new PropertyType( - PropertyEditors - .TextboxMultipleAlias, - DataTypeDatabaseType - .Ntext) - { - Alias = Comments, - Name = - CommentsLabel - } - }, - { - FailedPasswordAttempts, - new PropertyType( - PropertyEditors - .IntegerAlias, - DataTypeDatabaseType - .Integer) - { - Alias = - FailedPasswordAttempts, - Name = - FailedPasswordAttemptsLabel - } - }, - { - IsApproved, - new PropertyType( - PropertyEditors - .TrueFalseAlias, - DataTypeDatabaseType - .Integer) - { - Alias = IsApproved, - Name = - IsApprovedLabel - } - }, - { - IsLockedOut, - new PropertyType( - PropertyEditors - .TrueFalseAlias, - DataTypeDatabaseType - .Integer) - { - Alias = - IsLockedOut, - Name = - IsLockedOutLabel - } - }, - { - LastLockoutDate, - new PropertyType( - PropertyEditors.DateAlias, - DataTypeDatabaseType - .Date) - { - Alias = - LastLockoutDate, - Name = - LastLockoutDateLabel - } - }, - { - LastLoginDate, - new PropertyType( - PropertyEditors.DateAlias, - DataTypeDatabaseType - .Date) - { - Alias = - LastLoginDate, - Name = - LastLoginDateLabel - } - }, - { - LastPasswordChangeDate, - new PropertyType( - PropertyEditors.DateAlias, - DataTypeDatabaseType - .Date) - { - Alias = - LastPasswordChangeDate, - Name = - LastPasswordChangeDateLabel - } - }, - { - PasswordAnswer, - new PropertyType( - PropertyEditors - .TextboxAlias, - DataTypeDatabaseType - .Nvarchar) - { - Alias = - PasswordAnswer, - Name = - PasswordAnswerLabel - } - }, - { - PasswordQuestion, - new PropertyType( - PropertyEditors - .TextboxAlias, - DataTypeDatabaseType - .Nvarchar) - { - Alias = - PasswordQuestion, - Name = - PasswordQuestionLabel - } - } - }; + internal static Dictionary GetStandardPropertyTypeStubs() + { + return new Dictionary + { + { + Comments, + new PropertyType(PropertyEditors.TextboxMultipleAlias, DataTypeDatabaseType.Ntext) + { + Alias = Comments, + Name = CommentsLabel + } + }, + { + FailedPasswordAttempts, + new PropertyType(PropertyEditors.IntegerAlias, DataTypeDatabaseType.Integer) + { + Alias = FailedPasswordAttempts, + Name = FailedPasswordAttemptsLabel + } + }, + { + IsApproved, + new PropertyType(PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer) + { + Alias = IsApproved, + Name = IsApprovedLabel + } + }, + { + IsLockedOut, + new PropertyType(PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer) + { + Alias = IsLockedOut, + Name = IsLockedOutLabel + } + }, + { + LastLockoutDate, + new PropertyType(PropertyEditors.DateAlias, DataTypeDatabaseType.Date) + { + Alias = LastLockoutDate, + Name = LastLockoutDateLabel + } + }, + { + LastLoginDate, + new PropertyType(PropertyEditors.DateAlias, DataTypeDatabaseType.Date) + { + Alias = LastLoginDate, + Name = LastLoginDateLabel + } + }, + { + LastPasswordChangeDate, + new PropertyType(PropertyEditors.DateAlias, DataTypeDatabaseType.Date) + { + Alias = LastPasswordChangeDate, + Name = LastPasswordChangeDateLabel + } + }, + { + PasswordAnswer, + new PropertyType(PropertyEditors.TextboxAlias, DataTypeDatabaseType.Nvarchar) + { + Alias = PasswordAnswer, + Name = PasswordAnswerLabel + } + }, + { + PasswordQuestion, + new PropertyType(PropertyEditors.TextboxAlias, DataTypeDatabaseType.Nvarchar) + { + Alias = PasswordQuestion, + Name = PasswordQuestionLabel + } + } + }; + } } /// diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index d377095a05..e8e58ca9ba 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -641,21 +641,16 @@ namespace Umbraco.Core.Models return ApplicationContext.Current.Services.PackagingService.Export(media); } + /// + /// Creates the full xml representation for the object and all of it's descendants + /// + /// to generate xml for + /// Xml representation of the passed in internal static XElement ToDeepXml(this IMedia media) { return ApplicationContext.Current.Services.PackagingService.Export(media, true); } - - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in - public static XElement ToXml(this IMember member) - { - return ApplicationContext.Current.Services.PackagingService.Export(member); - } - + /// /// Creates the xml representation for the object /// @@ -668,6 +663,16 @@ namespace Umbraco.Core.Models //If current IContent is published we should get latest unpublished version return content.ToXml(); } + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// Xml representation of the passed in + public static XElement ToXml(this IMember member) + { + return ApplicationContext.Current.Services.PackagingService.Export(member); + } #endregion } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 0628fe8184..efec3fc61b 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -20,6 +20,17 @@ namespace Umbraco.Core.Models private object _providerUserKey; private Type _userTypeKey; + /// + /// Constructor for creating a Member object + /// + /// Name of the content + /// ContentType for the current Content object + public Member(string name, IMemberType contentType) + : base(name, -1, contentType, new PropertyCollection()) + { + _contentType = contentType; + } + internal Member(string name, string email, string username, string password, int parentId, IMemberType contentType) : base(name, parentId, contentType, new PropertyCollection()) { diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 5caa8db61a..452178939d 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence.Factories var propertyTypes = GetPropertyTypes(dto, memberType); //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. - var standardPropertyTypes = Constants.Conventions.Member.StandardPropertyTypeStubs; + var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); foreach (var standardPropertyType in standardPropertyTypes) { if(dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; diff --git a/src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs b/src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs index 31fd430118..79486b6e5b 100644 --- a/src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Relators @@ -20,8 +21,12 @@ namespace Umbraco.Core.Persistence.Relators // Is this the same MemberReadOnlyDto as the current one we're processing if (Current != null && Current.UniqueId == a.UniqueId) { - // Yes, just add this PropertyDataReadOnlyDto to the current MemberReadOnlyDto's collection - Current.Properties.Add(p); + //This property may already be added so we need to check for that + if (Current.Properties.Any(x => x.Id == p.Id) == false) + { + // Yes, just add this PropertyDataReadOnlyDto to the current MemberReadOnlyDto's collection + Current.Properties.Add(p); + } // Return null to indicate we're not done with this MemberReadOnlyDto yet return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 0f5b963e5c..08bed74923 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -168,7 +168,7 @@ namespace Umbraco.Core.Persistence.Repositories ((MemberType)entity).AddingEntity(); //By Convention we add 9 stnd PropertyTypes to an Umbraco MemberType - var standardPropertyTypes = Constants.Conventions.Member.StandardPropertyTypeStubs; + var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); foreach (var standardPropertyType in standardPropertyTypes) { entity.AddPropertyType(standardPropertyType.Value); diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index a2f27f58a2..8dbd3a2d98 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; @@ -21,7 +20,7 @@ namespace Umbraco.Core.Services /// /// Represents the ContentType Service, which is an easy access to operations involving /// - public class ContentTypeService : IContentTypeService + public class ContentTypeService : ContentTypeServiceBase, IContentTypeService { private readonly RepositoryFactory _repositoryFactory; private readonly IContentService _contentService; @@ -147,48 +146,7 @@ namespace Umbraco.Core.Services private void UpdateContentXmlStructure(params IContentTypeBase[] contentTypes) { - var toUpdate = new List(); - - foreach (var contentType in contentTypes) - { - //we need to determine if we need to refresh the xml content in the database. This is to be done when: - // - the item is not new (already existed in the db) AND - // - a content type changes it's alias OR - // - if a content type has it's property removed OR - // - if a content type has a property whose alias has changed - //here we need to check if the alias of the content type changed or if one of the properties was removed. - var dirty = contentType as IRememberBeingDirty; - if (dirty == null) continue; - - //check if any property types have changed their aliases (and not new property types) - var hasAnyPropertiesChangedAlias = contentType.PropertyTypes.Any(propType => - { - var dirtyProperty = propType as IRememberBeingDirty; - if (dirtyProperty == null) return false; - return dirtyProperty.WasPropertyDirty("HasIdentity") == false //ensure it's not 'new' - && dirtyProperty.WasPropertyDirty("Alias"); //alias has changed - }); - - if (dirty.WasPropertyDirty("HasIdentity") == false //ensure it's not 'new' - && (dirty.WasPropertyDirty("Alias") || dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") || hasAnyPropertiesChangedAlias)) - { - //If the alias was changed then we only need to update the xml structures for content of the current content type. - //If a property was deleted or a property alias was changed then we need to update the xml structures for any - // content of the current content type and any of the content type's child content types. - if (dirty.WasPropertyDirty("Alias") - && dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") == false && hasAnyPropertiesChangedAlias == false) - { - //if only the alias changed then only update the current content type - toUpdate.Add(contentType); - } - else - { - //if a property was deleted or alias changed, then update all content of the current content type - // and all of it's desscendant doc types. - toUpdate.AddRange(contentType.DescendantsAndSelf()); - } - } - } + var toUpdate = GetContentTypesForXmlUpdates(contentTypes).ToArray(); if (toUpdate.Any()) { diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs new file mode 100644 index 0000000000..b5115d61c2 --- /dev/null +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Services +{ + public class ContentTypeServiceBase + { + /// + /// This is called after an content type is saved and is used to update the content xml structures in the database + /// if they are required to be updated. + /// + /// + internal IEnumerable GetContentTypesForXmlUpdates(params IContentTypeBase[] contentTypes) + { + + var toUpdate = new List(); + + foreach (var contentType in contentTypes) + { + //we need to determine if we need to refresh the xml content in the database. This is to be done when: + // - the item is not new (already existed in the db) AND + // - a content type changes it's alias OR + // - if a content type has it's property removed OR + // - if a content type has a property whose alias has changed + //here we need to check if the alias of the content type changed or if one of the properties was removed. + var dirty = contentType as IRememberBeingDirty; + if (dirty == null) continue; + + //check if any property types have changed their aliases (and not new property types) + var hasAnyPropertiesChangedAlias = contentType.PropertyTypes.Any(propType => + { + var dirtyProperty = propType as IRememberBeingDirty; + if (dirtyProperty == null) return false; + return dirtyProperty.WasPropertyDirty("HasIdentity") == false //ensure it's not 'new' + && dirtyProperty.WasPropertyDirty("Alias"); //alias has changed + }); + + if (dirty.WasPropertyDirty("HasIdentity") == false //ensure it's not 'new' + && (dirty.WasPropertyDirty("Alias") || dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") || hasAnyPropertiesChangedAlias)) + { + //If the alias was changed then we only need to update the xml structures for content of the current content type. + //If a property was deleted or a property alias was changed then we need to update the xml structures for any + // content of the current content type and any of the content type's child content types. + if (dirty.WasPropertyDirty("Alias") + && dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") == false && hasAnyPropertiesChangedAlias == false) + { + //if only the alias changed then only update the current content type + toUpdate.Add(contentType); + } + else + { + //if a property was deleted or alias changed, then update all content of the current content type + // and all of it's desscendant doc types. + toUpdate.AddRange(contentType.DescendantsAndSelf()); + } + } + } + + return toUpdate; + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 278a27ca24..7797b8c59b 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -19,10 +19,13 @@ namespace Umbraco.Core.Services IMember GetById(int id); IMember GetByKey(Guid id); IEnumerable GetMembersByMemberType(string memberTypeAlias); + IEnumerable GetMembersByMemberType(int memberTypeId); IEnumerable GetMembersByGroup(string memberGroupName); IEnumerable GetAllMembers(params int[] ids); //TODO: Need to get all members that start with a certain letter + + void DeleteMembersOfType(int memberTypeId); } /// @@ -40,7 +43,7 @@ namespace Umbraco.Core.Services /// bool Exists(string username); - IMember CreateMember(string username, string email, string password, string memberTypeAlias, int userId = 0); + IMember CreateMember(string username, string email, string password, string memberTypeAlias); IMember GetById(object id); diff --git a/src/Umbraco.Core/Services/IMemberTypeService.cs b/src/Umbraco.Core/Services/IMemberTypeService.cs index e456653193..b2aca1bf54 100644 --- a/src/Umbraco.Core/Services/IMemberTypeService.cs +++ b/src/Umbraco.Core/Services/IMemberTypeService.cs @@ -25,5 +25,35 @@ namespace Umbraco.Core.Services /// Alias of the to retrieve /// IMemberType GetMemberType(string alias); + + /// + /// Saves a single object + /// + /// to save + /// Optional Id of the User saving the ContentType + void Save(IMemberType memberType, int userId = 0); + + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Optional Id of the User saving the ContentTypes + void Save(IEnumerable memberTypes, int userId = 0); + + /// + /// Deletes a single object + /// + /// to delete + /// Deleting a will delete all the objects based on this + /// Optional Id of the User deleting the ContentType + void Delete(IMemberType memberType, int userId = 0); + + /// + /// Deletes a collection of objects + /// + /// Collection of to delete + /// Deleting a will delete all the objects based on this + /// Optional Id of the User deleting the ContentTypes + void Delete(IEnumerable memberTypes, int userId = 0); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 10f442b3f2..f32b47dc94 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -598,7 +598,7 @@ namespace Umbraco.Core.Services //The ContentType has to be removed from the composition somehow as it would otherwise break //Dbl.check+test that the ContentType's Id is removed from the ContentType2ContentType table var query = Query.Builder.Where(x => x.ContentTypeId == mediaTypeId); - var contents = repository.GetByQuery(query); + var contents = repository.GetByQuery(query).ToArray(); if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(contents), this)) return; diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index bb57428889..35271c63d6 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading; using System.Xml.Linq; +using System.Xml.Linq; +using Umbraco.Core.Events; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -20,6 +23,8 @@ namespace Umbraco.Core.Services private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + public MemberService(RepositoryFactory repositoryFactory) : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) { @@ -111,6 +116,22 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a list of Members by their MemberType + /// + /// + /// + public IEnumerable GetMembersByMemberType(int memberTypeId) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + repository.Get(memberTypeId); + var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query); + return members; + } + } + /// /// Gets a list of Members by the MemberGroup they are part of /// @@ -137,6 +158,29 @@ namespace Umbraco.Core.Services } } + public void DeleteMembersOfType(int memberTypeId) + { + using (new WriteLock(Locker)) + { + using (var uow = _uowProvider.GetUnitOfWork()) + { + var repository = _repositoryFactory.CreateMemberRepository(uow); + //NOTE What about content that has the contenttype as part of its composition? + var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query).ToArray(); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) + return; + + foreach (var member in members) + { + //Permantly delete the member + Delete(member); + } + } + } + } + /// /// Does a search for members that contain the specified string in their email address /// @@ -246,15 +290,14 @@ namespace Umbraco.Core.Services #region IMembershipMemberService Implementation /// - /// Creates a new Member + /// Creates and persists a new Member /// /// /// /// /// - /// /// - public IMember CreateMember(string email, string username, string password, string memberTypeAlias, int userId = 0) + public IMember CreateMember(string email, string username, string password, string memberTypeAlias) { var uow = _uowProvider.GetUnitOfWork(); IMemberType memberType; @@ -277,7 +320,7 @@ namespace Umbraco.Core.Services //insert the xml var xml = member.ToXml(); - CreateAndSaveContentXml(xml, member.Id, uow.Database); + CreateAndSaveMemberXml(xml, member.Id, uow.Database); } return member; @@ -380,9 +423,8 @@ namespace Umbraco.Core.Services repository.AddOrUpdate(member); uow.Commit(); - //insert the xml var xml = member.ToXml(); - CreateAndSaveContentXml(xml, member.Id, uow.Database); + CreateAndSaveMemberXml(xml, member.Id, uow.Database); } if (raiseEvents) @@ -391,11 +433,79 @@ namespace Umbraco.Core.Services #endregion - private void CreateAndSaveContentXml(XElement xml, int id, UmbracoDatabase db) + /// + /// Rebuilds all xml content in the cmsContentXml table for all media + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all members = USE WITH CARE! + /// + /// True if publishing succeeded, otherwise False + internal void RebuildXmlStructures(params int[] memberTypeIds) { - var contentPoco = new ContentXmlDto { NodeId = id, Xml = xml.ToString(SaveOptions.None) }; - var contentExists = db.ExecuteScalar("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = id }) != 0; - int contentResult = contentExists ? db.Update(contentPoco) : Convert.ToInt32(db.Insert(contentPoco)); + using (new WriteLock(Locker)) + { + var list = new List(); + + var uow = _uowProvider.GetUnitOfWork(); + + //First we're going to get the data that needs to be inserted before clearing anything, this + //ensures that we don't accidentally leave the content xml table empty if something happens + //during the lookup process. + + if (memberTypeIds.Any() == false) + { + list.AddRange(GetAllMembers()); + } + else + { + list.AddRange(memberTypeIds.SelectMany(GetMembersByMemberType)); + } + + var xmlItems = new List(); + foreach (var c in list) + { + var xml = c.ToXml(); + xmlItems.Add(new ContentXmlDto { NodeId = c.Id, Xml = xml.ToString(SaveOptions.None) }); + } + + //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. + using (var tr = uow.Database.GetTransaction()) + { + if (memberTypeIds.Any() == false) + { + //Remove all media records from the cmsContentXml table (DO NOT REMOVE Content/Members!) + uow.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id + WHERE nodeObjectType = @nodeObjectType)", + new { nodeObjectType = Constants.ObjectTypes.Member }); + } + else + { + foreach (var id in memberTypeIds) + { + //first we'll clear out the data from the cmsContentXml table for this type + uow.Database.Execute(@"delete from cmsContentXml where nodeId in + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id + INNER JOIN cmsContent ON cmsContent.nodeId = umbracoNode.id + WHERE nodeObjectType = @nodeObjectType AND cmsContent.contentType = @contentTypeId)", + new { contentTypeId = id, nodeObjectType = Constants.ObjectTypes.Member }); + } + } + + //bulk insert it into the database + uow.Database.BulkInsertRecords(xmlItems, tr); + } + } + } + + private void CreateAndSaveMemberXml(XElement xml, int id, UmbracoDatabase db) + { + var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToString(SaveOptions.None) }; + var exists = db.FirstOrDefault("WHERE nodeId = @Id", new { Id = id }) != null; + int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco)); } #region Event Handlers @@ -484,5 +594,6 @@ namespace Umbraco.Core.Services return new Member(name, email, username, password, -1, memType); } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 4f3e09b224..c46628041c 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using Umbraco.Core.Auditing; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; @@ -8,25 +11,29 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - internal class MemberTypeService : IMemberTypeService + internal class MemberTypeService : ContentTypeServiceBase, IMemberTypeService { private readonly IDatabaseUnitOfWorkProvider _uowProvider; private readonly RepositoryFactory _repositoryFactory; + private readonly IMemberService _memberService; - public MemberTypeService() - : this(new PetaPocoUnitOfWorkProvider(), new RepositoryFactory()) + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + + public MemberTypeService(IMemberService memberService) + : this(new PetaPocoUnitOfWorkProvider(), new RepositoryFactory(), memberService) {} - public MemberTypeService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) + public MemberTypeService(RepositoryFactory repositoryFactory, IMemberService memberService) + : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory, memberService) { } - public MemberTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) + public MemberTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IMemberService memberService) { if (provider == null) throw new ArgumentNullException("provider"); if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); _uowProvider = provider; _repositoryFactory = repositoryFactory; + _memberService = memberService; } public IEnumerable GetAllMemberTypes(params int[] ids) @@ -66,5 +73,143 @@ namespace Umbraco.Core.Services } } + public void Save(IMemberType memberType, int userId = 0) + { + if (SavingMemberType.IsRaisedEventCancelled(new SaveEventArgs(memberType), this)) + return; + + using (new WriteLock(Locker)) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberTypeRepository(uow)) + { + memberType.CreatorId = userId; + repository.AddOrUpdate(memberType); + + uow.Commit(); + } + + UpdateContentXmlStructure(memberType); + } + SavedMemberType.RaiseEvent(new SaveEventArgs(memberType, false), this); + } + + public void Save(IEnumerable memberTypes, int userId = 0) + { + var asArray = memberTypes.ToArray(); + + if (SavingMemberType.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) + return; + + using (new WriteLock(Locker)) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberTypeRepository(uow)) + { + foreach (var memberType in asArray) + { + memberType.CreatorId = userId; + repository.AddOrUpdate(memberType); + } + + //save it all in one go + uow.Commit(); + } + + UpdateContentXmlStructure(asArray.Cast().ToArray()); + } + SavedMemberType.RaiseEvent(new SaveEventArgs(asArray, false), this); + } + + public void Delete(IMemberType memberType, int userId = 0) + { + if (DeletingMemberType.IsRaisedEventCancelled(new DeleteEventArgs(memberType), this)) + return; + + using (new WriteLock(Locker)) + { + _memberService.DeleteMembersOfType(memberType.Id); + + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberTypeRepository(uow)) + { + repository.Delete(memberType); + uow.Commit(); + + DeletedMemberType.RaiseEvent(new DeleteEventArgs(memberType, false), this); + } + } + } + + public void Delete(IEnumerable memberTypes, int userId = 0) + { + var asArray = memberTypes.ToArray(); + + if (DeletingMemberType.IsRaisedEventCancelled(new DeleteEventArgs(asArray), this)) + return; + + using (new WriteLock(Locker)) + { + foreach (var contentType in asArray) + { + _memberService.DeleteMembersOfType(contentType.Id); + } + + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberTypeRepository(uow)) + { + foreach (var memberType in asArray) + { + repository.Delete(memberType); + } + + uow.Commit(); + + DeletedMemberType.RaiseEvent(new DeleteEventArgs(asArray, false), this); + } + } + } + + /// + /// This is called after an IContentType is saved and is used to update the content xml structures in the database + /// if they are required to be updated. + /// + /// A tuple of a content type and a boolean indicating if it is new (HasIdentity was false before committing) + private void UpdateContentXmlStructure(params IContentTypeBase[] contentTypes) + { + + var toUpdate = GetContentTypesForXmlUpdates(contentTypes).ToArray(); + + if (toUpdate.Any()) + { + //if it is a media type then call the rebuilding methods for media + var typedMemberService = _memberService as MemberService; + if (typedMemberService != null) + { + typedMemberService.RebuildXmlStructures(toUpdate.Select(x => x.Id).ToArray()); + } + } + + } + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> SavingMemberType; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> SavedMemberType; + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> DeletingMemberType; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> DeletedMemberType; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index baa29f8915..68e09dee38 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -147,7 +147,7 @@ namespace Umbraco.Core.Services _macroService = new Lazy(() => new MacroService(provider, repositoryFactory.Value)); if (_memberTypeService == null) - _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory.Value)); + _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory.Value, _memberService.Value)); if (_tagService == null) _tagService = new Lazy(() => new TagService(provider, repositoryFactory.Value)); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1d68dd864e..3619c26d13 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -960,6 +960,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 7f379d2bc2..23d62e5f8c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -216,8 +216,8 @@ namespace Umbraco.Tests.Persistence.Repositories var sut = repository.Get(member.Id); Assert.That(sut.ContentType.PropertyGroups.Count(), Is.EqualTo(1)); - Assert.That(sut.ContentType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.StandardPropertyTypeStubs.Count)); - Assert.That(sut.Properties.Count(), Is.EqualTo(3 + Constants.Conventions.Member.StandardPropertyTypeStubs.Count)); + Assert.That(sut.ContentType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); + Assert.That(sut.Properties.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); Assert.That(sut.Properties.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); } } @@ -235,7 +235,7 @@ namespace Umbraco.Tests.Persistence.Repositories memberTypeRepository.AddOrUpdate(memberType); unitOfWork.Commit(); - var member = MockedMember.CreateSimpleContent(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty", -1); + var member = MockedMember.CreateSimpleMember(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty"); repository.AddOrUpdate(member); unitOfWork.Commit(); @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Persistence.Repositories memberTypeRepository.AddOrUpdate(memberType); unitOfWork.Commit(); - var member = MockedMember.CreateSimpleContent(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty", -1); + var member = MockedMember.CreateSimpleMember(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty"); repository.AddOrUpdate(member); unitOfWork.Commit(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 3ccd151713..e16cee6a68 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -58,9 +58,11 @@ namespace Umbraco.Tests.Persistence.Repositories var sut = repository.Get(memberType.Id); + var standardProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + Assert.That(sut, Is.Not.Null); Assert.That(sut.PropertyGroups.Count(), Is.EqualTo(1)); - Assert.That(sut.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.StandardPropertyTypeStubs.Count)); + Assert.That(sut.PropertyTypes.Count(), Is.EqualTo(3 + standardProps.Count)); Assert.That(sut.PropertyGroups.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); Assert.That(sut.PropertyTypes.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); @@ -91,6 +93,33 @@ namespace Umbraco.Tests.Persistence.Repositories } } + //NOTE: This tests for left join logic (rev 7b14e8eacc65f82d4f184ef46c23340c09569052) + [Test] + public void Can_Get_All_Members_When_No_Properties_Assigned() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var memberType1 = MockedContentTypes.CreateSimpleMemberType(); + memberType1.PropertyTypeCollection.Clear(); + repository.AddOrUpdate(memberType1); + unitOfWork.Commit(); + + var memberType2 = MockedContentTypes.CreateSimpleMemberType(); + memberType2.PropertyTypeCollection.Clear(); + memberType2.Name = "AnotherType"; + memberType2.Alias = "anotherType"; + repository.AddOrUpdate(memberType2); + unitOfWork.Commit(); + + var result = repository.GetAll(); + + //there are 3 because of the Member type created for init data + Assert.AreEqual(3, result.Count()); + } + } + [Test] public void Can_Get_Member_Type_By_Id() { @@ -119,11 +148,33 @@ namespace Umbraco.Tests.Persistence.Repositories memberType = repository.Get(memberType.Id); - Assert.That(memberType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.StandardPropertyTypeStubs.Count)); + Assert.That(memberType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); Assert.That(memberType.PropertyGroups.Count(), Is.EqualTo(1)); } } + //This is to show that new properties are created for each member type - there was a bug before + // that was reusing the same properties with the same Ids between member types + [Test] + public void Built_In_Member_Type_Properties_Are_Not_Reused_For_Different_Member_Types() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + IMemberType memberType1 = MockedContentTypes.CreateSimpleMemberType(); + IMemberType memberType2 = MockedContentTypes.CreateSimpleMemberType("test2"); + repository.AddOrUpdate(memberType1); + repository.AddOrUpdate(memberType2); + unitOfWork.Commit(); + + var m1Ids = memberType1.PropertyTypes.Select(x => x.Id).ToArray(); + var m2Ids = memberType2.PropertyTypes.Select(x => x.Id).ToArray(); + + Assert.IsFalse(m1Ids.Any(m2Ids.Contains)); + } + } + [Test] public void Can_Delete_MemberType() { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs new file mode 100644 index 0000000000..7ddbe972ec --- /dev/null +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + [TestFixture, RequiresSTA] + public class MemberServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void Can_Delete_member() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + ServiceContext.MemberService.Delete(member); + var deleted = ServiceContext.MemberService.GetById(member.Id); + + // Assert + Assert.That(deleted, Is.Null); + } + + [Test] + public void ContentXml_Created_When_Saved() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + var xml = DatabaseContext.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = member.Id }); + Assert.IsNotNull(xml); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs new file mode 100644 index 0000000000..3bbb6b7bf6 --- /dev/null +++ b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + [TestFixture, RequiresSTA] + public class MemberTypeServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void Deleting_PropertyType_Removes_The_Property_From_Member() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + var initProps = member.Properties.Count; + var initPropTypes = member.PropertyTypes.Count(); + + //remove a property (NOT ONE OF THE DEFAULTS) + var standardProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + memberType.RemovePropertyType(memberType.PropertyTypes.First(x => standardProps.ContainsKey(x.Alias) == false).Alias); + ServiceContext.MemberTypeService.Save(memberType); + + //re-load it from the db + member = ServiceContext.MemberService.GetById(member.Id); + + Assert.AreEqual(initPropTypes - 1, member.PropertyTypes.Count()); + Assert.AreEqual(initProps - 1, member.Properties.Count); + } + + [Test] + public void Rebuild_Member_Xml_On_Alias_Change() + { + var contentType1 = MockedContentTypes.CreateSimpleMemberType("test1", "Test1"); + var contentType2 = MockedContentTypes.CreateSimpleMemberType("test2", "Test2"); + ServiceContext.MemberTypeService.Save(contentType1); + ServiceContext.MemberTypeService.Save(contentType2); + var contentItems1 = MockedMember.CreateSimpleMember(contentType1, 10).ToArray(); + contentItems1.ForEach(x => ServiceContext.MemberService.Save(x)); + var contentItems2 = MockedMember.CreateSimpleMember(contentType2, 5).ToArray(); + contentItems2.ForEach(x => ServiceContext.MemberService.Save(x)); + //only update the contentType1 alias which will force an xml rebuild for all content of that type + contentType1.Alias = "newAlias"; + ServiceContext.MemberTypeService.Save(contentType1); + + foreach (var c in contentItems1) + { + var xml = DatabaseContext.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = c.Id }); + Assert.IsNotNull(xml); + Assert.IsTrue(xml.Xml.StartsWith("("WHERE nodeId = @Id", new { Id = c.Id }); + Assert.IsNotNull(xml); + Assert.IsTrue(xml.Xml.StartsWith(" ServiceContext.MemberService.Save(x)); + + var alias = contentType1.PropertyTypes.First(x => standardProps.ContainsKey(x.Alias) == false).Alias; + var elementToMatch = "<" + alias + ">"; + + foreach (var c in contentItems1) + { + var xml = DatabaseContext.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = c.Id }); + Assert.IsNotNull(xml); + Assert.IsTrue(xml.Xml.Contains(elementToMatch)); //verify that it is there before we remove the property + } + + //remove a property (NOT ONE OF THE DEFAULTS) + contentType1.RemovePropertyType(alias); + ServiceContext.MemberTypeService.Save(contentType1); + + var reQueried = ServiceContext.ContentTypeService.GetContentType(contentType1.Id); + var reContent = ServiceContext.ContentService.GetById(contentItems1.First().Id); + + foreach (var c in contentItems1) + { + var xml = DatabaseContext.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = c.Id }); + Assert.IsNotNull(xml); + Assert.IsFalse(xml.Xml.Contains(elementToMatch)); //verify that it is no longer there + } + } + + //[Test] + //public void Can_Save_MemberType_Structure_And_Create_A_Member_Based_On_It() + //{ + // // Arrange + // var cs = ServiceContext.MemberService; + // var cts = ServiceContext.MemberTypeService; + // var dtdYesNo = ServiceContext.DataTypeService.GetDataTypeDefinitionById(-49); + // var ctBase = new MemberType(-1) { Name = "Base", Alias = "Base", Icon = "folder.gif", Thumbnail = "folder.png" }; + // ctBase.AddPropertyType(new PropertyType(dtdYesNo) + // { + // Name = "Hide From Navigation", + // Alias = Constants.Conventions.Content.NaviHide + // } + // /*,"Navigation"*/); + // cts.Save(ctBase); + + // var ctHomePage = new MemberType(ctBase) + // { + // Name = "Home Page", + // Alias = "HomePage", + // Icon = "settingDomain.gif", + // Thumbnail = "folder.png", + // AllowedAsRoot = true + // }; + // ctHomePage.AddPropertyType(new PropertyType(dtdYesNo) { Name = "Some property", Alias = "someProperty" } + // /*,"Navigation"*/); + // cts.Save(ctHomePage); + + // // Act + // var homeDoc = cs.CreateMember("Test", "test@test.com", "test", "HomePage"); + + // // Assert + // Assert.That(ctBase.HasIdentity, Is.True); + // Assert.That(ctHomePage.HasIdentity, Is.True); + // Assert.That(homeDoc.HasIdentity, Is.True); + // Assert.That(homeDoc.ContentTypeId, Is.EqualTo(ctHomePage.Id)); + //} + + //[Test] + //public void Can_Create_And_Save_MemberType_Composition() + //{ + // /* + // * Global + // * - Components + // * - Category + // */ + // var service = ServiceContext.ContentTypeService; + // var global = MockedContentTypes.CreateSimpleContentType("global", "Global"); + // service.Save(global); + + // var components = MockedContentTypes.CreateSimpleContentType("components", "Components", global); + // service.Save(components); + + // var component = MockedContentTypes.CreateSimpleContentType("component", "Component", components); + // service.Save(component); + + // var category = MockedContentTypes.CreateSimpleContentType("category", "Category", global); + // service.Save(category); + + // var success = category.AddContentType(component); + + // Assert.That(success, Is.False); + //} + + //[Test] + //public void Can_Remove_ContentType_Composition_From_ContentType() + //{ + // //Test for U4-2234 + // var cts = ServiceContext.ContentTypeService; + // //Arrange + // var component = CreateComponent(); + // cts.Save(component); + // var banner = CreateBannerComponent(component); + // cts.Save(banner); + // var site = CreateSite(); + // cts.Save(site); + // var homepage = CreateHomepage(site); + // cts.Save(homepage); + + // //Add banner to homepage + // var added = homepage.AddContentType(banner); + // cts.Save(homepage); + + // //Assert composition + // var bannerExists = homepage.ContentTypeCompositionExists(banner.Alias); + // var bannerPropertyExists = homepage.CompositionPropertyTypes.Any(x => x.Alias.Equals("bannerName")); + // Assert.That(added, Is.True); + // Assert.That(bannerExists, Is.True); + // Assert.That(bannerPropertyExists, Is.True); + // Assert.That(homepage.CompositionPropertyTypes.Count(), Is.EqualTo(6)); + + // //Remove banner from homepage + // var removed = homepage.RemoveContentType(banner.Alias); + // cts.Save(homepage); + + // //Assert composition + // var bannerStillExists = homepage.ContentTypeCompositionExists(banner.Alias); + // var bannerPropertyStillExists = homepage.CompositionPropertyTypes.Any(x => x.Alias.Equals("bannerName")); + // Assert.That(removed, Is.True); + // Assert.That(bannerStillExists, Is.False); + // Assert.That(bannerPropertyStillExists, Is.False); + // Assert.That(homepage.CompositionPropertyTypes.Count(), Is.EqualTo(4)); + //} + + //[Test] + //public void Can_Copy_ContentType_By_Performing_Clone() + //{ + // // 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 sut = simpleContentType.Clone("newcategory"); + // service.Save(sut); + + // // Assert + // Assert.That(sut.HasIdentity, Is.True); + + // var contentType = service.GetContentType(sut.Id); + // var category = service.GetContentType(categoryId); + + // Assert.That(contentType.CompositionAliases().Any(x => x.Equals("meta")), Is.True); + // Assert.AreEqual(contentType.ParentId, category.ParentId); + // Assert.AreEqual(contentType.Level, category.Level); + // Assert.AreEqual(contentType.PropertyTypes.Count(), category.PropertyTypes.Count()); + // Assert.AreNotEqual(contentType.Id, category.Id); + // Assert.AreNotEqual(contentType.Key, category.Key); + // Assert.AreNotEqual(contentType.Path, category.Path); + // Assert.AreNotEqual(contentType.SortOrder, category.SortOrder); + // Assert.AreNotEqual(contentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id, category.PropertyTypes.First(x => x.Alias.Equals("title")).Id); + // Assert.AreNotEqual(contentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id, category.PropertyGroups.First(x => x.Name.Equals("Content")).Id); + + //} + + //private ContentType CreateComponent() + //{ + // var component = new ContentType(-1) + // { + // Alias = "component", + // Name = "Component", + // Description = "ContentType used for Component grouping", + // Icon = ".sprTreeDoc3", + // Thumbnail = "doc.png", + // SortOrder = 1, + // CreatorId = 0, + // Trashed = false + // }; + + // var contentCollection = new PropertyTypeCollection(); + // contentCollection.Add(new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) { Alias = "componentGroup", Name = "Component Group", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + // component.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Component", SortOrder = 1 }); + + // return component; + //} + + //private ContentType CreateBannerComponent(ContentType parent) + //{ + // var banner = new ContentType(parent) + // { + // Alias = "banner", + // Name = "Banner Component", + // Description = "ContentType used for Banner Component", + // Icon = ".sprTreeDoc3", + // Thumbnail = "doc.png", + // SortOrder = 1, + // CreatorId = 0, + // Trashed = false + // }; + + // var propertyType = new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) + // { + // Alias = "bannerName", + // Name = "Banner Name", + // Description = "", + // HelpText = "", + // Mandatory = false, + // SortOrder = 2, + // DataTypeDefinitionId = -88 + // }; + // banner.AddPropertyType(propertyType, "Component"); + // return banner; + //} + + //private ContentType CreateSite() + //{ + // var site = new ContentType(-1) + // { + // Alias = "site", + // Name = "Site", + // Description = "ContentType used for Site inheritence", + // Icon = ".sprTreeDoc3", + // Thumbnail = "doc.png", + // SortOrder = 2, + // CreatorId = 0, + // Trashed = false + // }; + + // var contentCollection = new PropertyTypeCollection(); + // contentCollection.Add(new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) { Alias = "hostname", Name = "Hostname", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + // site.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Site Settings", SortOrder = 1 }); + + // return site; + //} + + //private ContentType CreateHomepage(ContentType parent) + //{ + // var contentType = new ContentType(parent) + // { + // Alias = "homepage", + // Name = "Homepage", + // Description = "ContentType used for the Homepage", + // Icon = ".sprTreeDoc3", + // Thumbnail = "doc.png", + // SortOrder = 1, + // CreatorId = 0, + // Trashed = false + // }; + + // var contentCollection = new PropertyTypeCollection(); + // contentCollection.Add(new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + // contentCollection.Add(new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); + // contentCollection.Add(new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); + + // contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + + // return contentType; + //} + + //private IEnumerable CreateContentTypeHierarchy() + //{ + // //create the master type + // var masterContentType = MockedContentTypes.CreateSimpleContentType("masterContentType", "MasterContentType"); + // masterContentType.Key = new Guid("C00CA18E-5A9D-483B-A371-EECE0D89B4AE"); + // ServiceContext.ContentTypeService.Save(masterContentType); + + // //add the one we just created + // var list = new List { masterContentType }; + + // for (var i = 0; i < 10; i++) + // { + // var contentType = MockedContentTypes.CreateSimpleContentType("childType" + i, "ChildType" + i, + // //make the last entry in the list, this one's parent + // list.Last()); + + // list.Add(contentType); + // } + + // return list; + //} + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 84b6d80343..088c94b975 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -281,12 +281,12 @@ namespace Umbraco.Tests.TestHelpers.Entities return mediaType; } - public static MemberType CreateSimpleMemberType() + public static MemberType CreateSimpleMemberType(string alias = null, string name = null) { var contentType = new MemberType(-1) { - Alias = "simple", - Name = "Simple Page", + Alias = alias ?? "simple", + Name = name ?? "Simple Page", Description = "Some member type", Icon = ".sprTreeDoc3", Thumbnail = "doc.png", diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs index 207f4db34f..ac3949eb32 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -28,6 +29,26 @@ namespace Umbraco.Tests.TestHelpers.Entities member.ResetDirtyProperties(false); return member; + } + + public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount) + { + var list = new List(); + + for (int i = 0; i < amount; i++) + { + var name = "Member No-" + i; + var member = new Member(name, "test" + i + "@test.com", "test" + i, "test" + i, memberType); + member.SetValue("title", name + " member" + i); + member.SetValue("bodyText", "This is a subpage" + i); + member.SetValue("author", "John Doe" + i); + + member.ResetDirtyProperties(false); + + list.Add(member); + } + + return list; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 8e5c1e36d5..5b4b0b41ae 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -280,6 +280,8 @@ + + diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index dbfb5ea367..220063c6a0 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -219,8 +219,10 @@ namespace Umbraco.Web.Models.Mapping { protected override IEnumerable ResolveCore(IMember source) { + var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + //remove all membership properties, these values are set with the membership provider. - var exclude = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray(); + var exclude = defaultProps.Select(x => x.Value.Alias).ToArray(); return source.Properties .Where(x => exclude.Contains(x.Alias) == false) diff --git a/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs index c04b556e58..cc6e244886 100644 --- a/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs @@ -93,8 +93,10 @@ namespace Umbraco.Web.WebApi.Binders throw new InvalidOperationException("Could not find member with key " + key); } + var standardProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + //remove all membership properties, these values are set with the membership provider. - var exclude = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray(); + var exclude = standardProps.Select(x => x.Value.Alias).ToArray(); foreach (var remove in exclude) { @@ -160,8 +162,9 @@ namespace Umbraco.Web.WebApi.Binders /// private void FilterMembershipProviderProperties(IContentTypeBase contentType) { + var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); //remove all membership properties, these values are set with the membership provider. - var exclude = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray(); + var exclude = defaultProps.Select(x => x.Value.Alias).ToArray(); FilterContentTypeProperties(contentType, exclude); } @@ -190,7 +193,8 @@ namespace Umbraco.Web.WebApi.Binders protected override bool ValidateProperties(ContentItemBasic postedItem, HttpActionContext actionContext) { var propertiesToValidate = postedItem.Properties.ToList(); - var exclude = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray(); + var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + var exclude = defaultProps.Select(x => x.Value.Alias).ToArray(); foreach (var remove in exclude) { propertiesToValidate.RemoveAll(property => property.Alias == remove);