diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index a013f37b75..f46633dd65 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -5,10 +5,20 @@ namespace Umbraco.Core.Events { public class RecycleBinEventArgs : CancellableEventArgs { - public RecycleBinEventArgs(Guid nodeObjectType, IEnumerable files) + public RecycleBinEventArgs(Guid nodeObjectType, IEnumerable ids, List files, bool emptiedSuccessfully) : base(false) { NodeObjectType = nodeObjectType; + Ids = ids; + Files = files; + RecycleBinEmptiedSuccessfully = emptiedSuccessfully; + } + + public RecycleBinEventArgs(Guid nodeObjectType, IEnumerable ids, List files) + : base(true) + { + NodeObjectType = nodeObjectType; + Ids = ids; Files = files; } @@ -17,11 +27,37 @@ namespace Umbraco.Core.Events /// being deleted from the Recycle Bin. /// public Guid NodeObjectType { get; private set; } + + /// + /// Gets the list of Ids for each of the items being deleted from the Recycle Bin. + /// + public IEnumerable Ids { get; private set; } /// /// Gets the list of Files that should be deleted as part /// of emptying the Recycle Bin. /// - public IEnumerable Files { get; private set; } + public List Files { get; private set; } + + /// + /// Boolean indicating whether the Recycle Bin was emptied successfully + /// + public bool RecycleBinEmptiedSuccessfully { get; private set; } + + /// + /// Boolean indicating whether this event was fired for the Content's Recycle Bin. + /// + public bool IsContentRecycleBin + { + get { return NodeObjectType == new Guid(Constants.ObjectTypes.Document); } + } + + /// + /// Boolean indicating whether this event was fired for the Media's Recycle Bin. + /// + public bool IsMediaRecycleBin + { + get { return NodeObjectType == new Guid(Constants.ObjectTypes.Media); } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 4577046b17..df2a7bc1ef 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -181,6 +181,38 @@ namespace Umbraco.Core.Models } } + /// + /// Creates a clone of the current entity + /// + /// + public IContentType Clone(string alias) + { + var clone = (ContentType)this.MemberwiseClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + var propertyGroups = this.PropertyGroups.Select(x => x.Clone()).ToList(); + clone.PropertyGroups = new PropertyGroupCollection(propertyGroups); + clone.PropertyTypes = this.PropertyTypeCollection.Select(x => x.Clone()).ToList(); + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + + foreach (var propertyGroup in clone.PropertyGroups) + { + propertyGroup.ResetIdentity(); + foreach (var propertyType in propertyGroup.PropertyTypes) + { + propertyType.ResetIdentity(); + } + } + + foreach (var propertyType in clone.PropertyTypes.Where(x => x.HasIdentity)) + { + propertyType.ResetIdentity(); + } + + return clone; + } + /// /// Method to call when Entity is being saved /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index aba4ef66a9..dc5d9c7898 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -494,5 +494,14 @@ namespace Umbraco.Core.Models { _parentId = id; } + + /// + /// PropertyTypes that are not part of a PropertyGroup + /// + [IgnoreDataMember] + internal PropertyTypeCollection PropertyTypeCollection + { + get { return _propertyTypes; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 94ed57569a..4a0e09518a 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -110,6 +110,23 @@ namespace Umbraco.Core.Models } } + internal PropertyGroup Clone() + { + var clone = (PropertyGroup) this.MemberwiseClone(); + var collection = new PropertyTypeCollection(); + foreach (var propertyType in this.PropertyTypes) + { + var property = propertyType.Clone(); + property.ResetIdentity(); + property.ResetDirtyProperties(false); + collection.Add(property); + } + clone.PropertyTypes = collection; + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + /// /// Sets the ParentId from the lazy integer id /// diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index c723710e7a..3b4915862a 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -378,6 +378,14 @@ namespace Umbraco.Core.Models return true; } + internal PropertyType Clone() + { + var clone = (PropertyType) this.MemberwiseClone(); + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + public bool Equals(PropertyType other) { //Check whether the compared object is null. diff --git a/src/Umbraco.Core/Persistence/Querying/QueryHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs similarity index 61% rename from src/Umbraco.Core/Persistence/Querying/QueryHelper.cs rename to src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 508ad787ea..d01eed30f9 100644 --- a/src/Umbraco.Core/Persistence/Querying/QueryHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -6,9 +6,9 @@ namespace Umbraco.Core.Persistence.Querying /// /// Logic that is shared with the expression helpers /// - internal class QueryHelper + internal class BaseExpressionHelper { - public static string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) + public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) { if (value == null) return "NULL"; @@ -57,14 +57,45 @@ namespace Umbraco.Core.Persistence.Querying : value.ToString(); } - public static string EscapeParam(object paramValue) + public virtual string EscapeParam(object paramValue) { return paramValue.ToString().Replace("'", "''"); } - public static bool ShouldQuoteValue(Type fieldType) + public virtual string EscapeAtArgument(string exp) + { + if (exp.StartsWith("@")) + return string.Concat("@", exp); + + return exp; + } + + public virtual bool ShouldQuoteValue(Type fieldType) { return true; } + + protected virtual string RemoveQuote(string exp) + { + if (exp.StartsWith("'") && exp.EndsWith("'")) + { + exp = exp.Remove(0, 1); + exp = exp.Remove(exp.Length - 1, 1); + } + return exp; + } + + protected virtual string RemoveQuoteFromAlias(string exp) + { + + if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) + && + (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) + { + exp = exp.Remove(0, 1); + exp = exp.Remove(exp.Length - 1, 1); + } + return exp; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 559b978721..3042510b05 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Persistence.Querying { - internal class ModelToSqlExpressionHelper + internal class ModelToSqlExpressionHelper : BaseExpressionHelper { private string sep = " "; private BaseMapper _mapper; @@ -246,7 +246,7 @@ namespace Umbraco.Core.Persistence.Querying case "ToLower": return string.Format("lower({0})", r); case "StartsWith": - return string.Format("upper({0}) like '{1}%'", r, RemoveQuote(args[0].ToString().ToUpper())); + return string.Format("upper({0}) like '{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString().ToUpper()))); case "EndsWith": return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); case "Contains": @@ -435,41 +435,7 @@ namespace Umbraco.Core.Persistence.Querying public virtual string GetQuotedValue(object value, Type fieldType) { - return QueryHelper.GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); - } - - public virtual string EscapeParam(object paramValue) - { - return paramValue.ToString().Replace("'", "''"); - } - - public virtual bool ShouldQuoteValue(Type fieldType) - { - return true; - } - - protected string RemoveQuote(string exp) - { - - if (exp.StartsWith("'") && exp.EndsWith("'")) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - protected string RemoveQuoteFromAlias(string exp) - { - - if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - && - (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; + return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); } private string GetTrueExpression() diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs index f84933b3e7..41057ed6c5 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { - internal class PocoToSqlExpressionHelper + internal class PocoToSqlExpressionHelper : BaseExpressionHelper { private string sep = " "; private Database.PocoData pd; @@ -250,7 +250,7 @@ namespace Umbraco.Core.Persistence.Querying case "ToLower": return string.Format("lower({0})", r); case "StartsWith": - return string.Format("upper({0}) like '{1}%'", r, RemoveQuote(args[0].ToString().ToUpper())); + return string.Format("upper({0}) like '{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString().ToUpper()))); case "EndsWith": return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); case "Contains": @@ -439,17 +439,7 @@ namespace Umbraco.Core.Persistence.Querying public virtual string GetQuotedValue(object value, Type fieldType) { - return QueryHelper.GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); - } - - public virtual string EscapeParam(object paramValue) - { - return paramValue.ToString().Replace("'", "''"); - } - - public virtual bool ShouldQuoteValue(Type fieldType) - { - return true; + return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); } protected virtual string GetFieldName(Database.PocoData pocoData, string name) @@ -460,30 +450,6 @@ namespace Umbraco.Core.Persistence.Querying SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName)); } - protected string RemoveQuote(string exp) - { - - if (exp.StartsWith("'") && exp.EndsWith("'")) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - protected string RemoveQuoteFromAlias(string exp) - { - - if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - && - (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - private string GetTrueExpression() { object o = GetQuotedTrueValue(); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 7acd2cb7a0..fe4dae0be2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -486,10 +486,6 @@ namespace Umbraco.Core.Persistence.Repositories return GetByVersion(dto.ContentVersionDto.VersionId); } - public bool EmptyRecycleBin() - { - var repo = new RecycleBinRepository(UnitOfWork); - return repo.EmptyRecycleBin(NodeObjectTypeId); } public void AssignEntityPermissions(IContent entity, string permission, IEnumerable userIds) @@ -502,8 +498,6 @@ namespace Umbraco.Core.Persistence.Repositories { var repo = new PermissionRepository(UnitOfWork); return repo.GetPermissionsForEntity(entityId); - } - #endregion /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 08df9e768e..92748f1029 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; @@ -22,12 +23,6 @@ namespace Umbraco.Core.Persistence.Repositories /// An enumerable list of IEnumerable GetByPublishedVersion(IQuery query); - /// - /// Empties the Recycle Bin for deleted Content - /// - /// True if the Recycle Bin was successfully emptied and all items deleted otherwise False - bool EmptyRecycleBin(); - void AssignEntityPermissions(IContent entity, string permission, IEnumerable userIds); IEnumerable GetPermissionsForEntity(int entityId); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 8c016c8818..5d0e088d39 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -4,10 +4,5 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IMediaRepository : IRepositoryVersionable { - /// - /// Empties the Recycle Bin for media Content - /// - /// True if the Recycle Bin was successfully emptied and all items deleted otherwise False - bool EmptyRecycleBin(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index e2d9725849..e27fac1ba3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -356,16 +356,6 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Implementation of IMediaRepository - - public bool EmptyRecycleBin() - { - var repo = new RecycleBinRepository(UnitOfWork); - return repo.EmptyRecycleBin(NodeObjectTypeId); - } - - #endregion - private PropertyCollection GetPropertyCollection(int id, Guid versionId, IMediaType contentType, DateTime createDate, DateTime updateDate) { var sql = new Sql(); diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index 97ca510160..2b4728da5f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Umbraco.Core.Configuration; -using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -14,7 +13,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Represents a repository specific to the Recycle Bins /// available for Content and Media. /// - internal class RecycleBinRepository + internal class RecycleBinRepository : DisposableObject { private readonly IDatabaseUnitOfWork _unitOfWork; @@ -23,7 +22,14 @@ namespace Umbraco.Core.Persistence.Repositories _unitOfWork = unitOfWork; } - public bool EmptyRecycleBin(Guid nodeObjectType) + /// + /// Gets a list of files, which are referenced on items in the Recycle Bin. + /// The list is generated by the convention that a file is referenced by + /// the Upload data type or a property type with the alias 'umbracoFile'. + /// + /// + /// + public List GetFilesInRecycleBin(Guid nodeObjectType) { var db = _unitOfWork.Database; @@ -40,6 +46,36 @@ namespace Umbraco.Core.Persistence.Repositories new { FileAlias = Constants.Conventions.Media.File, NodeObjectType = nodeObjectType, ControlId = Constants.PropertyEditors.UploadField }); var files = db.Fetch(sql); + return files; + } + + /// + /// Gets a list of Ids for each of the items in the Recycle Bin. + /// + /// + /// + public List GetIdsOfItemsInRecycleBin(Guid nodeObjectType) + { + var db = _unitOfWork.Database; + + var idsSql = new Sql(); + idsSql.Select("DISTINCT(id)") + .From() + .Where(x => x.Trashed && x.NodeObjectType == nodeObjectType); + + var ids = db.Fetch(idsSql); + return ids; + } + + /// + /// Empties the Recycle Bin by running single bulk-Delete queries + /// against the Content- or Media's Recycle Bin. + /// + /// + /// + public bool EmptyRecycleBin(Guid nodeObjectType) + { + var db = _unitOfWork.Database; //Construct and execute delete statements for all trashed items by 'nodeObjectType' var deletes = new List @@ -73,9 +109,6 @@ namespace Umbraco.Core.Persistence.Repositories trans.Complete(); - //Trigger (internal) event with list of files to delete - RecycleBinEmptied - RecycleBinEmptied.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, files), this); - return true; } catch (Exception ex) @@ -87,6 +120,11 @@ namespace Umbraco.Core.Persistence.Repositories } } + /// + /// Deletes all files passed in. + /// + /// + /// public bool DeleteFiles(IEnumerable files) { try @@ -127,8 +165,14 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// Occurs after RecycleBin was been Emptied + /// Dispose disposable properties /// - internal static event TypedEventHandler RecycleBinEmptied; + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _unitOfWork.DisposeIfDisposable(); + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/SimilarNodeNameComparer.cs b/src/Umbraco.Core/Persistence/Repositories/SimilarNodeNameComparer.cs index 82ba7fcc6c..ce25486422 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimilarNodeNameComparer.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimilarNodeNameComparer.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Repositories { public int Compare(string x, string y) { - if (x.LastIndexOf(')') == x.Length - 1 && y.LastIndexOf(')') == y.Length - 1) + if (x.LastIndexOf('(') != -1 && x.LastIndexOf(')') == x.Length - 1 && y.LastIndexOf(')') == y.Length - 1) { if (x.ToLower().Substring(0, x.LastIndexOf('(')) == y.ToLower().Substring(0, y.LastIndexOf('('))) { diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index d8d4b1c7e2..1d9d9aff21 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -9,34 +9,6 @@ namespace Umbraco.Core.Persistence /// public class RepositoryFactory { - - internal virtual ServerRegistrationRepository CreateServerRegistrationRepository(IDatabaseUnitOfWork uow) - { - return new ServerRegistrationRepository( - uow, - NullCacheProvider.Current); - } - - internal virtual IUserTypeRepository CreateUserTypeRepository(IDatabaseUnitOfWork uow) - { - return new UserTypeRepository( - uow, - NullCacheProvider.Current); - } - - internal virtual IUserRepository CreateUserRepository(IDatabaseUnitOfWork uow) - { - return new UserRepository( - uow, - NullCacheProvider.Current, - CreateUserTypeRepository(uow)); - } - - internal virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) - { - return new EntityRepository(uow); - } - public virtual IContentRepository CreateContentRepository(IDatabaseUnitOfWork uow) { return new ContentRepository( @@ -120,5 +92,37 @@ namespace Umbraco.Core.Persistence { return new TemplateRepository(uow, RuntimeCacheProvider.Current); } + + internal virtual ServerRegistrationRepository CreateServerRegistrationRepository(IDatabaseUnitOfWork uow) + { + return new ServerRegistrationRepository( + uow, + NullCacheProvider.Current); + } + + internal virtual IUserTypeRepository CreateUserTypeRepository(IDatabaseUnitOfWork uow) + { + return new UserTypeRepository( + uow, + NullCacheProvider.Current); + } + + internal virtual IUserRepository CreateUserRepository(IDatabaseUnitOfWork uow) + { + return new UserRepository( + uow, + NullCacheProvider.Current, + CreateUserTypeRepository(uow)); + } + + internal virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) + { + return new EntityRepository(uow); + } + + internal virtual RecycleBinRepository CreateRecycleBinRepository(IDatabaseUnitOfWork uow) + { + return new RecycleBinRepository(uow); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index f999a2bc21..6d0e002eea 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1029,28 +1029,29 @@ namespace Umbraco.Core.Services /// public void EmptyRecycleBin() { - //TODO: Why don't we have a base class to share between MediaService/ContentService as some of this is exacty the same? - - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateContentRepository(uow)) + using (new WriteLock(Locker)) { - /*var query = Query.Builder.Where(x => x.Trashed == true); - var contents = repository.GetByQuery(query).OrderByDescending(x => x.Level); + List ids; + List files; + bool success; + var nodeObjectType = new Guid(Constants.ObjectTypes.Document); - foreach (var content in contents) + using (var repository = _repositoryFactory.CreateRecycleBinRepository(_uowProvider.GetUnitOfWork())) { - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(content), this)) - continue; + ids = repository.GetIdsOfItemsInRecycleBin(nodeObjectType); + files = repository.GetFilesInRecycleBin(nodeObjectType); - repository.Delete(content); + if (EmptyingRecycleBin.IsRaisedEventCancelled(new RecycleBinEventArgs(nodeObjectType, ids, files), this)) + return; - Deleted.RaiseEvent(new DeleteEventArgs(content, false), this); - }*/ - repository.EmptyRecycleBin(); - uow.Commit(); + success = repository.EmptyRecycleBin(nodeObjectType); + if (success) + repository.DeleteFiles(files); + } + + EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, ids, files, success), this); } - - Audit.Add(AuditTypes.Delete, "Empty Recycle Bin performed by user", 0, -20); + Audit.Add(AuditTypes.Delete, "Empty Content Recycle Bin performed by user", 0, -20); } /// @@ -1903,7 +1904,7 @@ namespace Umbraco.Core.Services /// Occurs after Create /// /// - /// Please note that the Content object has been created, but not saved + /// Please note that the Content 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; @@ -1957,6 +1958,16 @@ namespace Umbraco.Core.Services /// Occurs after Send to Publish /// public static event TypedEventHandler> SentToPublish; + + /// + /// 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 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index dac42a4fc6..68b4253f77 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -537,28 +537,29 @@ namespace Umbraco.Core.Services /// public void EmptyRecycleBin() { - //TODO: Why don't we have a base class to share between MediaService/ContentService as some of this is exacty the same? + using (new WriteLock(Locker)) + { + List ids; + List files; + bool success; + var nodeObjectType = new Guid(Constants.ObjectTypes.Media); - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateMediaRepository(uow)) - { - /*var query = Query.Builder.Where(x => x.Trashed == true); - var contents = repository.GetByQuery(query).OrderByDescending(x => x.Level); + using (var repository = _repositoryFactory.CreateRecycleBinRepository(_uowProvider.GetUnitOfWork())) + { + ids = repository.GetIdsOfItemsInRecycleBin(nodeObjectType); + files = repository.GetFilesInRecycleBin(nodeObjectType); - foreach (var content in contents) - { - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(content), this)) - continue; + if (EmptyingRecycleBin.IsRaisedEventCancelled(new RecycleBinEventArgs(nodeObjectType, ids, files), this)) + return; - repository.Delete(content); + success = repository.EmptyRecycleBin(nodeObjectType); + if (success) + repository.DeleteFiles(files); + } - Deleted.RaiseEvent(new DeleteEventArgs(content, false), this); - }*/ - repository.EmptyRecycleBin(); - uow.Commit(); - } - - Audit.Add(AuditTypes.Delete, "Empty Recycle Bin performed by user", 0, -20); + EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, ids, files, success), this); + } + Audit.Add(AuditTypes.Delete, "Empty Media Recycle Bin performed by user", 0, -21); } /// @@ -1012,6 +1013,16 @@ namespace Umbraco.Core.Services /// 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 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0d67bbbdae..72d2d64f53 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -489,11 +489,11 @@ + - diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 706322b81d..522f608272 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -725,15 +725,21 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1048); + var temp = contentService.GetById(1048); // Act - var copy = contentService.Copy(content, content.ParentId, false, 0); + var copy = contentService.Copy(temp, temp.ParentId, false, 0); + var content = contentService.GetById(1048); // Assert Assert.That(copy, Is.Not.Null); Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); Assert.AreNotSame(content, copy); + foreach (var property in copy.Properties) + { + Assert.AreNotEqual(property.Id, content.Properties[property.Alias].Id); + Assert.AreEqual(property.Value, content.Properties[property.Alias].Value); + } //Assert.AreNotEqual(content.Name, copy.Name); } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 0773b968f7..90bc77a0b3 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -263,6 +263,41 @@ namespace Umbraco.Tests.Services Assert.That(homepage.CompositionPropertyTypes.Count(), Is.EqualTo(4)); } + [Test] + public void Can_Copy_ContentType_By_Performing_Clone() + { + // Arrange + var service = ServiceContext.ContentTypeService; + var metaContentType = MockedContentTypes.CreateMetaContentType(); + service.Save(metaContentType); + + var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", metaContentType); + service.Save(simpleContentType); + var categoryId = simpleContentType.Id; + + // Act + var sut = simpleContentType.Clone("newcategory"); + service.Save(sut); + + // Assert + Assert.That(sut.HasIdentity, Is.True); + + var contentType = service.GetContentType(sut.Id); + var category = service.GetContentType(categoryId); + + Assert.That(contentType.CompositionAliases().Any(x => x.Equals("meta")), Is.True); + Assert.AreEqual(contentType.ParentId, category.ParentId); + Assert.AreEqual(contentType.Level, category.Level); + Assert.AreEqual(contentType.PropertyTypes.Count(), category.PropertyTypes.Count()); + Assert.AreNotEqual(contentType.Id, category.Id); + Assert.AreNotEqual(contentType.Key, category.Key); + Assert.AreNotEqual(contentType.Path, category.Path); + Assert.AreNotEqual(contentType.SortOrder, category.SortOrder); + Assert.AreNotEqual(contentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id, category.PropertyTypes.First(x => x.Alias.Equals("title")).Id); + Assert.AreNotEqual(contentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id, category.PropertyGroups.First(x => x.Name.Equals("Content")).Id); + + } + private ContentType CreateComponent() { var component = new ContentType(-1) diff --git a/src/Umbraco.Web/Strategies/DeleteFilesAfterEmptiedRecycleBin.cs b/src/Umbraco.Web/Strategies/DeleteFilesAfterEmptiedRecycleBin.cs deleted file mode 100644 index e60c0e3dc9..0000000000 --- a/src/Umbraco.Web/Strategies/DeleteFilesAfterEmptiedRecycleBin.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Repositories; - -namespace Umbraco.Web.Strategies -{ - /// - /// Represents the DeleteFilesAfterEmptiedRecycleBin class, which subscribes to the - /// RecycleBinEmptied event of the class - /// and is responsible for deleting files attached to the items that were - /// permanently deleted from the Recycle Bin - when the Recycle Bin was emptied. - /// - public sealed class DeleteFilesAfterEmptiedRecycleBin : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, - ApplicationContext applicationContext) - { - RecycleBinRepository.RecycleBinEmptied += RecycleBinRepository_RecycleBinEmptied; - } - - void RecycleBinRepository_RecycleBinEmptied(RecycleBinRepository sender, RecycleBinEventArgs e) - { - var success = sender.DeleteFiles(e.Files); - - if(success) - LogHelper.Info("All files attached to delete nodes were deleted as part of emptying the Recycle Bin"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 168657080a..4676a8e75f 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -470,7 +470,6 @@ - True True diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index 3cb48ce274..e27fd53785 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -138,52 +138,17 @@ namespace umbraco.dialogs return true; } - - - //PPH Handle doctype copies.. private void HandleDocumentTypeCopy() { - var documentType = new DocumentType(int.Parse(Request.GetItemAsString("id"))); + var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; + var contentType = contentTypeService.GetContentType( + int.Parse(Request.GetItemAsString("id"))); - //Documentype exists.. create new doc type... - var alias = rename.Text; - var newDocumentType = DocumentType.MakeNew(base.getUser(), alias.Replace("'", "''")); + var alias = rename.Text.Replace("'", "''"); + var clone = ((Umbraco.Core.Models.ContentType) contentType).Clone(alias); + contentTypeService.Save(clone); - newDocumentType.IconUrl = documentType.IconUrl; - newDocumentType.Thumbnail = documentType.Thumbnail; - newDocumentType.Description = documentType.Description; - newDocumentType.allowedTemplates = documentType.allowedTemplates; - newDocumentType.DefaultTemplate = documentType.DefaultTemplate; - newDocumentType.AllowedChildContentTypeIDs = documentType.AllowedChildContentTypeIDs; - newDocumentType.AllowAtRoot = documentType.AllowAtRoot; - - newDocumentType.MasterContentType = int.Parse(masterType.SelectedValue); - - var oldNewTabIds = new Hashtable(); - foreach (var tab in documentType.getVirtualTabs.Where(t => t.ContentType == documentType.Id)) - { - int tabId = newDocumentType.AddVirtualTab(tab.Caption); - oldNewTabIds.Add(tab.Id, tabId); - } - - foreach (var propertyType in documentType.PropertyTypes.Where(p => p.ContentTypeId == documentType.Id)) - { - var newPropertyType = cms.businesslogic.propertytype.PropertyType.MakeNew(propertyType.DataTypeDefinition, newDocumentType, propertyType.Name, propertyType.Alias); - newPropertyType.ValidationRegExp = propertyType.ValidationRegExp; - newPropertyType.SortOrder = propertyType.SortOrder; - newPropertyType.Mandatory = propertyType.Mandatory; - newPropertyType.Description = propertyType.Description; - - if (propertyType.TabId > 0 && oldNewTabIds[propertyType.TabId] != null) - { - var newTabId = (int)oldNewTabIds[propertyType.TabId]; - newPropertyType.TabId = newTabId; - } - } - - var returnUrl = string.Format("{0}/settings/editNodeTypeNew.aspx?id={1}", SystemDirectories.Umbraco, newDocumentType.Id); - - newDocumentType.Save(); + var returnUrl = string.Format("{0}/settings/editNodeTypeNew.aspx?id={1}", SystemDirectories.Umbraco, clone.Id); pane_settings.Visible = false; panel_buttons.Visible = false;