using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Xml.Linq; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { /// /// Represents the Media Service, which is an easy access to operations involving /// 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; public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) : base(provider, repositoryFactory, logger, eventMessagesFactory) { if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); if (userService == null) throw new ArgumentNullException("userService"); _dataTypeService = dataTypeService; _userService = userService; } /// /// Creates an object using the alias of the /// that this Media should based on. /// /// /// 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. /// /// 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 CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0) { var mediaType = FindMediaTypeByAlias(mediaTypeAlias); 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); return media; } /// /// Creates an object using the alias of the /// that this Media should based on. /// /// /// 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. /// /// 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 CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) { if (parent == null) throw new ArgumentNullException("parent"); var mediaType = FindMediaTypeByAlias(mediaTypeAlias); var media = new Models.Media(name, parent, mediaType); media.Path = string.Concat(parent.Path, ",", media.Id); if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) { media.WasCancelled = true; return media; } 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)) { media.WasCancelled = true; return media; } if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) { media.WasCancelled = true; return media; } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { media.CreatorId = userId; repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); } uow.Commit(); } Saved.RaiseEvent(new SaveEventArgs(media, false), this); Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), 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 /// 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"); 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; } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { media.CreatorId = userId; repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); } uow.Commit(); } Saved.RaiseEvent(new SaveEventArgs(media, false), this); Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); return media; } /// /// Gets an object by Id /// /// Id of the Content to retrieve /// public IMedia GetById(int id) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { return repository.Get(id); } } public int Count(string contentTypeAlias = null) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { return repository.Count(contentTypeAlias); } } public int CountChildren(int parentId, string contentTypeAlias = null) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { return repository.CountChildren(parentId, contentTypeAlias); } } public int CountDescendants(int parentId, string contentTypeAlias = null) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { return repository.CountDescendants(parentId, contentTypeAlias); } } /// /// Gets an object by Id /// /// Ids of the Media to retrieve /// public IEnumerable GetByIds(IEnumerable ids) { if (ids.Any() == false) return Enumerable.Empty(); using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { return repository.GetAll(ids.ToArray()); } } /// /// Gets an object by its 'UniqueId' /// /// Guid key of the Media to retrieve /// public IMedia GetById(Guid key) { using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.Key == key); var contents = repository.GetByQuery(query); return contents.SingleOrDefault(); } } /// /// Gets a collection of objects by Level /// /// The level to retrieve Media from /// An Enumerable list of objects public IEnumerable GetByLevel(int level) { using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith("-21")); var contents = repository.GetByQuery(query); return contents; } } /// /// Gets a specific version of an item. /// /// Id of the version to retrieve /// An item public IMedia GetByVersion(Guid versionId) { using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { return repository.GetByVersion(versionId); } } /// /// Gets a collection of an objects versions by Id /// /// /// An Enumerable list of objects public IEnumerable GetVersions(int id) { using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var versions = repository.GetAllVersions(id); return versions; } } /// /// Gets a collection of objects, which are ancestors of the current media. /// /// Id of the to retrieve ancestors for /// An Enumerable list of objects public IEnumerable GetAncestors(int id) { var media = GetById(id); return GetAncestors(media); } /// /// Gets a collection of objects, which are ancestors of the current media. /// /// to retrieve ancestors for /// 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(); if (ids.Any() == false) return new List(); using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { return repository.GetAll(ids); } } /// /// Gets a collection of objects by Parent Id /// /// Id of the Parent to retrieve Children from /// An Enumerable list of objects public IEnumerable GetChildren(int id) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { var query = Query.Builder.Where(x => x.ParentId == id); var medias = repository.GetByQuery(query); return medias; } } [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy, Direction orderDirection, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder; query.Where(x => x.ParentId == id); long total; var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); totalChildren = Convert.ToInt32(total); return medias; } } /// /// Gets a collection of objects by Parent Id /// /// Id of the Parent to retrieve Children from /// Page index (zero based) /// Page size /// Total records query would return without paging /// Field to order by /// Direction to order by /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder; query.Where(x => x.ParentId == id); var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); return medias; } } [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder; //if the id is -1, then just get all if (id != -1) { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } long total; var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); totalChildren = Convert.ToInt32(total); return contents; } } /// /// Gets a collection of objects by Parent Id /// /// Id of the Parent to retrieve Descendants from /// Page number /// Page size /// Total records query would return without paging /// Field to order by /// Direction to order by /// Search text filter /// 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 = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder; //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, filter); return contents; } } /// /// Gets descendants of a object by its Id /// /// Id of the Parent to retrieve descendants from /// An Enumerable flat list of objects public IEnumerable GetDescendants(int id) { var media = GetById(id); if (media == null) { return Enumerable.Empty(); } return GetDescendants(media); } /// /// Gets descendants of a object by its Id /// /// The Parent object to retrieve descendants from /// An Enumerable flat list of objects public IEnumerable GetDescendants(IMedia media) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { var pathMatch = media.Path + ","; var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != media.Id); var medias = repository.GetByQuery(query); return medias; } } /// /// Gets the parent of the current media as an item. /// /// Id of the to retrieve the parent from /// Parent object public IMedia GetParent(int id) { var media = GetById(id); return GetParent(media); } /// /// Gets the parent of the current media as an item. /// /// to retrieve the parent from /// Parent object public IMedia GetParent(IMedia media) { if (media.ParentId == -1 || media.ParentId == -21) 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) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { var query = Query.Builder.Where(x => x.ContentTypeId == id); var medias = repository.GetByQuery(query); return medias; } } /// /// Gets a collection of objects, which reside at the first level / root /// /// An Enumerable list of objects public IEnumerable GetRootMedia() { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { var query = Query.Builder.Where(x => x.ParentId == -1); var medias = repository.GetByQuery(query); return medias; } } /// /// Gets a collection of an objects, which resides in the Recycle Bin /// /// An Enumerable list of objects public IEnumerable GetMediaInRecycleBin() { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { var query = Query.Builder.Where(x => x.Path.Contains("-21")); var medias = repository.GetByQuery(query); 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) { var umbracoFileValue = mediaPath; const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*"; var isResized = Regex.IsMatch(mediaPath, Pattern); // If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url. if (isResized) { var underscoreIndex = mediaPath.LastIndexOf('_'); var dotIndex = mediaPath.LastIndexOf('.'); umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); } Func createSql = url => new Sql().Select("*") .From() .InnerJoin() .On(left => left.PropertyTypeId, right => right.Id) .Where(x => x.Alias == "umbracoFile") .Where(x => x.VarChar == url); var sql = createSql(umbracoFileValue); using (var uow = UowProvider.GetUnitOfWork()) { var propertyDataDto = uow.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 = uow.Database.Fetch(sql).FirstOrDefault(); } return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId); } } /// /// Checks whether an item has any children /// /// Id of the /// True if the media has any children otherwise False public bool HasChildren(int id) { using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.ParentId == id); int count = repository.Count(query); return count > 0; } } /// /// Moves an object to a new location /// /// 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) { //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) { 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.Cancelled(evtMsgs); } //Delete children before deleting the 'possible parent' var children = GetChildren(media.Id); foreach (var child in children) { Delete(child, userId); } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { repository.Delete(media); uow.Commit(); var args = new DeleteEventArgs(media, false, evtMsgs); Deleted.RaiseEvent(args, this); //remove any flagged media files repository.DeleteMediaFiles(args.MediaFilesToDelete); } Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); return OperationStatus.Success(evtMsgs); } /// /// Saves a single object /// /// The to save /// Id of the User saving the Media /// Optional boolean indicating whether or not to raise events. Attempt IMediaServiceOperations.Save(IMedia media, int userId, bool raiseEvents) { var evtMsgs = EventMessagesFactory.Get(); if (raiseEvents) { if (Saving.IsRaisedEventCancelled( new SaveEventArgs(media, evtMsgs), this)) { return OperationStatus.Cancelled(evtMsgs); } } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { media.CreatorId = userId; repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); } uow.Commit(); } if (raiseEvents) Saved.RaiseEvent(new SaveEventArgs(media, false, evtMsgs), this); Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); return OperationStatus.Success(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. Attempt IMediaServiceOperations.Save(IEnumerable medias, int userId, bool raiseEvents) { var asArray = medias.ToArray(); var evtMsgs = EventMessagesFactory.Get(); if (raiseEvents) { if (Saving.IsRaisedEventCancelled( new SaveEventArgs(asArray, evtMsgs), this)) { return OperationStatus.Cancelled(evtMsgs); } } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { foreach (var media in asArray) { media.CreatorId = userId; repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); } } //commit the whole lot in one go uow.Commit(); } if (raiseEvents) Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); Audit(AuditType.Save, "Save Media items performed by user", userId, -1); return OperationStatus.Success(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); var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { //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; success = repository.EmptyRecycleBin(); EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, entities, files, success), this); if (success) repository.DeleteMediaFiles(files); } } 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)) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { //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 = Query.Builder.Where(x => x.ContentTypeId == mediaTypeId); var contents = repository.GetByQuery(query).ToArray(); if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(contents), this)) return; 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 = Query.Builder.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); } } 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.Cancelled(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(); var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { //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 }); 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 }); descendant.ChangeTrashedState(true, descendant.ParentId); repository.AddOrUpdate(descendant); moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); } uow.Commit(); } 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.Success(evtMsgs); } /// /// Permanently deletes an object as well as all of its Children. /// /// /// Please note that this method will completely remove the Media from the database, /// as well as associated media files from the file system. /// /// The to delete /// Id of the User deleting the Media public void Delete(IMedia media, int userId = 0) { ((IMediaServiceOperations)this).Delete(media, userId); } /// /// Permanently deletes versions from an object prior to a specific date. /// This method will never delete the latest version of a content item. /// /// Id of the object to delete versions from /// Latest version date /// Optional Id of the User deleting versions of a Content object public void DeleteVersions(int id, DateTime versionDate, int userId = 0) { if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) return; var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { repository.DeleteVersions(id, versionDate); uow.Commit(); } DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, -1); } /// /// Permanently deletes specific version(s) from an object. /// This method will never delete the latest version of a content 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 public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) { if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) return; if (deletePriorVersions) { var content = GetByVersion(versionId); DeleteVersions(id, content.UpdateDate, userId); } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { repository.DeleteVersion(versionId); uow.Commit(); } DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); } /// /// Saves a single object /// /// 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) { ((IMediaServiceOperations)this).Save (media, userId, raiseEvents); } /// /// Saves a collection of objects /// /// 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) { ((IMediaServiceOperations)this).Save(medias, userId, raiseEvents); } /// /// Sorts a collection of objects by updating the SortOrder according /// to the ordering of items in the passed in . /// /// /// /// /// True if sorting succeeded, otherwise False public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) { var asArray = items.ToArray(); if (raiseEvents) { if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) return false; } var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { int i = 0; foreach (var media in asArray) { //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) { i++; continue; } media.SortOrder = i; i++; repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) { repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); } } uow.Commit(); } if (raiseEvents) Saved.RaiseEvent(new SaveEventArgs(asArray, 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) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { repository.RebuildXmlStructures( media => _entitySerializer.Serialize(this, _dataTypeService, _userService, media), contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); } 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) { 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"); var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) { var query = Query.Builder.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)); var mediaType = mediaTypes.First(); if (mediaType == null) throw new Exception(string.Format("MediaType matching the passed in Alias: '{0}' was null", mediaTypeAlias)); return mediaType; } } private void Audit(AuditType type, string message, int userId, int objectId) { var uow = UowProvider.GetUnitOfWork(); using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) { auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); uow.Commit(); } } #region Event Handlers /// /// Occurs before Delete /// public static event TypedEventHandler DeletingVersions; /// /// Occurs after Delete /// public static event TypedEventHandler DeletedVersions; /// /// Occurs before Delete /// public static event TypedEventHandler> Deleting; /// /// Occurs after Delete /// public static event TypedEventHandler> Deleted; /// /// Occurs before Save /// public static event TypedEventHandler> Saving; /// /// Occurs after Save /// public static event TypedEventHandler> Saved; /// /// Occurs before Create /// [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] public static event TypedEventHandler> Creating; /// /// Occurs after Create /// /// /// Please note that the Media object has been created, but not 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 /// public static event TypedEventHandler> Trashing; /// /// Occurs after Content is moved to Recycle Bin /// public static event TypedEventHandler> Trashed; /// /// Occurs before Move /// public static event TypedEventHandler> Moving; /// /// Occurs after Move /// public static event TypedEventHandler> Moved; /// /// Occurs before the Recycle Bin is emptied /// public static event TypedEventHandler EmptyingRecycleBin; /// /// Occurs after the Recycle Bin has been Emptied /// public static event TypedEventHandler EmptiedRecycleBin; #endregion } }