diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 542cceac40..cab511962d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy); + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Expression> orderBy); /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 34d4100b3e..b97283542e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -181,27 +181,27 @@ namespace Umbraco.Core.Persistence.Repositories umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); } - Func createSql = url => Sql().SelectAll() - .From() - .InnerJoin() - .On(left => left.PropertyTypeId, right => right.Id) - .Where(x => x.Alias == "umbracoFile") - .Where(x => x.VarChar == url); - - var sql = createSql(umbracoFileValue); - - var propertyDataDto = Database.Fetch(sql).FirstOrDefault(); - // If the stripped-down url returns null, we try again with the original url. // Previously, the function would fail on e.g. "my_x_image.jpg" - if (propertyDataDto == null) - { - sql = createSql(mediaPath); - propertyDataDto = Database.Fetch(sql).FirstOrDefault(); - } + var nodeId = GetMediaNodeIdByPath(umbracoFileValue); + if (nodeId < 0) nodeId = GetMediaNodeIdByPath(mediaPath); - return propertyDataDto == null ? null : Get(propertyDataDto.NodeId); + return nodeId < 0 ? null : Get(nodeId); } + + private int GetMediaNodeIdByPath(string url) + { + var sql = Sql().SelectAll() + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.VarChar == url); + + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto?.NodeId ?? -1; + } + protected override void PerformDeleteVersion(int id, Guid versionId) { Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index f36e69d747..497efdc5b2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -322,7 +322,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The query supplied will ONLY work with data specifically on the umbracoUser table because we are using NPoco paging (SQL paging) /// - public IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy) + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Expression> orderBy) { if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 3e4a67349b..c2f803e945 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -391,9 +391,9 @@ namespace Umbraco.Core.Services { uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var content = repository.GetAll(idsA); + var items = repository.GetAll(idsA); uow.Complete(); - return content; + return items; } } @@ -409,8 +409,7 @@ namespace Umbraco.Core.Services uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Key == key); - var contents = repository.GetByQuery(query); - var content = contents.SingleOrDefault(); + var content = repository.GetByQuery(query).SingleOrDefault(); uow.Complete(); return content; } @@ -428,9 +427,9 @@ namespace Umbraco.Core.Services uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ContentTypeId == id); - var content = repository.GetByQuery(query); + var items = repository.GetByQuery(query); uow.Complete(); - return content; + return items; } } @@ -460,9 +459,9 @@ namespace Umbraco.Core.Services uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Level == level && x.Trashed == false); - var content = repository.GetByQuery(query); + var items = repository.GetByQuery(query); uow.Complete(); - return content; + return items; } } @@ -494,9 +493,9 @@ namespace Umbraco.Core.Services { uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var content = repository.GetAllVersions(id); + var versions = repository.GetAllVersions(id); uow.Complete(); - return content; + return versions; } } @@ -590,12 +589,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); - } + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); } } @@ -624,11 +622,9 @@ namespace Umbraco.Core.Services var repository = uow.CreateRepository(); var query = repository.Query; - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - query.Where(x => x.ParentId == id); - } + //if the id is System Root, then just get all - NO! does not make sense! + //if (id != Constants.System.Root) + query.Where(x => x.ParentId == id); var children = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); uow.Complete(); return children; @@ -650,12 +646,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); - } + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); } } @@ -685,12 +680,10 @@ namespace Umbraco.Core.Services var query = repository.Query; //if the id is System Root, then just get all if (id != Constants.System.Root) - { query.Where(x => x.Path.SqlContains($",{id},", TextColumnType.NVarchar)); - } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + var descendants = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); uow.Complete(); - return contents; + return descendants; } } @@ -817,9 +810,9 @@ namespace Umbraco.Core.Services uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == Constants.System.Root); - var content = repository.GetByQuery(query); + var items = repository.GetByQuery(query); uow.Complete(); - return content; + return items; } } @@ -894,7 +887,8 @@ namespace Umbraco.Core.Services { uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); + var bin = $"{Constants.System.Root},{Constants.System.RecycleBinContent},"; + var query = repository.Query.Where(x => x.Path.StartsWith(bin)); var content = repository.GetByQuery(query); uow.Complete(); return content; @@ -1322,7 +1316,7 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content public void Delete(IContent content, int userId = 0) { - ((IContentServiceOperations)this).Delete(content, userId); + ((IContentServiceOperations) this).Delete(content, userId); } /// @@ -1387,7 +1381,7 @@ namespace Umbraco.Core.Services Deleted.RaiseEvent(args, this); IOHelper.DeleteFiles(args.MediaFilesToDelete, // remove flagged files - (file, e) => Logger.Error("An error occurred while deleting file attached to nodes: " + file, e)); + (file, e) => Logger.Error("An error occurred while deleting file attached to nodes: " + file, e)); } } @@ -1466,7 +1460,7 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content public void MoveToRecycleBin(IContent content, int userId = 0) { - ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); + ((IContentServiceOperations) this).MoveToRecycleBin(content, userId); } /// @@ -2583,6 +2577,8 @@ namespace Umbraco.Core.Services private IContentType GetContentType(string contentTypeAlias) { + Mandate.ParameterNotNullOrEmpty(contentTypeAlias, nameof(contentTypeAlias)); + using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.ContentTree); @@ -2601,7 +2597,7 @@ namespace Umbraco.Core.Services #endregion - #region Xml - Shoud Move! + #region Xml - Should Move! /// /// Returns the persisted content's XML structure @@ -2658,7 +2654,6 @@ namespace Umbraco.Core.Services } Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); - } #endregion diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 351035e285..b231119561 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -67,9 +67,9 @@ namespace Umbraco.Core.Services /// void RebuildXmlStructures(params int[] contentTypeIds); - int Count(string contentTypeAlias = null); - int CountChildren(int parentId, string contentTypeAlias = null); - int CountDescendants(int parentId, string contentTypeAlias = null); + int Count(string mediaTypeAlias = null); + int CountChildren(int parentId, string mediaTypeAlias = null); + int CountDescendants(int parentId, string mediaTypeAlias = null); IEnumerable GetByIds(IEnumerable ids); diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index a893d89feb..6591cc0b74 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -22,11 +22,6 @@ namespace Umbraco.Core.Services /// void RebuildXmlStructures(params int[] contentTypeIds); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); - /// /// Gets a list of paged objects /// @@ -183,10 +178,6 @@ namespace Umbraco.Core.Services /// Id of the MemberType void DeleteMembersOfType(int memberTypeId); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - /// /// Finds Members based on their display name /// diff --git a/src/Umbraco.Core/Services/IMemberTypeService.cs b/src/Umbraco.Core/Services/IMemberTypeService.cs index 6398ea62f9..3c0ed4d18f 100644 --- a/src/Umbraco.Core/Services/IMemberTypeService.cs +++ b/src/Umbraco.Core/Services/IMemberTypeService.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -8,63 +6,7 @@ namespace Umbraco.Core.Services /// Manages objects. /// public interface IMemberTypeService : IContentTypeServiceBase - { - /// - /// Gets a list of all available objects - /// - /// Optional list of ids - /// An Enumerable list of objects - IEnumerable GetAll(params int[] ids); - - /// - /// Gets an object by its Id - /// - /// Id of the to retrieve - /// - IMemberType Get(int id); - - /// - /// Gets an object by its Key - /// - /// Key of the to retrieve - /// - IMemberType Get(Guid key); - - /// - /// Gets an object by its Alias - /// - /// Alias of the to retrieve - /// - IMemberType Get(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); + { + string GetDefault(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 801f88d9ac..4698ffd966 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -131,7 +131,7 @@ namespace Umbraco.Core.Services /// Total number of records found (out) /// The type of match to make as . Default is /// - IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Finds a list of objects by a partial username @@ -143,7 +143,7 @@ namespace Umbraco.Core.Services /// Total number of records found (out) /// The type of match to make as . Default is /// - IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Gets a list of paged objects @@ -153,6 +153,6 @@ namespace Umbraco.Core.Services /// Size of the page /// Total number of records found (out) /// - IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords); + IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 2725d010ba..79f52d7b5c 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -2,12 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Threading; using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; @@ -21,218 +20,289 @@ namespace Umbraco.Core.Services /// public class MediaService : RepositoryService, IMediaService, IMediaServiceOperations { - - //Support recursive locks because some of the methods that require locking call other methods that require locking. - //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; private readonly IEnumerable _urlSegmentProviders; + private IMediaTypeService _mediaTypeService; - public MediaService(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService, IEnumerable urlSegmentProviders) + #region Constructors + + public MediaService( + IDatabaseUnitOfWorkProvider provider, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IDataTypeService dataTypeService, + IUserService userService, + IEnumerable urlSegmentProviders) : base(provider, logger, eventMessagesFactory) { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); + if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); + if (userService == null) throw new ArgumentNullException(nameof(userService)); + if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders)); _dataTypeService = dataTypeService; _userService = userService; _urlSegmentProviders = urlSegmentProviders; } + // don't change or remove this, will need it later + private IMediaTypeService MediaTypeService => _mediaTypeService; + //// handle circular dependencies + //internal IMediaTypeService MediaTypeService + //{ + // get + // { + // if (_mediaTypeService == null) + // throw new InvalidOperationException("MediaService.MediaTypeService has not been initialized."); + // return _mediaTypeService; + // } + // set { _mediaTypeService = value; } + //} + + #endregion + + #region Count + + public int Count(string mediaTypeAlias = null) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MediaTree); + var repo = uow.CreateRepository(); + var count = repo.Count(mediaTypeAlias); + uow.Complete(); + return count; + } + } + + public int CountChildren(int parentId, string mediaTypeAlias = null) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MediaTree); + var repo = uow.CreateRepository(); + var count = repo.CountChildren(parentId, mediaTypeAlias); + uow.Complete(); + return count; + } + } + + public int CountDescendants(int parentId, string mediaTypeAlias = null) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MediaTree); + var repo = uow.CreateRepository(); + var count = repo.CountDescendants(parentId, mediaTypeAlias); + uow.Complete(); + return count; + } + } + + #endregion + + #region Create + /// - /// Creates an object using the alias of the - /// that this Media should based on. + /// Creates an object of a specified media type. /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. + /// This method simply returns a new, non-persisted, IMedia without any identity. It + /// is intended as a shortcut to creating new media objects that does not invoke a save + /// operation against the database. /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// + /// The name of the media object. + /// The identifier of the parent, or -1. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. public IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0) { - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var mediaType = GetMediaType(mediaTypeAlias); + if (mediaType == null) + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); + var parent = parentId > 0 ? GetById(parentId) : null; + if (parentId > 0 && parent == null) + throw new ArgumentException("No media with that id.", nameof(parentId)); + var media = new Models.Media(name, parentId, mediaType); - var parent = GetById(media.ParentId); - media.Path = string.Concat(parent.IfNotNull(x => x.Path, media.ParentId.ToString()), ",", media.Id); - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parentId), this)) - { - media.WasCancelled = true; - return media; - } - - media.CreatorId = userId; - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); + CreateMedia(null, media, parent, userId, false); return media; } /// - /// Creates an object using the alias of the - /// that this Media should based on. + /// Creates an object of a specified media type, at root. /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. + /// This method simply returns a new, non-persisted, IMedia without any identity. It + /// is intended as a shortcut to creating new media objects that does not invoke a save + /// operation against the database. /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// + /// The name of the media object. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = 0) + { + // not locking since not saving anything + + var mediaType = GetMediaType(mediaTypeAlias); + if (mediaType == null) + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); + + var media = new Models.Media(name, -1, mediaType); + CreateMedia(null, media, null, userId, false); + + return media; + } + + /// + /// Creates an object of a specified media type, under a parent. + /// + /// This method simply returns a new, non-persisted, IMedia without any identity. It + /// is intended as a shortcut to creating new media objects that does not invoke a save + /// operation against the database. + /// + /// The name of the media object. + /// The parent media object. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) { - if (parent == null) throw new ArgumentNullException("parent"); + if (parent == null) throw new ArgumentNullException(nameof(parent)); - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parent, mediaType); - media.Path = string.Concat(parent.Path, ",", media.Id); + using (var uow = UowProvider.CreateUnitOfWork()) + { + // not locking since not saving anything - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) + var mediaType = GetMediaType(mediaTypeAlias); + if (mediaType == null) + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback + + var media = new Models.Media(name, parent, mediaType); + CreateMedia(uow, media, parent, userId, false); + + uow.Complete(); + return media; + } + } + + /// + /// Creates an object of a specified media type. + /// + /// This method returns a new, persisted, IMedia with an identity. + /// The name of the media object. + /// The identifier of the parent, or -1. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + // locking the media tree secures media types too + uow.WriteLock(Constants.Locks.MediaTree); + + var mediaType = GetMediaType(mediaTypeAlias); // + locks + if (mediaType == null) + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback + + var parent = parentId > 0 ? GetById(parentId) : null; // + locks + if (parentId > 0 && parent == null) + throw new ArgumentException("No media with that id.", nameof(parentId)); // causes rollback + + var media = parentId > 0 ? new Models.Media(name, parent, mediaType) : new Models.Media(name, parentId, mediaType); + CreateMedia(uow, media, parent, userId, true); + + uow.Complete(); + return media; + } + } + + /// + /// Creates an object of a specified media type, under a parent. + /// + /// This method returns a new, persisted, IMedia with an identity. + /// The name of the media object. + /// The parent media object. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException(nameof(parent)); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + // locking the media tree secures media types too + uow.WriteLock(Constants.Locks.MediaTree); + + var mediaType = GetMediaType(mediaTypeAlias); // + locks + if (mediaType == null) + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback + + var media = new Models.Media(name, parent, mediaType); + CreateMedia(uow, media, parent, userId, true); + + uow.Complete(); + return media; + } + } + + private void CreateMedia(IDatabaseUnitOfWork uow, Models.Media media, IMedia parent, int userId, bool withIdentity) + { + // NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + var newArgs = parent != null + ? new NewEventArgs(media, media.ContentType.Alias, parent) + : new NewEventArgs(media, media.ContentType.Alias, -1); + + if (Creating.IsRaisedEventCancelled(newArgs, this)) { media.WasCancelled = true; - return media; + return; } media.CreatorId = userId; - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); - - return media; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0) - { - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parentId, mediaType); - - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parentId), this)) + if (withIdentity) { - media.WasCancelled = true; - return media; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) - { - media.WasCancelled = true; - return media; - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - media.CreatorId = userId; - repository.AddOrUpdate(media); - - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); + media.WasCancelled = true; + return; } - uow.Complete(); + var repo = uow.CreateRepository(); + repo.AddOrUpdate(media); + // FIXME contentXml?! + repo.AddOrUpdatePreviewXml(media, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + + Saved.RaiseEvent(new SaveEventArgs(media, false), this); } - Saved.RaiseEvent(new SaveEventArgs(media, false), this); - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); - Audit(AuditType.New, $"Media '{name}' was created with Id {media.Id}", media.CreatorId, media.Id); + Created.RaiseEvent(new NewEventArgs(media, false, media.ContentType.Alias, parent), this); - return media; + var msg = withIdentity + ? "Media '{0}' was created with Id {1}" + : "Media '{0}' was created"; + Audit(AuditType.New, string.Format(msg, media.Name, media.Id), media.CreatorId, media.Id); } - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("parent"); + #endregion - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parent, mediaType); - - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) - { - media.WasCancelled = true; - return media; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) - { - media.WasCancelled = true; - return media; - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); - } - - uow.Complete(); - } - - Saved.RaiseEvent(new SaveEventArgs(media, false), this); - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); - Audit(AuditType.New, $"Media '{name}' was created with Id {media.Id}", media.CreatorId, media.Id); - return media; - } + #region Get, Has, Is /// /// Gets an object by Id /// - /// Id of the Content to retrieve + /// Id of the Media to retrieve /// public IMedia GetById(int id) { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); var media = repository.Get(id); uow.Complete(); @@ -240,39 +310,6 @@ namespace Umbraco.Core.Services } } - public int Count(string contentTypeAlias = null) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var count = repository.Count(contentTypeAlias); - uow.Complete(); - return count; - } - } - - public int CountChildren(int parentId, string contentTypeAlias = null) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var count = repository.CountChildren(parentId, contentTypeAlias); - uow.Complete(); - return count; - } - } - - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var count = repository.CountDescendants(parentId, contentTypeAlias); - uow.Complete(); - return count; - } - } - /// /// Gets an object by Id /// @@ -280,12 +317,14 @@ namespace Umbraco.Core.Services /// public IEnumerable GetByIds(IEnumerable ids) { - if (ids.Any() == false) return Enumerable.Empty(); + var idsA = ids.ToArray(); + if (idsA.Length == 0) return Enumerable.Empty(); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - var items = repository.GetAll(ids.ToArray()); + var items = repository.GetAll(idsA); uow.Complete(); return items; } @@ -300,11 +339,30 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Key == key); - var item = repository.GetByQuery(query).SingleOrDefault(); + var media = repository.GetByQuery(query).SingleOrDefault(); uow.Complete(); - return item; + return media; + } + } + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + public IEnumerable GetMediaOfMediaType(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + var query = repository.Query.Where(x => x.ContentTypeId == id); + var items = repository.GetByQuery(query); + uow.Complete(); + return items; } } @@ -313,15 +371,17 @@ namespace Umbraco.Core.Services /// /// The level to retrieve Media from /// An Enumerable list of objects + /// Contrary to most methods, this method filters out trashed media items. public IEnumerable GetByLevel(int level) { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Level == level && x.Path.StartsWith("-21") == false); - var contents = repository.GetByQuery(query); + var query = repository.Query.Where(x => x.Level == level && x.Trashed == false); + var items = repository.GetByQuery(query); uow.Complete(); - return contents; + return items; } } @@ -334,10 +394,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - var item = repository.GetByVersion(versionId); + var media = repository.GetByVersion(versionId); uow.Complete(); - return item; + return media; } } @@ -350,6 +411,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); var versions = repository.GetAllVersions(id); uow.Complete(); @@ -364,6 +426,7 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAncestors(int id) { + // intentionnaly not locking var media = GetById(id); return GetAncestors(media); } @@ -375,16 +438,22 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAncestors(IMedia media) { - var ids = media.Path.Split(',').Where(x => x != "-1" && x != media.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); + //null check otherwise we get exceptions + if (media.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); + + var rootId = Constants.System.Root.ToInvariantString(); + var ids = media.Path.Split(',') + .Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); if (ids.Any() == false) return new List(); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - var items = repository.GetAll(ids); + var ancestors = repository.GetAll(ids); uow.Complete(); - return items; + return ancestors; } } @@ -397,11 +466,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == id); - var medias = repository.GetByQuery(query); + var children = repository.GetByQuery(query).OrderBy(x => x.SortOrder); uow.Complete(); - return medias; + return children; } } @@ -415,18 +485,17 @@ namespace Umbraco.Core.Services /// Field to order by /// Direction to order by /// Search text filter - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, string filter = "") { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); - } + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); } } @@ -442,7 +511,7 @@ namespace Umbraco.Core.Services /// Direction to order by /// Flag to indicate when ordering by system field /// - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { @@ -450,18 +519,21 @@ namespace Umbraco.Core.Services Mandate.ParameterCondition(pageSize > 0, "pageSize"); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); var query = repository.Query; + //if the id is System Root, then just get all - NO! does not make sense! + //if (id != Constants.System.Root) query.Where(x => x.ParentId == id); - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + var children = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); uow.Complete(); - return medias; + return children; } - } + } /// - /// Gets a collection of objects by Parent Id + /// Gets a collection of objects by Parent Id /// /// Id of the Parent to retrieve Descendants from /// Page number @@ -470,23 +542,22 @@ namespace Umbraco.Core.Services /// Field to order by /// Direction to order by /// Search text filter - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); - } + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : repository.QueryFactory.Create().Where(x => x.Name.Contains(filter)); return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); } } /// - /// Gets a collection of objects by Parent Id + /// Gets a collection of objects by Parent Id /// /// Id of the Parent to retrieve Descendants from /// Page number @@ -496,24 +567,24 @@ namespace Umbraco.Core.Services /// Direction to order by /// Flag to indicate when ordering by system field /// - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); + Mandate.ParameterCondition(pageIndex >= 0, nameof(pageIndex)); + Mandate.ParameterCondition(pageSize > 0, nameof(pageSize)); + using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); var query = repository.Query; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + //if the id is System Root, then just get all + if (id != Constants.System.Root) + query.Where(x => x.Path.SqlContains($",{id},", TextColumnType.NVarchar)); + var descendants = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); uow.Complete(); - return contents; + return descendants; } } @@ -524,12 +595,22 @@ namespace Umbraco.Core.Services /// An Enumerable flat list of objects public IEnumerable GetDescendants(int id) { - var media = GetById(id); - if (media == null) + using (var uow = UowProvider.CreateUnitOfWork()) { - return Enumerable.Empty(); + uow.ReadLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + var media = GetById(id); + if (media == null) + { + uow.Complete(); // else causes rollback + return Enumerable.Empty(); + } + var pathMatch = media.Path + ","; + var query = repository.Query.Where(x => x.Id != media.Id && x.Path.StartsWith(pathMatch)); + var descendants = repository.GetByQuery(query); + uow.Complete(); + return descendants; } - return GetDescendants(media); } /// @@ -541,12 +622,13 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); var pathMatch = media.Path + ","; - var query = repository.Query.Where(x => x.Path.StartsWith(pathMatch) && x.Id != media.Id); - var medias = repository.GetByQuery(query); + var query = repository.Query.Where(x => x.Id != media.Id && x.Path.StartsWith(pathMatch)); + var descendants = repository.GetByQuery(query); uow.Complete(); - return medias; + return descendants; } } @@ -557,6 +639,7 @@ namespace Umbraco.Core.Services /// Parent object public IMedia GetParent(int id) { + // intentionnaly not locking var media = GetById(id); return GetParent(media); } @@ -568,29 +651,12 @@ namespace Umbraco.Core.Services /// Parent object public IMedia GetParent(IMedia media) { - if (media.ParentId == -1 || media.ParentId == -21) + if (media.ParentId == Constants.System.Root || media.ParentId == Constants.System.RecycleBinMedia) return null; return GetById(media.ParentId); } - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetMediaOfMediaType(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ContentTypeId == id); - var medias = repository.GetByQuery(query); - uow.Complete(); - return medias; - } - } - /// /// Gets a collection of objects, which reside at the first level / root /// @@ -599,11 +665,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ParentId == -1); - var medias = repository.GetByQuery(query); + var query = repository.Query.Where(x => x.ParentId == Constants.System.Root); + var items = repository.GetByQuery(query); uow.Complete(); - return medias; + return items; } } @@ -615,30 +682,16 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Path.Contains("-21")); + var bin = $"{Constants.System.Root},{Constants.System.RecycleBinMedia},"; + var query = repository.Query.Where(x => x.Path.StartsWith(bin)); var medias = repository.GetByQuery(query); uow.Complete(); return medias; } } - /// - /// Gets an object from the path stored in the 'umbracoFile' property. - /// - /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) - /// - public IMedia GetMediaByPath(string mediaPath) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - var item = repo.GetMediaByPath(mediaPath); - uow.Complete(); - return item; - } - } - /// /// Checks whether an item has any children /// @@ -657,122 +710,34 @@ namespace Umbraco.Core.Services } /// - /// Moves an object to a new location + /// Gets an object from the path stored in the 'umbracoFile' property. /// - /// The to move - /// Id of the Media's new Parent - /// Id of the User moving the Media - public void Move(IMedia media, int parentId, int userId = 0) + /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) + /// + public IMedia GetMediaByPath(string mediaPath) { - //TODO: This all needs to be on the repo layer in one transaction! - - if (media == null) throw new ArgumentNullException("media"); - - using (new WriteLock(Locker)) - { - //This ensures that the correct method is called if this method is used to Move to recycle bin. - if (parentId == -21) - { - MoveToRecycleBin(media, userId); - return; - } - - var originalPath = media.Path; - - if (Moving.IsRaisedEventCancelled( - new MoveEventArgs( - new MoveEventInfo(media, originalPath, parentId)), this)) - { - return; - } - - media.ParentId = parentId; - if (media.Trashed) - { - ((Models.Media)media).ChangeTrashedState(false, parentId); - } - Save(media, userId, - //no events! - false); - - //used to track all the moved entities to be given to the event - var moveInfo = new List> - { - new MoveEventInfo(media, originalPath, parentId) - }; - - //Ensure that relevant properties are updated on children - var children = GetChildren(media.Id).ToArray(); - if (children.Any()) - { - var parentPath = media.Path; - var parentLevel = media.Level; - var parentTrashed = media.Trashed; - var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed, moveInfo); - Save(updatedDescendants, userId, - //no events! - false); - } - - Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); - - Audit(AuditType.Move, "Move Media performed by user", userId, media.Id); - } - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - public void MoveToRecycleBin(IMedia media, int userId = 0) - { - ((IMediaServiceOperations)this).MoveToRecycleBin(media, userId); - } - - /// - /// Permanently deletes an object - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// but current not from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - Attempt IMediaServiceOperations.Delete(IMedia media, int userId) - { - //TODO: IT would be much nicer to mass delete all in one trans in the repo level! - var evtMsgs = EventMessagesFactory.Get(); - - if (Deleting.IsRaisedEventCancelled( - new DeleteEventArgs(media, evtMsgs), this)) - { - return OperationStatus.Attempt.Cancel(evtMsgs); - } - - //Delete children before deleting the 'possible parent' - var children = GetChildren(media.Id); - foreach (var child in children) - { - Delete(child, userId); - } - using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - repository.Delete(media); + var repo = uow.CreateRepository(); + var item = repo.GetMediaByPath(mediaPath); uow.Complete(); - - var args = new DeleteEventArgs(media, false, evtMsgs); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); + return item; } + } - Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); + #endregion - return OperationStatus.Attempt.Succeed(evtMsgs); + #region Save + + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + public void Save(IMedia media, int userId = 0, bool raiseEvents = true) + { + ((IMediaServiceOperations)this).Save(media, userId, raiseEvents); } /// @@ -785,28 +750,22 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(media, evtMsgs), - this)) - { - return OperationStatus.Attempt.Cancel(evtMsgs); - } - - } + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(media, evtMsgs), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); - media.CreatorId = userId; + if (media.HasIdentity == false) + media.CreatorId = userId; repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); + // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); - } uow.Complete(); } @@ -818,6 +777,17 @@ namespace Umbraco.Core.Services return OperationStatus.Attempt.Succeed(evtMsgs); } + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) + { + ((IMediaServiceOperations) this).Save(medias, userId, raiseEvents); + } + /// /// Saves a collection of objects /// @@ -826,191 +796,41 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise events. Attempt IMediaServiceOperations.Save(IEnumerable medias, int userId, bool raiseEvents) { - var asArray = medias.ToArray(); var evtMsgs = EventMessagesFactory.Get(); + var mediasA = medias.ToArray(); - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(asArray, evtMsgs), - this)) - { - return OperationStatus.Attempt.Cancel(evtMsgs); - } - } + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(mediasA, evtMsgs), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - foreach (var media in asArray) + foreach (var media in mediasA) { - media.CreatorId = userId; + if (media.HasIdentity == false) + media.CreatorId = userId; repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); + // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); - } } - //commit the whole lot in one go uow.Complete(); } if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); - Audit(AuditType.Save, "Save Media items performed by user", userId, -1); + Saved.RaiseEvent(new SaveEventArgs(mediasA, false, evtMsgs), this); + Audit(AuditType.Save, "Bulk Save media performed by user", userId == -1 ? 0 : userId, Constants.System.Root); return OperationStatus.Attempt.Succeed(evtMsgs); } - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - public void EmptyRecycleBin() - { - using (new WriteLock(Locker)) - { - Dictionary> entities; - List files; - bool success; - var nodeObjectType = new Guid(Constants.ObjectTypes.Media); + #endregion - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - //Create a dictionary of ids -> dictionary of property aliases + values - entities = repository.GetEntitiesInRecycleBin() - .ToDictionary( - key => key.Id, - val => (IEnumerable)val.Properties); - - files = ((MediaRepository)repository).GetFilesInRecycleBinForUploadField(); - - if (EmptyingRecycleBin.IsRaisedEventCancelled(new RecycleBinEventArgs(nodeObjectType, entities, files), this)) - return; // causes rollback - - success = repository.EmptyRecycleBin(); - - EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, entities, files, success), this); - - if (success) - repository.DeleteMediaFiles(files); - - uow.Complete(); - } - } - Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21); - } - - /// - /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin. - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Id of the - /// Optional id of the user deleting the media - public void DeleteMediaOfType(int mediaTypeId, int userId = 0) - { - //TODO: This all needs to be done on the repo level in one trans - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - //NOTE What about media that has the contenttype as part of its composition? - //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 = repository.Query.Where(x => x.ContentTypeId == mediaTypeId); - var contents = repository.GetByQuery(query).ToArray(); - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(contents), this)) - return; // causes rollback - - foreach (var content in contents.OrderByDescending(x => x.ParentId)) - { - //Look for children of current content and move that to trash before the current content is deleted - var c = content; - var childQuery = repository.Query.Where(x => x.Path.StartsWith(c.Path)); - var children = repository.GetByQuery(childQuery); - - foreach (var child in children) - { - if (child.ContentType.Id != mediaTypeId) - MoveToRecycleBin(child, userId); - } - - //Permanently delete the content - Delete(content, userId); - } - - uow.Complete(); - } - - Audit(AuditType.Delete, "Delete Media items by Type performed by user", userId, -1); - } - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, int userId) - { - if (media == null) throw new ArgumentNullException("media"); - - var originalPath = media.Path; - - var evtMsgs = EventMessagesFactory.Get(); - - if (Trashing.IsRaisedEventCancelled( - new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) - { - return OperationStatus.Attempt.Cancel(evtMsgs); - } - - var moveInfo = new List> - { - new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia) - }; - - //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. - var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList(); - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - //TODO: This should be part of the repo! - - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id }); - - ((Models.Media)media).ChangeTrashedState(true, Constants.System.RecycleBinMedia); - repository.AddOrUpdate(media); - - //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId - foreach (var descendant in descendants) - { - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = descendant.Id }); - - ((Models.Media)descendant).ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); - - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - } - - uow.Complete(); - } - - Trashed.RaiseEvent( - new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); - - return OperationStatus.Attempt.Succeed(evtMsgs); - } + #region Delete /// /// Permanently deletes an object as well as all of its Children. @@ -1023,18 +843,83 @@ namespace Umbraco.Core.Services /// Id of the User deleting the Media public void Delete(IMedia media, int userId = 0) { - ((IMediaServiceOperations)this).Delete(media, userId); + ((IMediaServiceOperations) this).Delete(media, userId); } + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// FIXME uh? + /// + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.Delete(IMedia media, int userId) + { + var evtMsgs = EventMessagesFactory.Get(); + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(media, evtMsgs), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + + DeleteLocked(repository, media); + uow.Complete(); + } + + Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); + + return OperationStatus.Attempt.Succeed(evtMsgs); + } + + private void DeleteLocked(IMediaRepository repository, IMedia media) + { + // then recursively delete descendants, bottom-up + // just repository.Delete + an event + var stack = new Stack(); + stack.Push(media); + var level = 1; + while (stack.Count > 0) + { + var c = stack.Peek(); + IMedia[] cc; + if (c.Level == level) + while ((cc = c.Children().ToArray()).Length > 0) + { + foreach (var ci in cc) + stack.Push(ci); + c = cc[cc.Length - 1]; + } + c = stack.Pop(); + level = c.Level; + + repository.Delete(c); + var args = new DeleteEventArgs(c, false); // raise event & get flagged files + Deleted.RaiseEvent(args, this); + + IOHelper.DeleteFiles(args.MediaFilesToDelete, // remove flagged files + (file, e) => Logger.Error("An error occurred while deleting file attached to nodes: " + file, e)); + } + } + + //TODO: + // both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way + // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT, + // if that's not the case, then the file will never be deleted, because when we delete the media, + // the version referencing the file will not be there anymore. SO, we can leak files. /// /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a content item. + /// This method will never delete the latest version of a media item. /// /// Id of the object to delete versions from /// Latest version date - /// Optional Id of the User deleting versions of a Content object + /// Optional Id of the User deleting versions of a Media object public void DeleteVersions(int id, DateTime versionDate, int userId = 0) { if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) @@ -1042,67 +927,233 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); repository.DeleteVersions(id, versionDate); uow.Complete(); } DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); - Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, -1); + + Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, Constants.System.Root); } /// /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a content item. + /// This method will never delete the latest version of a media item. /// /// Id of the object to delete a version from /// Id of the version to delete /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object + /// Optional Id of the User deleting versions of a Media object public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId), this)) return; if (deletePriorVersions) { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); + var media = GetByVersion(versionId); + DeleteVersions(id, media.UpdateDate, userId); } using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); repository.DeleteVersion(versionId); uow.Complete(); } - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, /*specificVersion:*/ versionId), this); + Audit(AuditType.Delete, "Delete Media by version performed by user", userId, Constants.System.Root); + } + + #endregion + + #region Move, RecycleBin + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + public void MoveToRecycleBin(IMedia media, int userId = 0) + { + ((IMediaServiceOperations) this).MoveToRecycleBin(media, userId); } /// - /// Saves a single object + /// Deletes an object by moving it to the Recycle Bin /// - /// The to save - /// Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IMedia media, int userId = 0, bool raiseEvents = true) + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, int userId) { - ((IMediaServiceOperations)this).Save(media, userId, raiseEvents); + var evtMsgs = EventMessagesFactory.Get(); + var moves = new List>(); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + + var originalPath = media.Path; + if (Trashing.IsRaisedEventCancelled(new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); // causes rollback + + PerformMoveLocked(repository, media, Constants.System.RecycleBinMedia, null, userId, moves, true); + uow.Complete(); + } + + var moveInfo = moves + .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + + Trashed.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo), this); + Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); + + return OperationStatus.Attempt.Succeed(evtMsgs); } /// - /// Saves a collection of objects + /// Moves an object to a new location /// - /// Collection of to save - /// Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) + /// The to move + /// Id of the Media's new Parent + /// Id of the User moving the Media + public void Move(IMedia media, int parentId, int userId = 0) { - ((IMediaServiceOperations)this).Save(medias, userId, raiseEvents); + // if moving to the recycle bin then use the proper method + if (parentId == Constants.System.RecycleBinMedia) + { + MoveToRecycleBin(media, userId); + return; + } + + var moves = new List>(); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + + var parent = parentId == Constants.System.Root ? null : GetById(parentId); + if (parentId != Constants.System.Root && (parent == null || parent.Trashed)) + throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback + + if (Moving.IsRaisedEventCancelled(new MoveEventArgs(new MoveEventInfo(media, media.Path, parentId)), this)) + return; // causes rollback + + // if media was trashed, and since we're not moving to the recycle bin, + // indicate that the trashed status should be changed to false, else just + // leave it unchanged + var trashed = media.Trashed ? false : (bool?)null; + + PerformMoveLocked(repository, media, parentId, parent, userId, moves, trashed); + + uow.Complete(); + } + + var moveInfo = moves //changes + .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + + Moved.RaiseEvent(new MoveEventArgs(false, moveInfo), this); + + Audit(AuditType.Move, "Move Media performed by user", userId, media.Id); } + // MUST be called from within WriteLock + // trash indicates whether we are trashing, un-trashing, or not changing anything + private void PerformMoveLocked(IMediaRepository repository, + IMedia media, int parentId, IMedia parent, int userId, + ICollection> moves, + bool? trash) + { + media.ParentId = parentId; + + // get the level delta (old pos to new pos) + var levelDelta = parent == null + ? 1 - media.Level + (parentId == Constants.System.RecycleBinMedia ? 1 : 0) + : parent.Level + 1 - media.Level; + + var paths = new Dictionary(); + + moves.Add(Tuple.Create(media, media.Path)); // capture original path + + // these will be updated by the repo because we changed parentId + //media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id; + //media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId); + //media.Level += levelDelta; + PerformMoveMediaLocked(repository, media, userId, trash); + + // BUT media.Path will be updated only when the UOW commits, and + // because we want it now, we have to calculate it by ourselves + //paths[media.Id] = media.Path; + paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : "-1") : parent.Path) + "," + media.Id; + + var descendants = GetDescendants(media); + foreach (var descendant in descendants) + { + moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path + + // update path and level since we do not update parentId + descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; + descendant.Level += levelDelta; + PerformMoveMediaLocked(repository, descendant, userId, trash); + } + } + + private static void PerformMoveMediaLocked(IMediaRepository repository, IMedia media, int userId, + bool? trash) + { + if (trash.HasValue) ((ContentBase) media).Trashed = trash.Value; + repository.AddOrUpdate(media); + } + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + public void EmptyRecycleBin() + { + var nodeObjectType = new Guid(Constants.ObjectTypes.Media); + var deleted = new List(); + var evtMsgs = EventMessagesFactory.Get(); // todo - and then? + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + + // v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since + // each deleted items will have its own deleting/deleted events. so, files and such + // are managed by Delete, and not here. + + // no idea what those events are for, keep a simplified version + if (EmptyingRecycleBin.IsRaisedEventCancelled(new RecycleBinEventArgs(nodeObjectType), this)) + return; // causes rollback + + // emptying the recycle bin means deleting whetever is in there - do it properly! + var query = repository.Query.Where(x => x.ParentId == Constants.System.RecycleBinMedia); + var medias = repository.GetByQuery(query).ToArray(); + foreach (var media in medias) + { + DeleteLocked(repository, media); + deleted.Add(media); + } + + EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, true), this); + uow.Complete(); + } + + Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, Constants.System.RecycleBinMedia); + } + + #endregion + + #region Others + /// /// Sorts a collection of objects by updating the SortOrder according /// to the ordering of items in the passed in . @@ -1113,138 +1164,55 @@ namespace Umbraco.Core.Services /// True if sorting succeeded, otherwise False public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) { - var asArray = items.ToArray(); + var itemsA = items.ToArray(); + if (itemsA.Length == 0) return true; - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(itemsA), this)) return false; - } + + var saved = new List(); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MediaTree); var repository = uow.CreateRepository(); - int i = 0; - foreach (var media in asArray) + var sortOrder = 0; + + foreach (var media in itemsA) { - //If the current sort order equals that of the media - //we don't need to update it, so just increment the sort order - //and continue. - if (media.SortOrder == i) + // if the current sort order equals that of the media we don't + // need to update it, so just increment the sort order and continue. + if (media.SortOrder == sortOrder) { - i++; + sortOrder++; continue; } - media.SortOrder = i; - i++; - + // else update + media.SortOrder = sortOrder++; + // save + saved.Add(media); repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); + // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m)); - } } uow.Complete(); } if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); + Saved.RaiseEvent(new SaveEventArgs(saved, false), this); Audit(AuditType.Sort, "Sorting Media performed by user", userId, 0); return true; } - /// - /// 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 media - /// - public void RebuildXmlStructures(params int[] contentTypeIds) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.RebuildXmlStructures( - media => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, media), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - uow.Complete(); - } + #endregion - Audit(AuditType.Publish, "MediaService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); - } - - /// - /// Updates the Path and Level on a collection of objects - /// based on the Parent's Path and Level. Also change the trashed state if relevant. - /// - /// Collection of objects to update - /// Path of the Parent media - /// Level of the Parent media - /// Indicates whether the Parent is trashed or not - /// Used to track the objects to be used in the move event - /// Collection of updated objects - private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed, ICollection> eventInfo) - { - var list = new List(); - foreach (var child in children) - { - var originalPath = child.Path; - child.Path = string.Concat(parentPath, ",", child.Id); - child.Level = parentLevel + 1; - if (parentTrashed != child.Trashed) - { - ((Models.Media)child).ChangeTrashedState(parentTrashed, child.ParentId); - } - - eventInfo.Add(new MoveEventInfo(child, originalPath, child.ParentId)); - list.Add(child); - - var grandkids = GetChildren(child.Id).ToArray(); - if (grandkids.Any()) - { - list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed, eventInfo)); - } - } - return list; - } - - //private void CreateAndSaveMediaXml(XElement xml, int id, UmbracoDatabase db) - //{ - // var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToDataString() }; - // var exists = db.FirstOrDefault("WHERE nodeId = @Id", new { Id = id }) != null; - // int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco)); - //} - - private IMediaType FindMediaTypeByAlias(string mediaTypeAlias) - { - Mandate.ParameterNotNullOrEmpty(mediaTypeAlias, "mediaTypeAlias"); - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.QueryFactory.Create().Where(x => x.Alias == mediaTypeAlias); - var mediaTypes = repository.GetByQuery(query); - - if (mediaTypes.Any() == false) - throw new Exception(string.Format("No MediaType matching the passed in Alias: '{0}' was found", - mediaTypeAlias)); // causes rollback - - var mediaType = mediaTypes.First(); - - if (mediaType == null) - throw new Exception(string.Format("MediaType matching the passed in Alias: '{0}' was null", - mediaTypeAlias)); // causes rollback - - uow.Complete(); - return mediaType; - } - } + #region Private Methods private void Audit(AuditType type, string message, int userId, int objectId) { @@ -1256,18 +1224,10 @@ namespace Umbraco.Core.Services } } + #endregion + #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler DeletedVersions; - /// /// Occurs before Delete /// @@ -1278,6 +1238,16 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> Deleted; + /// + /// Occurs before Delete Versions + /// + public static event TypedEventHandler DeletingVersions; + + /// + /// Occurs after Delete Versions + /// + public static event TypedEventHandler DeletedVersions; + /// /// Occurs before Save /// @@ -1298,18 +1268,18 @@ namespace Umbraco.Core.Services /// Occurs after Create /// /// - /// Please note that the Media object has been created, but not saved + /// Please note that the Media object has been created, but might not have been saved /// so it does not have an identity yet (meaning no Id has been set). /// public static event TypedEventHandler> Created; /// - /// Occurs before Content is moved to Recycle Bin + /// Occurs before Media is moved to Recycle Bin /// public static event TypedEventHandler> Trashing; /// - /// Occurs after Content is moved to Recycle Bin + /// Occurs after Media is moved to Recycle Bin /// public static event TypedEventHandler> Trashed; @@ -1332,6 +1302,117 @@ namespace Umbraco.Core.Services /// Occurs after the Recycle Bin has been Emptied /// public static event TypedEventHandler EmptiedRecycleBin; + + #endregion + + #region Content Types + + /// + /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Id of the + /// Optional id of the user deleting the media + public void DeleteMediaOfType(int mediaTypeId, int userId = 0) + { + //TODO: This currently this is called from the ContentTypeService but that needs to change, + // if we are deleting a content type, we should just delete the data and do this operation slightly differently. + // This method will recursively go lookup every content item, check if any of it's descendants are + // of a different type, move them to the recycle bin, then permanently delete the content items. + // The main problem with this is that for every content item being deleted, events are raised... + // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. + + var moves = new List>(); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + + // fixme what about media that has the contenttype as part of its composition? + var query = repository.Query.Where(x => x.ContentTypeId == mediaTypeId); + var medias = repository.GetByQuery(query).ToArray(); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(medias), this)) + return; // causes rollback + + // order by level, descending, so deepest first - that way, we cannot move + // a media of the deleted type, to the recycle bin (and then delete it...) + foreach (var media in medias.OrderByDescending(x => x.ParentId)) + { + // if current media has children, move them to trash + var m = media; + var childQuery = repository.Query.Where(x => x.Path.StartsWith(m.Path)); + var children = repository.GetByQuery(childQuery); + foreach (var child in children.Where(x => x.ContentTypeId != mediaTypeId)) + { + // see MoveToRecycleBin + PerformMoveLocked(repository, child, Constants.System.RecycleBinMedia, null, userId, moves, true); + } + + // delete media + // triggers the deleted event (and handles the files) + DeleteLocked(repository, media); + } + + uow.Complete(); + } + + var moveInfos = moves + .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + if (moveInfos.Length > 0) + Trashed.RaiseEvent(new MoveEventArgs(false, moveInfos), this); + + Audit(AuditType.Delete, $"Delete Media of Type {mediaTypeId} performed by user", userId, Constants.System.Root); + } + + private IMediaType GetMediaType(string mediaTypeAlias) + { + Mandate.ParameterNotNullOrEmpty(mediaTypeAlias, nameof(mediaTypeAlias)); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MediaTree); + + var repository = uow.CreateRepository(); + var query = repository.Query.Where(x => x.Alias == mediaTypeAlias); + var mediaType = repository.GetByQuery(query).FirstOrDefault(); + + if (mediaType == null) + throw new Exception($"No MediaType matching the passed in Alias: '{mediaTypeAlias}' was found"); // causes rollback + + uow.Complete(); + return mediaType; + } + } + + #endregion + + #region Xml - Should Move! + + /// + /// 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 media + /// + public void RebuildXmlStructures(params int[] contentTypeIds) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + var repository = uow.CreateRepository(); + repository.RebuildXmlStructures( + media => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, media), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + uow.Complete(); + } + + Audit(AuditType.Publish, "MediaService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); + } + #endregion } } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index b89e7bffef..46aea609da 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -1,21 +1,16 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Threading; using System.Web.Security; -using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using System.Linq; +using Umbraco.Core.IO; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Security; @@ -26,634 +21,44 @@ namespace Umbraco.Core.Services /// public class MemberService : RepositoryService, IMemberService { - private readonly IMemberGroupService _memberGroupService; private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + private readonly IMemberGroupService _memberGroupService; + private IMemberTypeService _memberTypeService; - public MemberService(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) + #region Constructor + + public MemberService( + IDatabaseUnitOfWorkProvider provider, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IMemberGroupService memberGroupService, + IDataTypeService dataTypeService) : base(provider, logger, eventMessagesFactory) { - if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (memberGroupService == null) throw new ArgumentNullException(nameof(memberGroupService)); + if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); _memberGroupService = memberGroupService; _dataTypeService = dataTypeService; } - #region IMemberService Implementation - - /// - /// Gets the default MemberType alias - /// - /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll - /// return the first type that is not an admin, otherwise if there's only one we will return that one. - /// Alias of the default MemberType - public string GetDefaultMemberType() - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var types = repository.GetAll(new int[] { }).Select(x => x.Alias).ToArray(); - - if (types.Any() == false) - { - throw new InvalidOperationException("No member types could be resolved"); // causes rollback - } - uow.Complete(); - if (types.InvariantContains("Member")) - { - return types.First(x => x.InvariantEquals("Member")); - } - - return types.First(); - } - } - - /// - /// Checks if a Member with the username exists - /// - /// Username to check - /// True if the Member exists otherwise False - public bool Exists(string username) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var exists = repository.Exists(username); - uow.Complete(); - return exists; - } - } - - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// This method exists so that Umbraco developers can use one entry point to create/update - /// Members if they choose to. - /// The Member to save the password for - /// The password to encrypt and save - public void SavePassword(IMember member, string password) - { - if (member == null) throw new ArgumentNullException("member"); - - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider()) - { - provider.ChangePassword(member.Username, "", password); - } - else - { - throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); - } - - //go re-fetch the member and update the properties that may have changed - var result = GetByUsername(member.Username); - - //should never be null but it could have been deleted by another thread. - if (result == null) - return; - - member.RawPasswordValue = result.RawPasswordValue; - member.LastPasswordChangeDate = result.LastPasswordChangeDate; - member.UpdateDate = result.UpdateDate; - } - - /// - /// Checks if a Member with the id exists - /// - /// Id of the Member - /// True if the Member exists otherwise False - public bool Exists(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var exists = repository.Exists(id); - uow.Complete(); - return exists; - } - } - - /// - /// Gets a Member by its integer id - /// - /// Id - /// - public IMember GetById(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var member = repository.Get(id); - uow.Complete(); - return member; - } - } - - /// - /// Gets a Member by the unique key - /// - /// The guid key corresponds to the unique id in the database - /// and the user id in the membership provider. - /// Id - /// - public IMember GetByKey(Guid id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Key == id); - var member = repository.GetByQuery(query).FirstOrDefault(); - uow.Complete(); - return member; - } - } - - /// - /// Gets all Members for the specified MemberType alias - /// - /// Alias of the MemberType - /// - public IEnumerable GetMembersByMemberType(string memberTypeAlias) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ContentTypeAlias == memberTypeAlias); - var members = repository.GetByQuery(query); - uow.Complete(); - return members; - } - } - - /// - /// Gets all Members for the MemberType id - /// - /// Id of the MemberType - /// - public IEnumerable GetMembersByMemberType(int memberTypeId) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.Get(memberTypeId); - var query = repository.Query.Where(x => x.ContentTypeId == memberTypeId); - var members = repository.GetByQuery(query); - uow.Complete(); - return members; - } - } - - /// - /// Gets all Members within the specified MemberGroup name - /// - /// Name of the MemberGroup - /// - public IEnumerable GetMembersByGroup(string memberGroupName) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var members = repository.GetByMemberGroup(memberGroupName); - uow.Complete(); - return members; - } - } - - /// - /// Gets all Members with the ids specified - /// - /// If no Ids are specified all Members will be retrieved - /// Optional list of Member Ids - /// - public IEnumerable GetAllMembers(params int[] ids) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var members = repository.GetAll(ids); - uow.Complete(); - return members; - } - } - - /// - /// Delete Members of the specified MemberType id - /// - /// Id of the MemberType - public void DeleteMembersOfType(int memberTypeId) - { - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - //TODO: What about content that has the contenttype as part of its composition? - var query = repository.Query.Where(x => x.ContentTypeId == memberTypeId); - var members = repository.GetByQuery(query).ToArray(); - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) - return; // causes rollback - - foreach (var member in members) - { - //Permantly delete the member - Delete(member); - } - - uow.Complete(); - } - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindMembersByDisplayName(displayNameToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds Members based on their display name - /// - /// Display name to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query; - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Name.Equals(displayNameToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Name.Contains(displayNameToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Name.StartsWith(displayNameToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Name.EndsWith(displayNameToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); // causes rollback - } - - var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, true); - uow.Complete(); - return members; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindByEmail(emailStringToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds a list of objects by a partial email string - /// - /// Partial email string to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query; - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Email.Equals(emailStringToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Email.Contains(emailStringToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Email.StartsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Email.EndsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, true); - uow.Complete(); - return members; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindByUsername(login, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds a list of objects by a partial username - /// - /// Partial username to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query; - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Username.Equals(login)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Username.Contains(login)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Username.StartsWith(login)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Username.EndsWith(login)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); - uow.Complete(); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - IQuery query; - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.Contains: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.StartsWith: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.EndsWith: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); // causes rollback - } - - var members = repository.GetByQuery(query); - uow.Complete(); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - IQuery query; - - switch (matchType) - { - case ValuePropertyMatchType.Exact: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue == value); - break; - case ValuePropertyMatchType.GreaterThan: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue > value); - break; - case ValuePropertyMatchType.LessThan: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue < value); - break; - case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue >= value); - break; - case ValuePropertyMatchType.LessThanOrEqualTo: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue <= value); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); // causes rollback - } - - var members = repository.GetByQuery(query); - uow.Complete(); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).BoolPropertyValue == value); - - var members = repository.GetByQuery(query); - uow.Complete(); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - IQuery query; - - switch (matchType) - { - case ValuePropertyMatchType.Exact: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue == value); - break; - case ValuePropertyMatchType.GreaterThan: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue > value); - break; - case ValuePropertyMatchType.LessThan: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue < value); - break; - case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue >= value); - break; - case ValuePropertyMatchType.LessThanOrEqualTo: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue <= value); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); // causes rollback - } - - //TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! - var members = repository.GetByQuery(query); - uow.Complete(); - return members; - } - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all members - /// - /// - /// 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 - public void RebuildXmlStructures(params int[] memberTypeIds) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.RebuildXmlStructures( - member => _entitySerializer.Serialize(_dataTypeService, member), - contentTypeIds: memberTypeIds.Length == 0 ? null : memberTypeIds); - uow.Complete(); - } - - Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); - } + // don't change or remove this, will need it later + private IMemberTypeService MemberTypeService => _memberTypeService; + //// handle circular dependencies + //internal IMemberTypeService MemberTypeService + //{ + // get + // { + // if (_memberTypeService == null) + // throw new InvalidOperationException("MemberService.MemberTypeService has not been initialized."); + // return _memberTypeService; + // } + // set { _memberTypeService = value; } + //} #endregion - #region IMembershipMemberService Implementation + #region Count /// /// Gets the total number of Members based on the count type @@ -669,6 +74,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); IQuery query; @@ -679,101 +85,28 @@ namespace Umbraco.Core.Services break; case MemberCountType.Online: var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && - ((Member)x).DateTimePropertyValue > fromDate); + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && + ((Member)x).DateTimePropertyValue > fromDate); break; case MemberCountType.LockedOut: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && - ((Member)x).BoolPropertyValue); + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && + ((Member)x).BoolPropertyValue); break; case MemberCountType.Approved: - query = - repository.Query.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && - ((Member)x).BoolPropertyValue); + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && + ((Member)x).BoolPropertyValue); break; default: - throw new ArgumentOutOfRangeException("countType"); // causes rollback; + throw new ArgumentOutOfRangeException(nameof(countType)); // causes rollback; } var count = repository.GetCountByQuery(query); uow.Complete(); return count; } - - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords) - { - long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Gets a list of paged objects - /// - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var members = repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); - uow.Complete(); - return members; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") - { - long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, memberTypeAlias, filter); - totalRecords = Convert.ToInt32(total); - return result; - } - - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") - { - return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); - } - - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - IEnumerable members; - if (memberTypeAlias == null) - { - members = repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); - } - else - { - var query = repository.Query.Where(x => x.ContentTypeAlias == memberTypeAlias); - members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); - } - uow.Complete(); - return members; - } } /// @@ -786,6 +119,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var count = repository.Count(memberTypeAlias); uow.Complete(); @@ -793,6 +127,10 @@ namespace Umbraco.Core.Services } } + #endregion + + #region Create + /// /// Creates an object without persisting it /// @@ -806,8 +144,14 @@ namespace Umbraco.Core.Services /// public IMember CreateMember(string username, string email, string name, string memberTypeAlias) { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMember(username, email, name, memberType); + var memberType = GetMemberType(memberTypeAlias); + if (memberType == null) + throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); + + var member = new Member(name, email.ToLower().Trim(), username, memberType); + CreateMember(null, member, 0, false); + + return member; } /// @@ -823,9 +167,10 @@ namespace Umbraco.Core.Services /// public IMember CreateMember(string username, string email, string name, IMemberType memberType) { - var member = new Member(name, email.ToLower().Trim(), username, memberType); + if (memberType == null) throw new ArgumentNullException(nameof(memberType)); - Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); + var member = new Member(name, email.ToLower().Trim(), username, memberType); + CreateMember(null, member, 0, false); return member; } @@ -833,7 +178,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -842,14 +187,27 @@ namespace Umbraco.Core.Services /// public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMemberWithIdentity(username, email, name, memberType); + using (var uow = UowProvider.CreateUnitOfWork()) + { + // locking the media tree secures media types too + uow.WriteLock(Constants.Locks.MediaTree); + + var memberType = GetMemberType(memberTypeAlias); // + locks + if (memberType == null) + throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); // causes rollback + + var member = new Member(name, email.ToLower().Trim(), username, memberType); + CreateMember(uow, member, 0, true); + + uow.Complete(); + return member; + } } /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -857,13 +215,13 @@ namespace Umbraco.Core.Services /// public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) { - return CreateMemberWithIdentity(username, email, username, memberType); + return CreateMemberWithIdentity(username, email, username, "", memberType); } /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -886,14 +244,27 @@ namespace Umbraco.Core.Services /// IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMemberWithIdentity(username, email, username, passwordValue, memberType); + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + + // ensure it all still make sense + var memberType = GetMemberType(memberTypeAlias); // + locks + if (memberType == null) + throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); // causes rollback + + var member = new Member(username, email.ToLower().Trim(), username, passwordValue, memberType); + CreateMember(uow, member, -1, true); + + uow.Complete(); + return member; + } } /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -903,35 +274,143 @@ namespace Umbraco.Core.Services /// private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType) { - if (memberType == null) throw new ArgumentNullException("memberType"); + if (memberType == null) throw new ArgumentNullException(nameof(memberType)); - var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MediaTree); + + // ensure it all still make sense + var vrfy = GetMemberType(memberType.Alias); // + locks + if (vrfy == null || vrfy.Id != memberType.Id) + throw new ArgumentException($"Member type with alias {memberType.Alias} does not exist or is a different member type."); // causes rollback + + var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); + CreateMember(uow, member, -1, true); + + uow.Complete(); + return member; + } + } + + private void CreateMember(IDatabaseUnitOfWork uow, Member member, int userId, bool withIdentity) + { + // there's no Creating event for members if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) { member.WasCancelled = true; - return member; + return; } - using (var uow = UowProvider.CreateUnitOfWork()) + member.CreatorId = userId; + + if (withIdentity) { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) + { + member.WasCancelled = true; + return; + } + var repository = uow.CreateRepository(); repository.AddOrUpdate(member); - //insert the xml + + // fixme kill repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - uow.Complete(); + Saved.RaiseEvent(new SaveEventArgs(member, false), this); } - Saved.RaiseEvent(new SaveEventArgs(member, false), this); - Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); + Created.RaiseEvent(new NewEventArgs(member, false, member.ContentType.Alias, -1), this); - return member; + var msg = withIdentity + ? "Member '{0}' was created with Id {1}" + : "Member '{0}' was created"; + Audit(AuditType.New, string.Format(msg, member.Name, member.Id), member.CreatorId, member.Id); + } + + #endregion + + #region Get, Has, Is, Exists... + + /// + /// Gets a Member by its integer id + /// + /// Id + /// + public IMember GetById(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var member = repository.Get(id); + uow.Complete(); + return member; + } + } + + /// + /// Gets a Member by the unique key + /// + /// The guid key corresponds to the unique id in the database + /// and the user id in the membership provider. + /// Id + /// + public IMember GetByKey(Guid id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var query = repository.Query.Where(x => x.Key == id); + var member = repository.GetByQuery(query).FirstOrDefault(); + uow.Complete(); + return member; + } + } + + /// + /// Gets a list of paged objects + /// + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var members = repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); + uow.Complete(); + return members; + } + } + + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + { + return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); + } + + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var query = memberTypeAlias == null ? null : repository.Query.Where(x => x.ContentTypeAlias == memberTypeAlias); + var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); + uow.Complete(); + return members; + } } /// @@ -943,14 +422,11 @@ namespace Umbraco.Core.Services { var asGuid = id.TryConvertTo(); if (asGuid.Success) - { - return GetByKey((Guid)id); - } + return GetByKey(asGuid.Result); + var asInt = id.TryConvertTo(); if (asInt.Success) - { - return GetById((int)id); - } + return GetById(asInt.Result); return null; } @@ -964,6 +440,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Email.Equals(email)); var member = repository.GetByQuery(query).FirstOrDefault(); @@ -979,12 +456,13 @@ namespace Umbraco.Core.Services /// public IMember GetByUsername(string username) { - //TODO: Somewhere in here, whether at this level or the repository level, we need to add + //TODO: Somewhere in here, whether at this level or the repository level, we need to add // a caching mechanism since this method is used by all the membership providers and could be // called quite a bit when dealing with members. using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Username.Equals(username)); var member = repository.GetByQuery(query).FirstOrDefault(); @@ -993,6 +471,500 @@ namespace Umbraco.Core.Services } } + /// + /// Gets all Members for the specified MemberType alias + /// + /// Alias of the MemberType + /// + public IEnumerable GetMembersByMemberType(string memberTypeAlias) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var query = repository.Query.Where(x => x.ContentTypeAlias == memberTypeAlias); + var members = repository.GetByQuery(query); + uow.Complete(); + return members; + } + } + + /// + /// Gets all Members for the MemberType id + /// + /// Id of the MemberType + /// + public IEnumerable GetMembersByMemberType(int memberTypeId) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + repository.Get(memberTypeId); + var query = repository.Query.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query); + uow.Complete(); + return members; + } + } + + /// + /// Gets all Members within the specified MemberGroup name + /// + /// Name of the MemberGroup + /// + public IEnumerable GetMembersByGroup(string memberGroupName) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var members = repository.GetByMemberGroup(memberGroupName); + uow.Complete(); + return members; + } + } + + /// + /// Gets all Members with the ids specified + /// + /// If no Ids are specified all Members will be retrieved + /// Optional list of Member Ids + /// + public IEnumerable GetAllMembers(params int[] ids) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var members = repository.GetAll(ids); + uow.Complete(); + return members; + } + } + + /// + /// Finds Members based on their display name + /// + /// Display name to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var query = repository.Query; + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Name.Equals(displayNameToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Name.Contains(displayNameToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Name.StartsWith(displayNameToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Name.EndsWith(displayNameToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback + } + + var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, true); + uow.Complete(); + return members; + } + } + + /// + /// Finds a list of objects by a partial email string + /// + /// Partial email string to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var query = repository.Query; + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Email.Equals(emailStringToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Email.Contains(emailStringToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Email.StartsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Email.EndsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); + } + + var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, true); + uow.Complete(); + return members; + } + } + + /// + /// Finds a list of objects by a partial username + /// + /// Partial username to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var query = repository.Query; + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(login)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(login)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(login)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(login)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); + } + + var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); + uow.Complete(); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + IQuery query; + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.Contains: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.StartsWith: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.EndsWith: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback + } + + var members = repository.GetByQuery(query); + uow.Complete(); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback + } + + var members = repository.GetByQuery(query); + uow.Complete(); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).BoolPropertyValue == value); + + var members = repository.GetByQuery(query); + uow.Complete(); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = repository.Query.Where( x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = repository.Query.Where(x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback + } + + //TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! + var members = repository.GetByQuery(query); + uow.Complete(); + return members; + } + } + + /// + /// Checks if a Member with the id exists + /// + /// Id of the Member + /// True if the Member exists otherwise False + public bool Exists(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var exists = repository.Exists(id); + uow.Complete(); + return exists; + } + } + + /// + /// Checks if a Member with the username exists + /// + /// Username to check + /// True if the Member exists otherwise False + public bool Exists(string username) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + var exists = repository.Exists(username); + uow.Complete(); + return exists; + } + } + + #endregion + + #region Save + + /// + /// Saves an + /// + /// to Save + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + public void Save(IMember member, bool raiseEvents = true) + { + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) + return; + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + + repository.AddOrUpdate(member); + + // fixme get rid of xml + repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + + uow.Complete(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(member, false), this); + Audit(AuditType.Save, "Save Member performed by user", 0, member.Id); + } + + /// + /// Saves a list of objects + /// + /// to save + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + public void Save(IEnumerable members, bool raiseEvents = true) + { + var membersA = members.ToArray(); + + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(membersA), this)) + return; + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + foreach (var member in membersA) + { + repository.AddOrUpdate(member); + + // fixme get rid of xml stuff + repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + } + + uow.Complete(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(membersA, false), this); + Audit(AuditType.Save, "Save Member items performed by user", 0, -1); + } + + #endregion + + #region Delete + /// /// Deletes an /// @@ -1004,100 +976,35 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); - repository.Delete(member); + DeleteLocked(repository, member); uow.Complete(); - var args = new DeleteEventArgs(member, false); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); } + + Audit(AuditType.Delete, "Delete Member performed by user", 0, member.Id); } - /// - /// Saves an - /// - /// to Save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IMember entity, bool raiseEvents = true) + private void DeleteLocked(IMemberRepository repository, IMember member) { - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(entity), this)) - { - return; - } - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.AddOrUpdate(entity); - repository.AddOrUpdateContentXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - - uow.Complete(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(entity, false), this); - } - - /// - /// Saves a list of objects - /// - /// to save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IEnumerable entities, bool raiseEvents = true) - { - var asArray = entities.ToArray(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - } - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - foreach (var member in asArray) - { - repository.AddOrUpdate(member); - repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - } - - //commit the whole lot in one go - uow.Complete(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - } + // a member has no descendants + repository.Delete(member); + var args = new DeleteEventArgs(member, false); // raise event & get flagged files + Deleted.RaiseEvent(args, this); + IOHelper.DeleteFiles(args.MediaFilesToDelete, // remove flagged files + (file, e) => Logger.Error("An error occurred while deleting file attached to nodes: " + file, e)); } #endregion - #region IMembershipRoleService Implementation + #region Roles public void AddRole(string roleName) { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.CreateIfNotExists(roleName); uow.Complete(); @@ -1108,6 +1015,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var result = repository.GetAll().Select(x => x.Name).Distinct(); uow.Complete(); @@ -1119,6 +1027,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var result = repository.GetMemberGroupsForMember(memberId); var roles = result.Select(x => x.Name).Distinct(); @@ -1131,6 +1040,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var result = repository.GetMemberGroupsForMember(username); var roles = result.Select(x => x.Name).Distinct(); @@ -1143,6 +1053,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var members = repository.GetByMemberGroup(roleName); uow.Complete(); @@ -1154,6 +1065,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var members = repository.FindMembersInRole(roleName, usernameToMatch, matchType); uow.Complete(); @@ -1161,33 +1073,32 @@ namespace Umbraco.Core.Services } } + // FIXME CURRENT WIP + public bool DeleteRole(string roleName, bool throwIfBeingUsed) { - using (new WriteLock(Locker)) + using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + if (throwIfBeingUsed) { - var inRole = GetMembersInRole(roleName); - if (inRole.Any()) - { + // get members in role + var memberRepository = uow.CreateRepository(); + var membersInRole = memberRepository.GetByMemberGroup(roleName); + if (membersInRole.Any()) throw new InvalidOperationException("The role " + roleName + " is currently assigned to members"); - } } - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var qry = repository.QueryFactory.Create().Where(g => g.Name == roleName); - var found = repository.GetByQuery(qry).ToArray(); + var query = repository.QueryFactory.Create().Where(g => g.Name == roleName); + var found = repository.GetByQuery(query).ToArray(); - foreach (var memberGroup in found) - { - _memberGroupService.Delete(memberGroup); - } - var deleted = found.Any(); - uow.Complete(); - return deleted; - } + foreach (var memberGroup in found) + _memberGroupService.Delete(memberGroup); // FIXME BAD BAD BAD! + + uow.Complete(); + return found.Length > 0; } } public void AssignRole(string username, string roleName) @@ -1199,6 +1110,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.AssignRoles(usernames, roleNames); uow.Complete(); @@ -1214,6 +1126,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.DissociateRoles(usernames, roleNames); uow.Complete(); @@ -1229,6 +1142,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.AssignRoles(memberIds, roleNames); uow.Complete(); @@ -1244,39 +1158,16 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.DissociateRoles(memberIds, roleNames); uow.Complete(); } } - - #endregion - private IMemberType FindMemberTypeByAlias(string memberTypeAlias) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.QueryFactory.Create().Where(x => x.Alias == memberTypeAlias); - var types = repository.GetByQuery(query); - - if (types.Any() == false) - throw new Exception( - string.Format("No MemberType matching the passed in Alias: '{0}' was found", - memberTypeAlias)); // causes rollback - - var contentType = types.First(); - - if (contentType == null) - throw new Exception(string.Format("MemberType matching the passed in Alias: '{0}' was null", - memberTypeAlias)); // causes rollback - - uow.Complete(); - return contentType; - } - } + #region Private Methods private void Audit(AuditType type, string message, int userId, int objectId) { @@ -1288,6 +1179,8 @@ namespace Umbraco.Core.Services } } + #endregion + #region Event Handlers /// @@ -1321,6 +1214,40 @@ namespace Umbraco.Core.Services #endregion + #region Membership + + /// + /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method + /// + /// This method exists so that Umbraco developers can use one entry point to create/update + /// Members if they choose to. + /// The Member to save the password for + /// The password to encrypt and save + public void SavePassword(IMember member, string password) + { + if (member == null) throw new ArgumentNullException("member"); + + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider()) + provider.ChangePassword(member.Username, "", password); + else + throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); + + //go re-fetch the member and update the properties that may have changed + var result = GetByUsername(member.Username); + + //should never be null but it could have been deleted by another thread. + // fixme - should LOCK! instead + if (result == null) + return; + + member.RawPasswordValue = result.RawPasswordValue; + member.LastPasswordChangeDate = result.LastPasswordChangeDate; + member.UpdateDate = result.UpdateDate; + + // fixme - not saving? + } + /// /// A helper method that will create a basic/generic member for use with a generic membership provider /// @@ -1380,6 +1307,7 @@ namespace Umbraco.Core.Services memType.PropertyGroups.Add(propGroup); + // should we "create member"? var member = new Member(name, email, username, password, memType); //we've assigned ids to the property types and groups but we also need to assign fake ids to the properties themselves. @@ -1390,5 +1318,82 @@ namespace Umbraco.Core.Services return member; } + + #endregion + + #region Content Types + + /// + /// Delete Members of the specified MemberType id + /// + /// Id of the MemberType + public void DeleteMembersOfType(int memberTypeId) + { + // note: no tree to manage here + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.MemberTree); + var repository = uow.CreateRepository(); + + //TODO: What about content that has the contenttype as part of its composition? + var query = repository.Query.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query).ToArray(); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) + return; // causes rollback + + foreach (var member in members) + { + // delete media + // triggers the deleted event (and handles the files) + DeleteLocked(repository, member); + } + + uow.Complete(); + } + } + + private IMemberType GetMemberType(string memberTypeAlias) + { + var memberType = MemberTypeService.Get(memberTypeAlias); + if (memberType == null) + throw new Exception(string.Format("No MemberType matching alias: \"{0}\".", memberTypeAlias)); + return memberType; + } + + [Obsolete("use MemberTypeService.GetDefault()")] // fixme kill! + public string GetDefaultMemberType() + { + return MemberTypeService.GetDefault(); + } + + #endregion + + #region Xml - Should Move! + + /// + /// Rebuilds all xml content in the cmsContentXml table for all members + /// + /// + /// 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 + public void RebuildXmlStructures(params int[] memberTypeIds) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repository = uow.CreateRepository(); + repository.RebuildXmlStructures( + member => _entitySerializer.Serialize(_dataTypeService, member), + contentTypeIds: memberTypeIds.Length == 0 ? null : memberTypeIds); + uow.Complete(); + } + + Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 03b864e852..df78b59bd1 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -54,5 +54,23 @@ namespace Umbraco.Core.Services var memberService = _memberService as MemberService; memberService?.RebuildXmlStructures(toUpdate.Select(x => x.Id).ToArray()); } + + public string GetDefault() + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(ReadLockIds); + var repo = uow.CreateRepository(); + var e = repo.GetAll(new int[0]).GetEnumerator(); + if (e.MoveNext() == false) + throw new InvalidOperationException("No member types could be resolved"); + var first = e.Current.Alias; + var current = true; + while (e.Current.Alias.InvariantEquals("Member") == false && (current = e.MoveNext())) + { } + uow.Complete(); + return current ? e.Current.Alias : first; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 8744409521..c772eb95f1 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -67,7 +67,7 @@ namespace Umbraco.Core.Services //we'll lazily get these if we need to send notifications IEnumerable allVersions = null; - int totalUsers; + long totalUsers; var allUsers = _userService.GetAll(0, int.MaxValue, out totalUsers); foreach (var u in allUsers) { diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 0cad058ceb..4498e64347 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -384,7 +384,7 @@ namespace Umbraco.Core.Services /// Total number of records found (out) /// The type of match to make as . Default is /// - public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (var uow = UowProvider.CreateUnitOfWork()) { @@ -427,7 +427,7 @@ namespace Umbraco.Core.Services /// Total number of records found (out) /// The type of match to make as . Default is /// - public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (var uow = UowProvider.CreateUnitOfWork()) { @@ -515,7 +515,7 @@ namespace Umbraco.Core.Services /// Size of the page /// Total number of records found (out) /// - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords) + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) { using (var uow = UowProvider.CreateUnitOfWork()) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 1407240fdf..000817cd5c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -59,7 +59,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Flush(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); @@ -99,7 +99,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Flush(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 14233283d7..1a2ec8e516 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -446,7 +446,7 @@ namespace Umbraco.Tests.Services var members = MockedMember.CreateSimpleMember(memberType, 10); ServiceContext.MemberService.Save(members); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.GetAll(0, 2, out totalRecs); Assert.AreEqual(2, found.Count()); @@ -466,7 +466,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "Bob", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindMembersByDisplayName("B", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(1, found.Count()); @@ -483,7 +483,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello","hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByEmail("tes", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(10, found.Count()); @@ -500,7 +500,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByEmail("test.com", 0, 100, out totalRecs, StringPropertyMatchType.EndsWith); Assert.AreEqual(11, found.Count()); @@ -517,7 +517,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByEmail("test", 0, 100, out totalRecs, StringPropertyMatchType.Contains); Assert.AreEqual(11, found.Count()); @@ -534,7 +534,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByEmail("hello@test.com", 0, 100, out totalRecs, StringPropertyMatchType.Exact); Assert.AreEqual(1, found.Count()); @@ -551,7 +551,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByUsername("tes", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(10, found.Count()); @@ -568,7 +568,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByUsername("llo", 0, 100, out totalRecs, StringPropertyMatchType.EndsWith); Assert.AreEqual(1, found.Count()); @@ -585,7 +585,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hellotest"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByUsername("test", 0, 100, out totalRecs, StringPropertyMatchType.Contains); Assert.AreEqual(11, found.Count()); @@ -602,7 +602,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - int totalRecs; + long totalRecs; var found = ServiceContext.MemberService.FindByUsername("hello", 0, 100, out totalRecs, StringPropertyMatchType.Exact); Assert.AreEqual(1, found.Count()); diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 6b522efddd..0ca0f11ebf 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -188,7 +188,7 @@ namespace Umbraco.Tests.Services customUser.Email = "hello@hello.com"; ServiceContext.UserService.Save(customUser); - int totalRecs; + long totalRecs; var found = ServiceContext.UserService.FindByEmail("tes", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(10, found.Count()); @@ -206,7 +206,7 @@ namespace Umbraco.Tests.Services customUser.Email = "hello@test.com"; ServiceContext.UserService.Save(customUser); - int totalRecs; + long totalRecs; var found = ServiceContext.UserService.FindByEmail("test.com", 0, 100, out totalRecs, StringPropertyMatchType.EndsWith); Assert.AreEqual(11, found.Count()); @@ -224,7 +224,7 @@ namespace Umbraco.Tests.Services customUser.Email = "hello@test.com"; ServiceContext.UserService.Save(customUser); - int totalRecs; + long totalRecs; var found = ServiceContext.UserService.FindByEmail("test", 0, 100, out totalRecs, StringPropertyMatchType.Contains); Assert.AreEqual(11, found.Count()); @@ -242,7 +242,7 @@ namespace Umbraco.Tests.Services customUser.Email = "hello@test.com"; ServiceContext.UserService.Save(customUser); - int totalRecs; + long totalRecs; var found = ServiceContext.UserService.FindByEmail("hello@test.com", 0, 100, out totalRecs, StringPropertyMatchType.Exact); Assert.AreEqual(1, found.Count()); @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Services var users = MockedUser.CreateUser(userType, 10); ServiceContext.UserService.Save(users); - int totalRecs; + long totalRecs; var found = ServiceContext.UserService.GetAll(0, 2, out totalRecs); Assert.AreEqual(2, found.Count()); diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 9f3203d4ed..8649962cd7 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -588,7 +588,7 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.User: - int total; + long total; var users = Services.UserService.GetAll(0, int.MaxValue, out total); var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); return Mapper.Map, IEnumerable>(filteredUsers); diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 8482eb72a2..ce7e9e14ec 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -211,7 +211,9 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + long totalRecords2; + var byEmail = MemberService.FindByEmail(emailToMatch, pageIndex, pageSize, out totalRecords2, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = Convert.ToInt32(totalRecords2); var collection = new MembershipUserCollection(); foreach (var m in byEmail) @@ -233,7 +235,9 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + long totalRecords2; + var byEmail = MemberService.FindByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords2, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = Convert.ToInt32(totalRecords2); var collection = new MembershipUserCollection(); foreach (var m in byEmail) @@ -256,7 +260,9 @@ namespace Umbraco.Web.Security.Providers { var membersList = new MembershipUserCollection(); - var pagedMembers = MemberService.GetAll(pageIndex, pageSize, out totalRecords); + long totalRecords2; + var pagedMembers = MemberService.GetAll(pageIndex, pageSize, out totalRecords2); + totalRecords = Convert.ToInt32(totalRecords2); foreach (var m in pagedMembers) { @@ -483,7 +489,7 @@ namespace Umbraco.Web.Security.Providers if (RequiresUniqueEmail && user.Email.Trim().IsNullOrWhiteSpace() == false) { - int totalRecs; + long totalRecs; var byEmail = MemberService.FindByEmail(user.Email.Trim(), 0, int.MaxValue, out totalRecs, StringPropertyMatchType.Exact); if (byEmail.Count(x => x.Id != m.Id) > 0) { diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs index 1ac0f25e27..cd3bf66649 100644 --- a/src/Umbraco.Web/Trees/UsersTreeController.cs +++ b/src/Umbraco.Web/Trees/UsersTreeController.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Trees { var nodes = new TreeNodeCollection(); - int totalusers; + long totalusers; var users = new List(Services.UserService.GetAll(0, int.MaxValue, out totalusers)); var currentUser = UmbracoContext.Current.Security.CurrentUser; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserPermissions.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserPermissions.cs index f6b0a0e145..c52326e80d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserPermissions.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/UserPermissions.cs @@ -35,7 +35,7 @@ namespace umbraco.cms.presentation.Trees public override void Render(ref XmlTree tree) { - int totalusers; + long totalusers; foreach (var user in Services.UserService.GetAll(0, int.MaxValue, out totalusers)) { if (user.Id > 0 && user.IsApproved) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs index 0602882ff1..fc6149808d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs @@ -63,7 +63,7 @@ namespace umbraco.dialogs ht.Rows.Add(names); - int totalUsers; + long totalUsers; foreach (var u in Services.UserService.GetAll(0, int.MaxValue, out totalUsers)) { // Not disabled users and not system account @@ -117,7 +117,7 @@ namespace umbraco.dialogs { //get non disabled, non admin users and project to a dictionary, // the string (value) portion will store the array of chars = their permissions - int totalUsers; + long totalUsers; var usersPermissions = Services.UserService.GetAll(0, int.MaxValue, out totalUsers) .Where(user => user.IsApproved && user.Id > 0) .ToDictionary(user => user, user => ""); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs index 92b138cbfd..dcc8bc9301 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs @@ -70,7 +70,7 @@ namespace umbraco.presentation.dialogs includeSubpages.Enabled = false; // Translators - int totalUsers; + long totalUsers; foreach (var u in Services.UserService.GetAll(0, int.MaxValue, out totalUsers)) if (u.UserType.Alias.ToLower() == "translator" || UserHasTranslatePermission(u, _currentPage)) translator.Items.Add(new ListItem(u.Name, u.Id.ToString())); diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index a2879bd283..700cf05c2e 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -152,7 +152,7 @@ namespace UmbracoExamine //no node types specified, do all members do { - int total; + long total; members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); IndexItems(GetValueSets(members)); diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs index 5ce35f0050..2fd03ea8d3 100644 --- a/src/umbraco.cms/businesslogic/member/Member.cs +++ b/src/umbraco.cms/businesslogic/member/Member.cs @@ -109,7 +109,7 @@ namespace umbraco.cms.businesslogic.member public static IEnumerable GetAllAsList() { - int totalRecs; + long totalRecs; return ApplicationContext.Current.Services.MemberService.GetAll(0, int.MaxValue, out totalRecs) .Select(x => new Member(x)) .ToArray(); @@ -154,7 +154,7 @@ namespace umbraco.cms.businesslogic.member /// public static Member[] getMemberFromFirstLetter(char letter) { - int totalRecs; + long totalRecs; return ApplicationContext.Current.Services.MemberService.FindMembersByDisplayName( letter.ToString(CultureInfo.InvariantCulture), 0, int.MaxValue, out totalRecs, StringPropertyMatchType.StartsWith) @@ -164,7 +164,7 @@ namespace umbraco.cms.businesslogic.member public static Member[] GetMemberByName(string usernameToMatch, bool matchByNameInsteadOfLogin) { - int totalRecs; + long totalRecs; if (matchByNameInsteadOfLogin) { var found = ApplicationContext.Current.Services.MemberService.FindMembersByDisplayName( @@ -289,7 +289,7 @@ namespace umbraco.cms.businesslogic.member if (string.IsNullOrEmpty(email)) return null; - int totalRecs; + long totalRecs; var found = ApplicationContext.Current.Services.MemberService.FindByEmail( email, 0, int.MaxValue, out totalRecs, StringPropertyMatchType.Exact);