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:
Shannon
2017-02-08 21:59:16 +11:00
parent 351796fd6b
commit fb02a5b06d
13 changed files with 460 additions and 154 deletions
@@ -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":
@@ -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).
+116
View File
@@ -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>
+51 -56
View File
@@ -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>
+135 -17
View File
@@ -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
}
}
+2 -1
View File
@@ -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"),