Fixes issue of removing content types in bulk (like when removing a package) and having the trashed event being raised for what will end up being a deleted node, Fixes a bug with the (very silly) extension methods for content types, removes singleton accessors from these (very silly) extension methods - which actually never worked for media types!
This commit is contained in:
@@ -1,32 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
//fixme: This whole thing needs to go, it's super hacky and doens't need to exist in the first place
|
||||
internal static class ContentTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all descendant content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> Descendants(this IContentTypeBase contentType)
|
||||
public static IEnumerable<IContentTypeBase> Descendants(this IContentTypeBase contentType, IContentTypeService contentTypeService)
|
||||
{
|
||||
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
|
||||
var descendants = contentTypeService.GetContentTypeChildren(contentType.Id)
|
||||
.SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id));
|
||||
return descendants;
|
||||
if (contentType is IContentType)
|
||||
{
|
||||
var descendants = contentTypeService.GetContentTypeChildren(contentType.Id)
|
||||
.SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id));
|
||||
return descendants;
|
||||
}
|
||||
|
||||
if (contentType is IMediaType)
|
||||
{
|
||||
var descendants = contentTypeService.GetMediaTypeChildren(contentType.Id)
|
||||
.SelectRecursive(type => contentTypeService.GetMediaTypeChildren(type.Id));
|
||||
return descendants;
|
||||
}
|
||||
|
||||
throw new NotSupportedException("The content type must be " + typeof(IContentType) + " or " + typeof(IMediaType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all descendant content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> Descendants(this IContentTypeBase contentType, ContentTypeServiceBase contentTypeService)
|
||||
{
|
||||
var cService = (IContentTypeService)contentTypeService;
|
||||
|
||||
return contentType.Descendants(cService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all descendant and self content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> DescendantsAndSelf(this IContentTypeBase contentType)
|
||||
public static IEnumerable<IContentTypeBase> DescendantsAndSelf(this IContentTypeBase contentType, IContentTypeService contentTypeService)
|
||||
{
|
||||
var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants());
|
||||
var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants(contentTypeService));
|
||||
return descendantsAndSelf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all descendant and self content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> DescendantsAndSelf(this IContentTypeBase contentType, ContentTypeServiceBase contentTypeService)
|
||||
{
|
||||
var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants(contentTypeService));
|
||||
return descendantsAndSelf;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an expression which caches the visitor's result.
|
||||
/// </summary>
|
||||
internal class CachedExpression : Expression
|
||||
{
|
||||
private string _visitResult;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the inner Expression.
|
||||
/// </summary>
|
||||
public Expression InnerExpression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compiled SQL statement output.
|
||||
/// </summary>
|
||||
public string VisitResult
|
||||
{
|
||||
get { return _visitResult; }
|
||||
set
|
||||
{
|
||||
if (Visited)
|
||||
throw new InvalidOperationException("Cached expression has already been visited.");
|
||||
_visitResult = value;
|
||||
Visited = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the cache Expression has been compiled already.
|
||||
/// </summary>
|
||||
public bool Visited { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the inner expression.
|
||||
/// </summary>
|
||||
/// <param name="expression">expression.</param>
|
||||
/// <remarks>The new expression is assumed to have different parameter but produce the same SQL statement.</remarks>
|
||||
public void Wrap(Expression expression)
|
||||
{
|
||||
InnerExpression = expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,49 +9,6 @@ using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an expression which caches the visitor's result.
|
||||
/// </summary>
|
||||
internal class CachedExpression : Expression
|
||||
{
|
||||
private string _visitResult;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the inner Expression.
|
||||
/// </summary>
|
||||
public Expression InnerExpression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compiled SQL statement output.
|
||||
/// </summary>
|
||||
public string VisitResult
|
||||
{
|
||||
get { return _visitResult; }
|
||||
set
|
||||
{
|
||||
if (Visited)
|
||||
throw new InvalidOperationException("Cached expression has already been visited.");
|
||||
_visitResult = value;
|
||||
Visited = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the cache Expression has been compiled already.
|
||||
/// </summary>
|
||||
public bool Visited { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the inner expression.
|
||||
/// </summary>
|
||||
/// <param name="expression">expression.</param>
|
||||
/// <remarks>The new expression is assumed to have different parameter but produce the same SQL statement.</remarks>
|
||||
public void Wrap(Expression expression)
|
||||
{
|
||||
InnerExpression = expression;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression.
|
||||
/// </summary>
|
||||
@@ -581,6 +538,17 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
case "InvariantContains":
|
||||
case "InvariantEquals":
|
||||
|
||||
//special case, if it is 'Contains' and the argumet that Contains is being called on is
|
||||
//Enumerable and the methodArgs is the actual member access, then it's an SQL IN claus
|
||||
if (m.Arguments.Count == 2
|
||||
&& m.Object == null
|
||||
&& methodArgs.Length == 1
|
||||
&& methodArgs[0].NodeType == ExpressionType.MemberAccess
|
||||
&& TypeHelper.IsTypeAssignableFrom<IEnumerable>(m.Arguments[0].Type))
|
||||
{
|
||||
goto case "SqlIn";
|
||||
}
|
||||
|
||||
string compareValue;
|
||||
|
||||
if (methodArgs[0].NodeType != ExpressionType.Constant)
|
||||
@@ -597,13 +565,6 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
compareValue = methodArgs[0].ToString();
|
||||
}
|
||||
|
||||
//special case, if it is 'Contains' and the member that Contains is being called on is not a string, then
|
||||
// we should be doing an 'In' clause - but we currently do not support this
|
||||
if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom<IEnumerable>(methodArgs[0].Type))
|
||||
{
|
||||
throw new NotSupportedException("An array Contains method is not supported");
|
||||
}
|
||||
|
||||
//default column type
|
||||
var colType = TextColumnType.NVarchar;
|
||||
|
||||
@@ -705,29 +666,33 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
// }
|
||||
// return string.Format("{0}{1}", r, s);
|
||||
|
||||
//case "In":
|
||||
case "SqlIn":
|
||||
|
||||
// var member = Expression.Convert(m.Arguments[0], typeof(object));
|
||||
// var lambda = Expression.Lambda<Func<object>>(member);
|
||||
// var getter = lambda.Compile();
|
||||
if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess)
|
||||
{
|
||||
var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]);
|
||||
|
||||
var member = Expression.Convert(m.Arguments[0], typeof(object));
|
||||
var lambda = Expression.Lambda<Func<object>>(member);
|
||||
var getter = lambda.Compile();
|
||||
|
||||
// var inArgs = (object[])getter();
|
||||
var inArgs = (IEnumerable)getter();
|
||||
|
||||
// var sIn = new StringBuilder();
|
||||
// foreach (var e in inArgs)
|
||||
// {
|
||||
// SqlParameters.Add(e);
|
||||
var sIn = new StringBuilder();
|
||||
foreach (var e in inArgs)
|
||||
{
|
||||
SqlParameters.Add(e);
|
||||
|
||||
// sIn.AppendFormat("{0}{1}",
|
||||
// sIn.Length > 0 ? "," : "",
|
||||
// string.Format("@{0}", SqlParameters.Count - 1));
|
||||
sIn.AppendFormat("{0}{1}",
|
||||
sIn.Length > 0 ? "," : "",
|
||||
string.Format("@{0}", SqlParameters.Count - 1));
|
||||
}
|
||||
|
||||
// //sIn.AppendFormat("{0}{1}",
|
||||
// // sIn.Length > 0 ? "," : "",
|
||||
// // GetQuotedValue(e, e.GetType()));
|
||||
// }
|
||||
return string.Format("{0} IN ({1})", memberAccess, sIn);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("SqlIn must contain the member being accessed");
|
||||
|
||||
// return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
|
||||
//case "Desc":
|
||||
// return string.Format("{0} DESC", r);
|
||||
//case "Alias":
|
||||
|
||||
+8
-1
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
@@ -6,8 +8,13 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
/// <summary>
|
||||
/// String extension methods used specifically to translate into SQL
|
||||
/// </summary>
|
||||
internal static class SqlStringExtensions
|
||||
internal static class SqlExpressionExtensions
|
||||
{
|
||||
public static bool SqlIn<T>(this IEnumerable<T> collection, T item)
|
||||
{
|
||||
return collection.Contains(item);
|
||||
}
|
||||
|
||||
public static bool SqlWildcard(this string str, string txt, TextColumnType columnType)
|
||||
{
|
||||
var wildcardmatch = new Regex("^" + Regex.Escape(txt).
|
||||
@@ -679,6 +679,7 @@ namespace Umbraco.Core.Services
|
||||
//if the id is System Root, then just get all
|
||||
if (id != Constants.System.Root)
|
||||
{
|
||||
//TODO: Wouldn't this be faster doing a starts with than a contains??
|
||||
query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar));
|
||||
}
|
||||
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter);
|
||||
@@ -1320,6 +1321,121 @@ namespace Umbraco.Core.Services
|
||||
((IContentServiceOperations)this).Save(contents, userId, raiseEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin.
|
||||
/// </summary>
|
||||
/// <param name="contentTypeIds">Id of the <see cref="IContentType"/></param>
|
||||
/// <param name="userId">Optional Id of the user issueing the delete operation</param>
|
||||
public void DeleteContentOfTypes(IEnumerable<int> contentTypeIds, int userId = 0)
|
||||
{
|
||||
var contentToRecycle = new List<IContent>();
|
||||
var contentToDelete = new List<IContent>();
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateContentRepository(uow);
|
||||
|
||||
//TODO: What about content that has the contenttype as part of its composition?
|
||||
|
||||
//track the 'root' items of the collection of nodes discovered to delete, we need to use
|
||||
//these items to lookup descendants that are not of this doc type so they can be transfered
|
||||
//to the recycle bin
|
||||
var rootItems = new Dictionary<int, IContent>();
|
||||
|
||||
var query = Query<IContent>.Builder.Where(x => contentTypeIds.Contains(x.ContentTypeId));
|
||||
|
||||
long pageIndex = 0;
|
||||
var pageSize = 10000;
|
||||
int currentPageSize;
|
||||
do
|
||||
{
|
||||
long total;
|
||||
|
||||
//start at the highest level
|
||||
var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "umbracoNode.level", Direction.Ascending, true).ToArray();
|
||||
|
||||
// need to store decendants count before filtering, in order for loop to work correctly
|
||||
currentPageSize = contents.Length;
|
||||
|
||||
//loop through the items, check if if the item exists already in the hierarchy of items tracked
|
||||
//and if not, we need to add it as a 'root' item to be used to lookup later
|
||||
foreach (var content in contents)
|
||||
{
|
||||
if (rootItems.ContainsKey(content.ParentId) == false)
|
||||
{
|
||||
//this item's parent doesn't exist so we need to track it
|
||||
rootItems[content.Id] = content;
|
||||
}
|
||||
|
||||
//track content for deletion
|
||||
contentToDelete.Add(content);
|
||||
}
|
||||
|
||||
pageIndex++;
|
||||
} while (currentPageSize == pageSize);
|
||||
|
||||
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IContent>(contentToDelete), "Deleting"))
|
||||
{
|
||||
uow.Commit();
|
||||
return;
|
||||
}
|
||||
|
||||
//iterate over the root items found in the collection to be deleted, then discover which descendant items
|
||||
//need to be moved to the recycle bin
|
||||
foreach (var content in rootItems)
|
||||
{
|
||||
//Look for children of current content and move that to trash before the current content is deleted
|
||||
var c = content;
|
||||
var pathMatch = string.Format("{0},", c.Value.Path);
|
||||
var descendantQuery = Query<IContent>.Builder.Where(x => x.Path.StartsWith(pathMatch));
|
||||
|
||||
do
|
||||
{
|
||||
long total;
|
||||
|
||||
var descendants = repository.GetPagedResultsByQuery(descendantQuery, pageIndex, pageSize, out total, "umbracoNode.id", Direction.Ascending, true).ToArray();
|
||||
|
||||
foreach (var d in descendants)
|
||||
{
|
||||
//track for recycling if this item is not of a contenttype that is being deleted
|
||||
if (contentTypeIds.Contains(d.ContentTypeId) == false)
|
||||
{
|
||||
contentToRecycle.Add(d);
|
||||
}
|
||||
}
|
||||
|
||||
// need to store decendants count before filtering, in order for loop to work correctly
|
||||
currentPageSize = descendants.Length;
|
||||
|
||||
pageIndex++;
|
||||
} while (currentPageSize == pageSize);
|
||||
}
|
||||
|
||||
// do it INSIDE the UOW because nested UOW kinda should work
|
||||
// fixme - and then we probably don't need the whole mess?
|
||||
// nesting UOW works, it's just that the outer one NEEDS to be flushed beforehand
|
||||
// nevertheless, it would be nicer to create a global scope and inner uow
|
||||
|
||||
foreach (var child in contentToRecycle)
|
||||
{
|
||||
MoveToRecycleBin(child, userId);
|
||||
}
|
||||
|
||||
foreach (var content in contentToDelete)
|
||||
{
|
||||
Delete(content, userId);
|
||||
}
|
||||
|
||||
uow.Commit();
|
||||
|
||||
Audit(AuditType.Delete,
|
||||
string.Format("Delete Content of Types {0} performed by user", string.Join(",", contentTypeIds)),
|
||||
userId, Constants.System.Root);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin.
|
||||
/// </summary>
|
||||
|
||||
@@ -817,23 +817,19 @@ namespace Umbraco.Core.Services
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
if (uow.Events.DispatchCancelable(DeletingContentType, this, new DeleteEventArgs<IContentType>(contentType)))
|
||||
return;
|
||||
var repository = RepositoryFactory.CreateContentTypeRepository(uow);
|
||||
|
||||
//TODO: This needs to change, if we are deleting a content type, we should just delete the data,
|
||||
// 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 deletedContentTypes = new List<IContentType>() {contentType};
|
||||
deletedContentTypes.AddRange(contentType.Descendants().OfType<IContentType>());
|
||||
|
||||
foreach (var deletedContentType in deletedContentTypes)
|
||||
{
|
||||
_contentService.DeleteContentOfType(deletedContentType.Id);
|
||||
uow.Commit();
|
||||
return;
|
||||
}
|
||||
|
||||
var repository = RepositoryFactory.CreateContentTypeRepository(uow);
|
||||
|
||||
//If we are deleting this content type, we are also deleting it's descendents!
|
||||
var deletedContentTypes = new List<IContentType>() {contentType};
|
||||
deletedContentTypes.AddRange(contentType.Descendants((IContentTypeService)this).OfType<IContentType>());
|
||||
|
||||
_contentService.DeleteContentOfTypes(deletedContentTypes.Select(x => x.Id), userId);
|
||||
|
||||
repository.Delete(contentType);
|
||||
uow.Commit();
|
||||
|
||||
@@ -855,32 +851,27 @@ namespace Umbraco.Core.Services
|
||||
public void Delete(IEnumerable<IContentType> contentTypes, int userId = 0)
|
||||
{
|
||||
var asArray = contentTypes.ToArray();
|
||||
|
||||
using (var scope = UowProvider.ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.Complete(); // always complete
|
||||
if (scope.Events.DispatchCancelable(DeletingContentType, this, new DeleteEventArgs<IContentType>(asArray)))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
if (uow.Events.DispatchCancelable(DeletingContentType, this, new DeleteEventArgs<IContentType>(asArray)))
|
||||
{
|
||||
uow.Commit();
|
||||
return;
|
||||
}
|
||||
|
||||
var repository = RepositoryFactory.CreateContentTypeRepository(uow);
|
||||
|
||||
var deletedContentTypes = new List<IContentType>();
|
||||
deletedContentTypes.AddRange(asArray);
|
||||
|
||||
//If we are deleting this content type, we are also deleting it's descendents!
|
||||
var deletedContentTypes = new List<IContentType>(asArray);
|
||||
foreach (var contentType in asArray)
|
||||
{
|
||||
deletedContentTypes.AddRange(contentType.Descendants().OfType<IContentType>());
|
||||
deletedContentTypes.AddRange(contentType.Descendants((IContentTypeService)this).OfType<IContentType>());
|
||||
}
|
||||
|
||||
foreach (var deletedContentType in deletedContentTypes)
|
||||
{
|
||||
_contentService.DeleteContentOfType(deletedContentType.Id);
|
||||
}
|
||||
_contentService.DeleteContentOfTypes(deletedContentTypes.Select(x => x.Id), userId);
|
||||
|
||||
foreach (var contentType in asArray)
|
||||
{
|
||||
@@ -1277,23 +1268,25 @@ namespace Umbraco.Core.Services
|
||||
/// <remarks>Deleting a <see cref="IMediaType"/> will delete all the <see cref="IMedia"/> objects based on this <see cref="IMediaType"/></remarks>
|
||||
public void Delete(IMediaType mediaType, int userId = 0)
|
||||
{
|
||||
using (var scope = UowProvider.ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.Complete(); // always
|
||||
if (scope.Events.DispatchCancelable(DeletingMediaType, this, new DeleteEventArgs<IMediaType>(mediaType)))
|
||||
return;
|
||||
}
|
||||
//TODO: Share all of this logic with the Delete IContentType methods, no need for code duplication
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
_mediaService.DeleteMediaOfType(mediaType.Id, userId);
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
if (uow.Events.DispatchCancelable(DeletingMediaType, this, new DeleteEventArgs<IMediaType>(mediaType)))
|
||||
{
|
||||
uow.Commit();
|
||||
return;
|
||||
}
|
||||
|
||||
var repository = RepositoryFactory.CreateMediaTypeRepository(uow);
|
||||
|
||||
//If we are deleting this content type, we are also deleting it's descendents!
|
||||
var deletedMediaTypes = new List<IMediaType> { mediaType };
|
||||
deletedMediaTypes.AddRange(mediaType.Descendants().OfType<IMediaType>());
|
||||
deletedMediaTypes.AddRange(mediaType.Descendants((IContentTypeService)this).OfType<IMediaType>());
|
||||
|
||||
_mediaService.DeleteMediaOfTypes(deletedMediaTypes.Select(x => x.Id), userId);
|
||||
|
||||
repository.Delete(mediaType);
|
||||
uow.Commit();
|
||||
@@ -1313,34 +1306,36 @@ namespace Umbraco.Core.Services
|
||||
/// <remarks>Deleting a <see cref="IMediaType"/> will delete all the <see cref="IMedia"/> objects based on this <see cref="IMediaType"/></remarks>
|
||||
public void Delete(IEnumerable<IMediaType> mediaTypes, int userId = 0)
|
||||
{
|
||||
//TODO: Share all of this logic with the Delete IContentType methods, no need for code duplication
|
||||
|
||||
var asArray = mediaTypes.ToArray();
|
||||
|
||||
using (var scope = UowProvider.ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.Complete(); // always
|
||||
if (scope.Events.DispatchCancelable(DeletingMediaType, this, new DeleteEventArgs<IMediaType>(asArray)))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
foreach (var mediaType in asArray)
|
||||
{
|
||||
_mediaService.DeleteMediaOfType(mediaType.Id);
|
||||
}
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateMediaTypeRepository(uow);
|
||||
if (uow.Events.DispatchCancelable(DeletingMediaType, this, new DeleteEventArgs<IMediaType>(asArray)))
|
||||
{
|
||||
uow.Commit();
|
||||
return;
|
||||
}
|
||||
|
||||
var deletedMediaTypes = new List<IMediaType>();
|
||||
deletedMediaTypes.AddRange(asArray);
|
||||
var repository = RepositoryFactory.CreateMediaTypeRepository(uow);
|
||||
|
||||
//If we are deleting this content type, we are also deleting it's descendents!
|
||||
var deletedMediaTypes = new List<IMediaType>(asArray);
|
||||
foreach (var mediaType in asArray)
|
||||
{
|
||||
deletedMediaTypes.AddRange(mediaType.Descendants((IContentTypeService)this).OfType<IMediaType>());
|
||||
}
|
||||
|
||||
_mediaService.DeleteMediaOfTypes(deletedMediaTypes.Select(x => x.Id), userId);
|
||||
|
||||
foreach (var mediaType in asArray)
|
||||
{
|
||||
deletedMediaTypes.AddRange(mediaType.Descendants().OfType<IMediaType>());
|
||||
repository.Delete(mediaType);
|
||||
}
|
||||
|
||||
uow.Commit();
|
||||
|
||||
uow.Events.Dispatch(DeletedMediaType, this, new DeleteEventArgs<IMediaType>(deletedMediaTypes.DistinctBy(x => x.Id), false));
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
//if a property was deleted or alias changed, then update all content of the current content type
|
||||
// and all of it's desscendant doc types.
|
||||
toUpdate.AddRange(contentType.DescendantsAndSelf());
|
||||
toUpdate.AddRange(contentType.DescendantsAndSelf(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +368,14 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="userId">Optional Id of the user issueing the delete operation</param>
|
||||
void DeleteContentOfType(int contentTypeId, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin.
|
||||
/// </summary>
|
||||
/// <remarks>This needs extra care and attention as its potentially a dangerous and extensive operation</remarks>
|
||||
/// <param name="contentTypeIds">Ids of the <see cref="IContentType"/>s</param>
|
||||
/// <param name="userId">Optional Id of the user issueing the delete operation</param>
|
||||
void DeleteContentOfTypes(IEnumerable<int> contentTypeIds, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Permanently deletes versions from an <see cref="IContent"/> object prior to a specific date.
|
||||
/// </summary>
|
||||
|
||||
@@ -255,6 +255,14 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="userId">Optional Id of the user deleting Media</param>
|
||||
void DeleteMediaOfType(int mediaTypeId, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all media of the specified types. All Descendants of deleted media that is not of these types is moved to Recycle Bin.
|
||||
/// </summary>
|
||||
/// <remarks>This needs extra care and attention as its potentially a dangerous and extensive operation</remarks>
|
||||
/// <param name="mediaTypeIds">Ids of the <see cref="IMediaType"/>s</param>
|
||||
/// <param name="userId">Optional Id of the user issueing the delete operation</param>
|
||||
void DeleteMediaOfTypes(IEnumerable<int> mediaTypeIds, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Permanently deletes an <see cref="IMedia"/> object
|
||||
/// </summary>
|
||||
|
||||
@@ -950,6 +950,124 @@ namespace Umbraco.Core.Services
|
||||
Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin.
|
||||
/// </summary>
|
||||
/// <param name="mediaTypeIds">Id of the <see cref="IContentType"/></param>
|
||||
/// <param name="userId">Optional Id of the user issueing the delete operation</param>
|
||||
public void DeleteMediaOfTypes(IEnumerable<int> mediaTypeIds, int userId = 0)
|
||||
{
|
||||
//TODO: put this duplicate logic in a base class between media/content/(member) services
|
||||
|
||||
var mediaToRecycle = new List<IMedia>();
|
||||
var mediaToDelete = new List<IMedia>();
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateMediaRepository(uow);
|
||||
|
||||
//TODO: What about content that has the contenttype as part of its composition?
|
||||
|
||||
//track the 'root' items of the collection of nodes discovered to delete, we need to use
|
||||
//these items to lookup descendants that are not of this doc type so they can be transfered
|
||||
//to the recycle bin
|
||||
var rootItems = new Dictionary<int, IMedia>();
|
||||
|
||||
var query = Query<IMedia>.Builder.Where(x => mediaTypeIds.Contains(x.ContentTypeId));
|
||||
|
||||
long pageIndex = 0;
|
||||
var pageSize = 10000;
|
||||
int currentPageSize;
|
||||
do
|
||||
{
|
||||
long total;
|
||||
|
||||
//start at the highest level
|
||||
var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "umbracoNode.level", Direction.Ascending, true).ToArray();
|
||||
|
||||
// need to store decendants count before filtering, in order for loop to work correctly
|
||||
currentPageSize = contents.Length;
|
||||
|
||||
//loop through the items, check if if the item exists already in the hierarchy of items tracked
|
||||
//and if not, we need to add it as a 'root' item to be used to lookup later
|
||||
foreach (var content in contents)
|
||||
{
|
||||
if (rootItems.ContainsKey(content.ParentId) == false)
|
||||
{
|
||||
//this item's parent doesn't exist so we need to track it
|
||||
rootItems[content.Id] = content;
|
||||
}
|
||||
|
||||
//track content for deletion
|
||||
mediaToDelete.Add(content);
|
||||
}
|
||||
|
||||
pageIndex++;
|
||||
} while (currentPageSize == pageSize);
|
||||
|
||||
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IMedia>(mediaToDelete), "Deleting"))
|
||||
{
|
||||
uow.Commit();
|
||||
return;
|
||||
}
|
||||
|
||||
//iterate over the root items found in the collection to be deleted, then discover which descendant items
|
||||
//need to be moved to the recycle bin
|
||||
foreach (var content in rootItems)
|
||||
{
|
||||
//Look for children of current content and move that to trash before the current content is deleted
|
||||
var c = content;
|
||||
var pathMatch = string.Format("{0},", c.Value.Path);
|
||||
var descendantQuery = Query<IMedia>.Builder.Where(x => x.Path.StartsWith(pathMatch));
|
||||
|
||||
do
|
||||
{
|
||||
long total;
|
||||
|
||||
var descendants = repository.GetPagedResultsByQuery(descendantQuery, pageIndex, pageSize, out total, "umbracoNode.id", Direction.Ascending, true).ToArray();
|
||||
|
||||
foreach (var d in descendants)
|
||||
{
|
||||
//track for recycling if this item is not of a contenttype that is being deleted
|
||||
if (mediaTypeIds.Contains(d.ContentTypeId) == false)
|
||||
{
|
||||
mediaToRecycle.Add(d);
|
||||
}
|
||||
}
|
||||
|
||||
// need to store decendants count before filtering, in order for loop to work correctly
|
||||
currentPageSize = descendants.Length;
|
||||
|
||||
pageIndex++;
|
||||
} while (currentPageSize == pageSize);
|
||||
}
|
||||
|
||||
// do it INSIDE the UOW because nested UOW kinda should work
|
||||
// fixme - and then we probably don't need the whole mess?
|
||||
// nesting UOW works, it's just that the outer one NEEDS to be flushed beforehand
|
||||
// nevertheless, it would be nicer to create a global scope and inner uow
|
||||
|
||||
foreach (var child in mediaToRecycle)
|
||||
{
|
||||
MoveToRecycleBin(child, userId);
|
||||
}
|
||||
|
||||
foreach (var content in mediaToDelete)
|
||||
{
|
||||
Delete(content, userId);
|
||||
}
|
||||
|
||||
uow.Commit();
|
||||
|
||||
Audit(AuditType.Delete,
|
||||
string.Format("Delete Media of Types {0} performed by user", string.Join(",", mediaTypeIds)),
|
||||
userId, Constants.System.Root);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
|
||||
/// </summary>
|
||||
@@ -993,27 +1111,27 @@ namespace Umbraco.Core.Services
|
||||
//track content for deletion
|
||||
contentList.Add(content);
|
||||
}
|
||||
//} // fixme - just so it builds
|
||||
//} // fixme - just so it builds
|
||||
|
||||
// FIXME WHAT THE MASSIVE FUCK
|
||||
//We need to do this outside of the uow because otherwise we'll have nested uow's
|
||||
//TODO: this is a problem because we are doing all of this logic in the Service when it should
|
||||
//all be happening in the repository
|
||||
// FIXME WHAT THE MASSIVE FUCK
|
||||
//We need to do this outside of the uow because otherwise we'll have nested uow's
|
||||
//TODO: this is a problem because we are doing all of this logic in the Service when it should
|
||||
//all be happening in the repository
|
||||
|
||||
foreach (var child in childList)
|
||||
{
|
||||
if (child.ContentType.Id != mediaTypeId)
|
||||
MoveToRecycleBin(child, userId);
|
||||
}
|
||||
foreach (var content in contentList)
|
||||
{
|
||||
//Permanently delete the content
|
||||
Delete(content, userId);
|
||||
foreach (var child in childList)
|
||||
{
|
||||
if (child.ContentType.Id != mediaTypeId)
|
||||
MoveToRecycleBin(child, userId);
|
||||
}
|
||||
foreach (var content in contentList)
|
||||
{
|
||||
//Permanently delete the content
|
||||
Delete(content, userId);
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
Audit(AuditType.Delete, "Delete Media items by Type performed by user", userId, -1);
|
||||
Audit(AuditType.Delete, "Delete Media items by Type performed by user", userId, -1);
|
||||
} // fixme
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,6 +518,7 @@
|
||||
<Compile Include="Media\Exif\Utility.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeOne\UpdateUserLanguagesToIsoCode.cs" />
|
||||
<Compile Include="Persistence\PocoDataDataReader.cs" />
|
||||
<Compile Include="Persistence\Querying\CachedExpression.cs" />
|
||||
<Compile Include="Persistence\Querying\QueryExtensions.cs" />
|
||||
<Compile Include="Persistence\RecordPersistenceType.cs" />
|
||||
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
|
||||
@@ -722,7 +723,7 @@
|
||||
<Compile Include="PropertyEditors\DefaultPropertyValueConverterAttribute.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSeven\UpdateRelatedLinksData.cs" />
|
||||
<Compile Include="PropertyEditors\IValueEditor.cs" />
|
||||
<Compile Include="Persistence\Querying\SqlStringExtensions.cs" />
|
||||
<Compile Include="Persistence\Querying\SqlExpressionExtensions.cs" />
|
||||
<Compile Include="Persistence\Querying\StringPropertyMatchType.cs" />
|
||||
<Compile Include="Persistence\Querying\TextColumnType.cs" />
|
||||
<Compile Include="Persistence\Querying\ValuePropertyMatchType.cs" />
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace Umbraco.Tests.Services
|
||||
var master = hierarchy.First();
|
||||
|
||||
//Act
|
||||
var descendants = master.Descendants();
|
||||
var descendants = master.Descendants(contentTypeService);
|
||||
|
||||
//Assert
|
||||
Assert.AreEqual(10, descendants.Count());
|
||||
@@ -193,7 +193,7 @@ namespace Umbraco.Tests.Services
|
||||
var master = hierarchy.First();
|
||||
|
||||
//Act
|
||||
var descendants = master.DescendantsAndSelf();
|
||||
var descendants = master.DescendantsAndSelf(contentTypeService);
|
||||
|
||||
//Assert
|
||||
Assert.AreEqual(11, descendants.Count());
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Umbraco.Web.Cache
|
||||
: (contentType is IMediaType)
|
||||
? typeof(IMediaType).Name
|
||||
: typeof(IMemberType).Name,
|
||||
DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(),
|
||||
DescendantPayloads = contentType.Descendants(ApplicationContext.Current.Services.ContentTypeService).Select(x => FromContentType(x)).ToArray(),
|
||||
WasDeleted = isDeleted,
|
||||
PropertyRemoved = contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"),
|
||||
AliasChanged = contentType.WasPropertyDirty("Alias"),
|
||||
|
||||
Reference in New Issue
Block a user