diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index d4dd6f48f0..1278ff0e33 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Cache public const string MemberLibraryCacheKey = "UL_GetMember"; public const string MemberBusinessLogicCacheKey = "MemberCacheItem_"; - + public const string TemplateFrontEndCacheKey = "template"; public const string TemplateBusinessLogicCacheKey = "UmbracoTemplateCache"; diff --git a/src/Umbraco.Core/Models/IMemberGroup.cs b/src/Umbraco.Core/Models/IMemberGroup.cs index f865a7d991..5c3741997b 100644 --- a/src/Umbraco.Core/Models/IMemberGroup.cs +++ b/src/Umbraco.Core/Models/IMemberGroup.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.EntityBase; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -16,5 +17,10 @@ namespace Umbraco.Core.Models /// Profile of the user who created this Entity /// int CreatorId { get; set; } + + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + IDictionary AdditionalData { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index 5d77c92385..e52448a11d 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; @@ -12,6 +13,11 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class MemberGroup : Entity, IMemberGroup { + public MemberGroup() + { + AdditionalData = new Dictionary(); + } + private string _name; private int _creatorId; @@ -26,6 +32,14 @@ namespace Umbraco.Core.Models { SetPropertyValueAndDetectChanges(o => { + if (_name != value) + { + //if the name has changed, add the value to the additional data, + //this is required purely for event handlers to know the previous name of the group + //so we can keep the public access up to date. + AdditionalData["previousName"] = _name; + } + _name = value; return _name; }, _name, NameSelector); @@ -45,6 +59,8 @@ namespace Umbraco.Core.Models } } + public IDictionary AdditionalData { get; private set; } + /// /// Method to call when Entity is being saved /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberGroupRepository.cs index 591d621403..2750457271 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberGroupRepository.cs @@ -5,11 +5,18 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IMemberGroupRepository : IRepositoryQueryable { + /// + /// Gets a member group by it's name + /// + /// + /// + IMemberGroup GetByName(string name); + /// /// Creates the new member group if it doesn't already exist /// /// - void CreateIfNotExists(string roleName); + IMemberGroup CreateIfNotExists(string roleName); /// /// Returns the member groups for a given member diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 3ae62f62f9..4b9d39c99c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; @@ -9,6 +10,8 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; +using Umbraco.Core.Cache; namespace Umbraco.Core.Persistence.Repositories { @@ -16,12 +19,20 @@ namespace Umbraco.Core.Persistence.Repositories internal class MemberGroupRepository : PetaPocoRepositoryBase, IMemberGroupRepository { - public MemberGroupRepository(IDatabaseUnitOfWork work) : base(work) + private readonly CacheHelper _cacheHelper; + + public MemberGroupRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper) + : base(work) { + if (cacheHelper == null) throw new ArgumentNullException("cacheHelper"); + _cacheHelper = cacheHelper; } - public MemberGroupRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + public MemberGroupRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, CacheHelper cacheHelper) + : base(work, cache) { + if (cacheHelper == null) throw new ArgumentNullException("cacheHelper"); + _cacheHelper = cacheHelper; } private readonly MemberGroupFactory _modelFactory = new MemberGroupFactory(); @@ -86,6 +97,11 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new[] { + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", "DELETE FROM cmsMember2MemberGroup WHERE MemberGroup = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; @@ -117,26 +133,52 @@ namespace Umbraco.Core.Persistence.Repositories var dto = _modelFactory.BuildDto(entity); Database.Update(dto); - + ((ICanBeDirty)entity).ResetDirtyProperties(); } - public void CreateIfNotExists(string roleName) + public IMemberGroup GetByName(string name) + { + return _cacheHelper.RuntimeCache.GetCacheItem( + string.Format("{0}.{1}", typeof (IMemberGroup).FullName, name), + () => + { + var qry = new Query().Where(group => group.Name.Equals(name)); + var result = GetByQuery(qry); + return result.FirstOrDefault(); + }, + //cache for 5 mins since that is the default in the RuntimeCacheProvider + TimeSpan.FromMinutes(5), + //sliding is true + true); + } + + public IMemberGroup CreateIfNotExists(string roleName) { using (var transaction = Database.GetTransaction()) { var qry = new Query().Where(group => group.Name.Equals(roleName)); var result = GetByQuery(qry); - if (result.Any()) return; + if (result.Any()) return null; - PersistNewItem(new MemberGroup + var grp = new MemberGroup { Name = roleName - }); + }; + PersistNewItem(grp); + + if (SavingMemberGroup.IsRaisedEventCancelled(new SaveEventArgs(grp), this)) + { + return null; + } transaction.Complete(); - } + + SavedMemberGroup.RaiseEvent(new SaveEventArgs(grp), this); + + return grp; + } } public IEnumerable GetMemberGroupsForMember(int memberId) @@ -244,10 +286,18 @@ namespace Umbraco.Core.Persistence.Repositories .Where("umbracoNode." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("text") + " in (@names)", new { names = roleNames }); var existingRoles = Database.Fetch(existingSql).Select(x => x.Text); var missingRoles = roleNames.Except(existingRoles); - foreach (var m in missingRoles) + var missingGroups = missingRoles.Select(x => new MemberGroup {Name = x}).ToArray(); + + if (SavingMemberGroup.IsRaisedEventCancelled(new SaveEventArgs(missingGroups), this)) { - PersistNewItem(new MemberGroup { Name = m }); + return; } + foreach (var m in missingGroups) + { + PersistNewItem(m); + } + SavedMemberGroup.RaiseEvent(new SaveEventArgs(missingGroups), this); + //now go get all the dto's for roles with these role names var rolesForNames = Database.Fetch(existingSql).ToArray(); @@ -318,5 +368,15 @@ namespace Umbraco.Core.Persistence.Repositories [Column("MemberGroup")] public int MemberGroupId { get; set; } } + + /// + /// Occurs before Save + /// + internal static event TypedEventHandler> SavingMemberGroup; + + /// + /// Occurs after Save + /// + internal static event TypedEventHandler> SavedMemberGroup; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 935c199749..0030ae5d66 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -166,7 +166,7 @@ namespace Umbraco.Core.Persistence internal virtual IMemberGroupRepository CreateMemberGroupRepository(IDatabaseUnitOfWork uow) { - return new MemberGroupRepository(uow, _disableAllCache ? (IRepositoryCacheProvider)NullCacheProvider.Current : RuntimeCacheProvider.Current); + return new MemberGroupRepository(uow, _disableAllCache ? (IRepositoryCacheProvider)NullCacheProvider.Current : RuntimeCacheProvider.Current, _cacheHelper); } internal virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) diff --git a/src/Umbraco.Core/Services/IMemberGroupService.cs b/src/Umbraco.Core/Services/IMemberGroupService.cs new file mode 100644 index 0000000000..966bb87e4c --- /dev/null +++ b/src/Umbraco.Core/Services/IMemberGroupService.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IMemberGroupService : IService + { + IEnumerable GetAll(); + IMemberGroup GetById(int id); + IMemberGroup GetByName(string name); + void Save(IMemberGroup memberGroup, bool raiseEvents = true); + void Delete(IMemberGroup memberGroup); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberGroupService.cs b/src/Umbraco.Core/Services/MemberGroupService.cs new file mode 100644 index 0000000000..ae12614d56 --- /dev/null +++ b/src/Umbraco.Core/Services/MemberGroupService.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + public class MemberGroupService : IMemberGroupService + { + private readonly RepositoryFactory _repositoryFactory; + private readonly IDatabaseUnitOfWorkProvider _uowProvider; + + public MemberGroupService(RepositoryFactory repositoryFactory) + : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) + { + } + + public MemberGroupService(IDatabaseUnitOfWorkProvider provider) + : this(provider, new RepositoryFactory()) + { + } + + public MemberGroupService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) + { + _repositoryFactory = repositoryFactory; + _uowProvider = provider; + + //Proxy events! + MemberGroupRepository.SavedMemberGroup += MemberGroupRepository_SavedMemberGroup; + MemberGroupRepository.SavingMemberGroup += MemberGroupRepository_SavingMemberGroup; + } + + #region Proxied event handlers + void MemberGroupRepository_SavingMemberGroup(IMemberGroupRepository sender, SaveEventArgs e) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(e.SavedEntities), this)) + { + e.Cancel = true; + } + } + + void MemberGroupRepository_SavedMemberGroup(IMemberGroupRepository sender, SaveEventArgs e) + { + Saved.RaiseEvent(new SaveEventArgs(e.SavedEntities, false), this); + } + #endregion + + public IEnumerable GetAll() + { + using (var repository = _repositoryFactory.CreateMemberGroupRepository(_uowProvider.GetUnitOfWork())) + { + return repository.GetAll(); + } + } + + public IMemberGroup GetById(int id) + { + using (var repository = _repositoryFactory.CreateMemberGroupRepository(_uowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + public IMemberGroup GetByName(string name) + { + using (var repository = _repositoryFactory.CreateMemberGroupRepository(_uowProvider.GetUnitOfWork())) + { + return repository.GetByName(name); + } + } + + public void Save(IMemberGroup memberGroup, bool raiseEvents = true) + { + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(memberGroup), this)) + { + return; + } + } + + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.AddOrUpdate(memberGroup); + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(memberGroup, false), this); + } + + public void Delete(IMemberGroup memberGroup) + { + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(memberGroup), this)) + return; + + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.Delete(memberGroup); + uow.Commit(); + } + + Deleted.RaiseEvent(new DeleteEventArgs(memberGroup, false), this); + } + + /// + /// Occurs before Delete of a member group + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete of a member group + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Save of a member group + /// + /// + /// We need to proxy these events because the events need to take place at the repo level + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save of a member group + /// + /// + /// We need to proxy these events because the events need to take place at the repo level + /// + public static event TypedEventHandler> Saved; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index ac6d652219..08b94380bb 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -19,26 +19,31 @@ namespace Umbraco.Core.Services /// /// Represents the MemberService. /// - internal class MemberService : IMemberService + public class MemberService : IMemberService { private readonly RepositoryFactory _repositoryFactory; + private readonly IMemberGroupService _memberGroupService; private readonly IDatabaseUnitOfWorkProvider _uowProvider; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - public MemberService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) + public MemberService(RepositoryFactory repositoryFactory, IMemberGroupService memberGroupService) + : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory, memberGroupService) { } - public MemberService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory()) + public MemberService(IDatabaseUnitOfWorkProvider provider, IMemberGroupService memberGroupService) + : this(provider, new RepositoryFactory(), memberGroupService) { } - public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) + public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IMemberGroupService memberGroupService) { + if (provider == null) throw new ArgumentNullException("provider"); + if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); + if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); _repositoryFactory = repositoryFactory; + _memberGroupService = memberGroupService; _uowProvider = provider; } @@ -843,10 +848,11 @@ namespace Umbraco.Core.Services { var qry = new Query().Where(g => g.Name == roleName); var found = repository.GetByQuery(qry).ToArray(); + foreach (var memberGroup in found) { - repository.Delete(memberGroup); - } + _memberGroupService.Delete(memberGroup); + } return found.Any(); } } @@ -977,8 +983,7 @@ namespace Umbraco.Core.Services } #region Event Handlers - - + /// /// Occurs before Delete /// @@ -998,7 +1003,7 @@ namespace Umbraco.Core.Services /// Occurs after Save /// public static event TypedEventHandler> Saved; - + #endregion ///// diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 63f71bd5e2..276aedc1a5 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core.Services //private Lazy _sectionService; //private Lazy _macroService; private Lazy _memberTypeService; + private Lazy _memberGroupService; private Lazy _notificationService; /// @@ -45,6 +46,7 @@ namespace Umbraco.Core.Services /// /// /// + /// public ServiceContext( IContentService contentService, IMediaService mediaService, @@ -54,7 +56,8 @@ namespace Umbraco.Core.Services ILocalizationService localizationService, PackagingService packagingService, IEntityService entityService, - IRelationService relationService/*, + IRelationService relationService, + IMemberGroupService memberGroupService/*, ISectionService sectionService, IApplicationTreeService treeService*/) { @@ -67,6 +70,7 @@ namespace Umbraco.Core.Services _packagingService = new Lazy(() => packagingService); _entityService = new Lazy(() => entityService); _relationService = new Lazy(() => relationService); + _memberGroupService = new Lazy(() => memberGroupService); //_sectionService = new Lazy(() => sectionService); //_treeService = new Lazy(() => treeService); } @@ -109,7 +113,7 @@ namespace Umbraco.Core.Services _userService = new Lazy(() => new UserService(provider, repositoryFactory.Value)); if (_memberService == null) - _memberService = new Lazy(() => new MemberService(provider, repositoryFactory.Value)); + _memberService = new Lazy(() => new MemberService(provider, repositoryFactory.Value, _memberGroupService.Value)); if (_contentService == null) _contentService = new Lazy(() => new ContentService(provider, repositoryFactory.Value, publishingStrategy)); @@ -149,6 +153,9 @@ namespace Umbraco.Core.Services if (_memberTypeService == null) _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory.Value, _memberService.Value)); + + if (_memberGroupService == null) + _memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory.Value)); } @@ -288,5 +295,13 @@ namespace Umbraco.Core.Services get { return _memberTypeService.Value; } } + /// + /// Gets the MemberGroupService + /// + public IMemberGroupService MemberGroupService + { + get { return _memberGroupService.Value; } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 43a940a829..5682fd8ec0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -774,6 +774,7 @@ + @@ -786,6 +787,7 @@ + diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index ce049b9679..a3e387ad1e 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -47,7 +47,8 @@ namespace Umbraco.Tests new RelationService( new Mock().Object, new RepositoryFactory(true), - new Mock().Object)/*, + new Mock().Object), + new Mock().Object/*, new Mock().Object, new Mock().Object*/); Assert.Pass(); @@ -66,26 +67,27 @@ namespace Umbraco.Tests var appCtx = new ApplicationContext( new DatabaseContext(new Mock().Object), new ServiceContext( - new Mock().Object, - new Mock().Object, - new Mock().Object, - new Mock().Object, - new Mock().Object, - new Mock().Object, - new PackagingService( new Mock().Object, - new Mock().Object, new Mock().Object, + new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, - new RepositoryFactory(true), - new Mock().Object), - new Mock().Object, - new RelationService( - new Mock().Object, - new RepositoryFactory(true), - new Mock().Object)), + new PackagingService( + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new RepositoryFactory(true), + new Mock().Object), + new Mock().Object, + new RelationService( + new Mock().Object, + new RepositoryFactory(true), + new Mock().Object), + new Mock().Object), CacheHelper.CreateDisabledCacheHelper()); Assert.Pass(); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 584091f1eb..3937dcb8c9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Persistence.Repositories private MemberRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out MemberTypeRepository memberTypeRepository, out MemberGroupRepository memberGroupRepository) { memberTypeRepository = new MemberTypeRepository(unitOfWork, NullCacheProvider.Current); - memberGroupRepository = new MemberGroupRepository(unitOfWork, NullCacheProvider.Current); + memberGroupRepository = new MemberGroupRepository(unitOfWork, NullCacheProvider.Current, CacheHelper.CreateDisabledCacheHelper()); var repository = new MemberRepository(unitOfWork, NullCacheProvider.Current, memberTypeRepository, memberGroupRepository); return repository; } diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index ca2861ab5c..791617d8a6 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -113,6 +113,8 @@ namespace Umbraco.Web.Cache MemberService.Saved += MemberServiceSaved; MemberService.Deleted += MemberServiceDeleted; + MemberGroupService.Saved += MemberGroupService_Saved; + MemberGroupService.Deleted += MemberGroupService_Deleted; //Bind to media events @@ -555,7 +557,7 @@ namespace Umbraco.Web.Cache #region Member event handlers - void MemberServiceDeleted(IMemberService sender, Core.Events.DeleteEventArgs e) + static void MemberServiceDeleted(IMemberService sender, Core.Events.DeleteEventArgs e) { foreach (var m in e.DeletedEntities.ToArray()) { @@ -563,7 +565,7 @@ namespace Umbraco.Web.Cache } } - void MemberServiceSaved(IMemberService sender, Core.Events.SaveEventArgs e) + static void MemberServiceSaved(IMemberService sender, Core.Events.SaveEventArgs e) { foreach (var m in e.SavedEntities.ToArray()) { @@ -572,5 +574,24 @@ namespace Umbraco.Web.Cache } #endregion + + #region Member group event handlers + + static void MemberGroupService_Deleted(IMemberGroupService sender, Core.Events.DeleteEventArgs e) + { + foreach (var m in e.DeletedEntities.ToArray()) + { + DistributedCache.Instance.RemoveMemberGroupCache(m.Id); + } + } + + static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) + { + foreach (var m in e.SavedEntities.ToArray()) + { + DistributedCache.Instance.RemoveMemberGroupCache(m.Id); + } + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index f07bc02b1d..e76a8f448a 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Cache { /// - /// A cache refresher to ensure content type cache is updated when members change + /// A cache refresher to ensure content type cache is updated when content types change - this is applicable to content, media and member types /// /// /// This is not intended to be used directly in your code diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index 1f7307b289..b66dd2174a 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -40,6 +40,7 @@ namespace Umbraco.Web.Cache public const string TemplateRefresherId = "DD12B6A0-14B9-46e8-8800-C154F74047C8"; public const string PageCacheRefresherId = "27AB3022-3DFA-47b6-9119-5945BC88FD66"; public const string MemberCacheRefresherId = "E285DF34-ACDC-4226-AE32-C0CB5CF388DA"; + public const string MemberGroupCacheRefresherId = "187F236B-BD21-4C85-8A7C-29FBA3D6C00C"; public const string MediaCacheRefresherId = "B29286DD-2D40-4DDB-B325-681226589FEC"; public const string MacroCacheRefresherId = "7B1E683C-5F34-43dd-803D-9699EA1E98CA"; public const string UserCacheRefresherId = "E057AF6D-2EE6-41F4-8045-3694010F0AA6"; diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index a1339ec8cd..faede0bffc 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -237,7 +237,7 @@ namespace Umbraco.Web.Cache #region Member cache /// - /// Refreshes the cache amongst servers for a member + /// Refreshes the cache among servers for a member /// /// /// @@ -247,7 +247,7 @@ namespace Umbraco.Web.Cache } /// - /// Removes the cache amongst servers for a member + /// Removes the cache among servers for a member /// /// /// @@ -258,6 +258,29 @@ namespace Umbraco.Web.Cache #endregion + #region Member group cache + /// + /// Refreshes the cache among servers for a member group + /// + /// + /// + public static void RefreshMemberGroupCache(this DistributedCache dc, int memberGroupId) + { + dc.Refresh(new Guid(DistributedCache.MemberGroupCacheRefresherId), memberGroupId); + } + + /// + /// Removes the cache among servers for a member group + /// + /// + /// + public static void RemoveMemberGroupCache(this DistributedCache dc, int memberGroupId) + { + dc.Remove(new Guid(DistributedCache.MemberGroupCacheRefresherId), memberGroupId); + } + + #endregion + #region Media Cache /// diff --git a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs new file mode 100644 index 0000000000..df1423ef1e --- /dev/null +++ b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs @@ -0,0 +1,115 @@ +using System; +using System.Linq; +using System.Web.Script.Serialization; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Caching; + +namespace Umbraco.Web.Cache +{ + public sealed class MemberGroupCacheRefresher : JsonCacheRefresherBase + { + #region Static helpers + + /// + /// Converts the json to a JsonPayload object + /// + /// + /// + private static JsonPayload[] DeserializeFromJsonPayload(string json) + { + var serializer = new JavaScriptSerializer(); + var jsonObject = serializer.Deserialize(json); + return jsonObject; + } + + /// + /// Creates the custom Json payload used to refresh cache amongst the servers + /// + /// + /// + internal static string SerializeToJsonPayload(params IMemberGroup[] groups) + { + var serializer = new JavaScriptSerializer(); + var items = groups.Select(FromMemberGroup).ToArray(); + var json = serializer.Serialize(items); + return json; + } + + /// + /// Converts a macro to a jsonPayload object + /// + /// + /// + private static JsonPayload FromMemberGroup(IMemberGroup group) + { + if (group == null) return null; + + var payload = new JsonPayload + { + Id = group.Id, + Name = group.Name + }; + return payload; + } + + #endregion + + #region Sub classes + + private class JsonPayload + { + public string Name { get; set; } + public int Id { get; set; } + } + + #endregion + + protected override MemberGroupCacheRefresher Instance + { + get { return this; } + } + + public override Guid UniqueIdentifier + { + get { return new Guid(DistributedCache.MemberGroupCacheRefresherId); } + } + + public override string Name + { + get { return "Clears Member Group Cache"; } + } + + public override void Refresh(string jsonPayload) + { + ClearCache(DeserializeFromJsonPayload(jsonPayload)); + base.Refresh(jsonPayload); + } + + public override void Refresh(int id) + { + ClearCache(FromMemberGroup(ApplicationContext.Current.Services.MemberGroupService.GetById(id))); + base.Refresh(id); + } + + public override void Remove(int id) + { + ClearCache(FromMemberGroup(ApplicationContext.Current.Services.MemberGroupService.GetById(id))); + base.Remove(id); + } + + private void ClearCache(params JsonPayload[] payloads) + { + if (payloads == null) return; + + payloads.ForEach(payload => + { + ApplicationContext.Current.ApplicationCache.RuntimeCache + .ClearCacheByKeySearch(string.Format("{0}.{1}", typeof(IMemberGroup).FullName, payload.Name)); + RuntimeCacheProvider.Current.Delete(typeof(IMemberGroup), payload.Id); + }); + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs b/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs new file mode 100644 index 0000000000..86b3066413 --- /dev/null +++ b/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using umbraco.cms.businesslogic.web; +using Umbraco.Core; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Strategies +{ + /// + /// Used to ensure that the access.xml file is kept up to date properly + /// + public sealed class PublicAccessEventHandler : ApplicationEventHandler + { + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + base.ApplicationStarted(umbracoApplication, applicationContext); + + MemberGroupService.Saved += MemberGroupService_Saved; + } + + static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) + { + foreach (var grp in e.SavedEntities) + { + //check if the name has changed + if (grp.AdditionalData.ContainsKey("previousName") + && grp.AdditionalData["previousName"] != null + && grp.AdditionalData["previousName"].ToString().IsNullOrWhiteSpace() == false + && grp.AdditionalData["previousName"].ToString() != grp.Name) + { + Access.RenameMemberShipRole(grp.AdditionalData["previousName"].ToString(), grp.Name); + } + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index aa21df2684..e343d61cd1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -283,6 +283,7 @@ + @@ -396,6 +397,7 @@ + diff --git a/src/umbraco.cms/businesslogic/CMSNode.cs b/src/umbraco.cms/businesslogic/CMSNode.cs index c92f82ec95..4052d89cc4 100644 --- a/src/umbraco.cms/businesslogic/CMSNode.cs +++ b/src/umbraco.cms/businesslogic/CMSNode.cs @@ -420,6 +420,11 @@ namespace umbraco.cms.businesslogic _entity = entity; } + protected internal CMSNode(IEntity entity) + { + _id = entity.Id; + } + #endregion #region Public Methods @@ -1138,6 +1143,13 @@ order by level,sortOrder"; _entity = content; } + internal protected void PopulateCMSNodeFromUmbracoEntity(IAggregateRoot content, Guid objectType) + { + _uniqueID = content.Key; + _nodeObjectType = objectType; + _createDate = content.CreateDate; + } + #endregion #region Private Methods diff --git a/src/umbraco.cms/businesslogic/member/MemberGroup.cs b/src/umbraco.cms/businesslogic/member/MemberGroup.cs index 93494bbfb7..eab3dd4aea 100644 --- a/src/umbraco.cms/businesslogic/member/MemberGroup.cs +++ b/src/umbraco.cms/businesslogic/member/MemberGroup.cs @@ -1,5 +1,8 @@ using System; using System.Data; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; using umbraco.DataLayer; using System.Collections; using umbraco.cms.businesslogic.web; @@ -16,8 +19,15 @@ namespace umbraco.cms.businesslogic.member /// public class MemberGroup : CMSNode { - private static Guid _objectType = new Guid(Constants.ObjectTypes.MemberGroup); - private string _oldGroupName; + private static readonly Guid MemberGroupObjectType = new Guid(Constants.ObjectTypes.MemberGroup); + + private IMemberGroup _memberGroupItem; + + internal MemberGroup(IMemberGroup memberGroup) + : base(memberGroup) + { + _memberGroupItem = memberGroup; + } /// /// Initialize a new object of the MemberGroup class @@ -41,13 +51,11 @@ namespace umbraco.cms.businesslogic.member { get { - return base.Text; + return _memberGroupItem.Name; } set { - // in order to be able to update access.xml if name changes we need to store a reference to the old name - _oldGroupName = Text; - base.Text = value; + _memberGroupItem.Name = value; } } @@ -57,17 +65,23 @@ namespace umbraco.cms.businesslogic.member [Obsolete("Use System.Web.Security.Role.DeleteRole")] public override void delete() { - DeleteEventArgs e = new DeleteEventArgs(); + var e = new DeleteEventArgs(); FireBeforeDelete(e); - if (!e.Cancel) { - // delete member specific data! - SqlHelper.ExecuteNonQuery("Delete from cmsMember2MemberGroup where memberGroup = @id", - SqlHelper.CreateParameter("@id", Id)); + if (e.Cancel) return; - // Delete all content and cmsnode specific data! - base.delete(); - FireAfterDelete(e); + if (_memberGroupItem != null) + { + ApplicationContext.Current.Services.MemberGroupService.Delete(_memberGroupItem); } + else + { + var memberGroup = ApplicationContext.Current.Services.MemberGroupService.GetById(Id); + ApplicationContext.Current.Services.MemberGroupService.Delete(memberGroup); + } + + // Delete all content and cmsnode specific data! + base.delete(); + FireAfterDelete(e); } @@ -76,18 +90,13 @@ namespace umbraco.cms.businesslogic.member /// public override void Save() { - SaveEventArgs e = new SaveEventArgs(); + var e = new SaveEventArgs(); FireBeforeSave(e); + if (e.Cancel) return; - // if the name has changed we need to update the public access - if (_oldGroupName != Text) - { - Access.RenameMemberShipRole(_oldGroupName, Text); - } + ApplicationContext.Current.Services.MemberGroupService.Save(_memberGroupItem); - if (!e.Cancel) { - FireAfterSave(e); - } + FireAfterSave(e); } /// @@ -98,82 +107,51 @@ namespace umbraco.cms.businesslogic.member { get { - Guid[] tmp = getAllUniquesFromObjectType(_objectType); - MemberGroup[] retval = new MemberGroup[tmp.Length]; - - int i = 0; - foreach(Guid g in tmp) - { - retval[i]= new MemberGroup(g); - i++; - } - return retval; + var result = ApplicationContext.Current.Services.MemberGroupService.GetAll(); + return result.Select(x => new MemberGroup(x)).ToArray(); } } - public int[] GetMembersAsIds() { - ArrayList retval = new ArrayList(); - IRecordsReader dr = SqlHelper.ExecuteReader("select member from cmsMember2MemberGroup where memberGroup = @memberGroup", - SqlHelper.CreateParameter("@memberGroup", Id)); - while (dr.Read()) { - retval.Add(dr.GetInt("member")); - } - dr.Close(); + public int[] GetMembersAsIds() + { + var result = ApplicationContext.Current.Services.MemberService.GetMembersInRole(_memberGroupItem.Name); + return result.Select(x => x.Id).ToArray(); + } - return (int[])retval.ToArray(typeof(int)); - } + [Obsolete("Use System.Web.Security.Roles.FindUsersInRole")] + public Member[] GetMembers() + { + var result = ApplicationContext.Current.Services.MemberService.GetMembersInRole(_memberGroupItem.Name); + return result.Select(x => new Member(x)).ToArray(); + } - [Obsolete("Use System.Web.Security.Roles.FindUsersInRole")] - public Member[] GetMembers() { - ArrayList retval = new ArrayList(); - IRecordsReader dr = SqlHelper.ExecuteReader("select member from cmsMember2MemberGroup where memberGroup = @memberGroup", - SqlHelper.CreateParameter("@memberGroup", Id)); - while (dr.Read()) { - retval.Add(new Member(dr.GetInt("member"))); - } - dr.Close(); + public Member[] GetMembers(string usernameToMatch) + { - return (Member[])retval.ToArray(typeof(Member)); - } + var result = ApplicationContext.Current.Services.MemberService.FindMembersInRole( + _memberGroupItem.Name, usernameToMatch, StringPropertyMatchType.StartsWith); - public Member[] GetMembers(string usernameToMatch) { - ArrayList retval = new ArrayList(); - IRecordsReader dr = SqlHelper.ExecuteReader("select member from cmsMember2MemberGroup inner join cmsMember on cmsMember.nodeId = cmsMember2MemberGroup.member where loginName like @username and memberGroup = @memberGroup", - SqlHelper.CreateParameter("@memberGroup", Id), SqlHelper.CreateParameter("@username", usernameToMatch + "%")); - while (dr.Read()) { - retval.Add(new Member(dr.GetInt("member"))); - } - dr.Close(); + return result.Select(x => new Member(x)).ToArray(); + } - return (Member[])retval.ToArray(typeof(Member)); - } + public bool HasMember(int memberId) + { + return SqlHelper.ExecuteScalar("select count(member) from cmsMember2MemberGroup where member = @member and memberGroup = @memberGroup", + SqlHelper.CreateParameter("@member", memberId), + SqlHelper.CreateParameter("@memberGroup", Id)) > 0; + } - public bool HasMember(int memberId) { - return SqlHelper.ExecuteScalar("select count(member) from cmsMember2MemberGroup where member = @member and memberGroup = @memberGroup", - SqlHelper.CreateParameter("@member", memberId), - SqlHelper.CreateParameter("@memberGroup", Id)) > 0; - } - - /// + /// /// Get a membergroup by it's name /// /// Name of the membergroup /// If a MemberGroup with the given name exists, it will return this, else: null - public static MemberGroup GetByName(string name) - { - try - { - return - new MemberGroup(SqlHelper.ExecuteScalar( - "select id from umbracoNode where Text = @text and nodeObjectType = @objectType", - SqlHelper.CreateParameter("@text", name), - SqlHelper.CreateParameter("@objectType", _objectType))); - } - catch - { - return null; - } - } + public static MemberGroup GetByName(string name) + { + var found = ApplicationContext.Current.Services.MemberGroupService.GetByName(name); + if (found == null) return null; + return new MemberGroup(found); + } /// @@ -184,15 +162,40 @@ namespace umbraco.cms.businesslogic.member /// The new MemberGroup public static MemberGroup MakeNew(string Name, BusinessLogic.User u) { - Guid newId = Guid.NewGuid(); - CMSNode.MakeNew(-1,_objectType, u.Id, 1, Name, newId); - MemberGroup mg = new MemberGroup(newId); - NewEventArgs e = new NewEventArgs(); + var group = new global::Umbraco.Core.Models.MemberGroup {Name = Name}; + ApplicationContext.Current.Services.MemberGroupService.Save(group); + + var mg = new MemberGroup(group); + var e = new NewEventArgs(); mg.OnNew(e); return mg; } - //EVENTS + protected override void setupNode() + { + if (Id == -1) + { + base.setupNode(); + return; + } + + var group = ApplicationContext.Current.Services.MemberGroupService.GetById(Id); + + if (group == null) + throw new ArgumentException(string.Format("No Member exists with id '{0}'", Id)); + + SetupNode(group); + } + + private void SetupNode(IMemberGroup group) + { + _memberGroupItem = group; + + //Setting private properties + base.PopulateCMSNodeFromUmbracoEntity(_memberGroupItem, MemberGroupObjectType); + } + + //EVENTS /// /// The save event handler ///