Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions src/Umbraco.Core/Services/ContentTypeService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;

Check notice on line 1 in src/Umbraco.Core/Services/ContentTypeService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v17/feature/global-elements)

✅ Getting better: Constructor Over-Injection

ContentTypeService decreases from 12 to 11 arguments, max arguments = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.

Check notice on line 1 in src/Umbraco.Core/Services/ContentTypeService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v17/feature/global-elements)

✅ Getting better: Constructor Over-Injection

ContentTypeService decreases from 13 to 12 arguments, max arguments = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
Expand All @@ -20,12 +20,15 @@
public class ContentTypeService : ContentTypeServiceBase<IContentTypeRepository, IContentType>, IContentTypeService
{
private readonly ITemplateService _templateService;
private readonly IContentService _contentService;
private readonly IElementService _elementService;

public ContentTypeService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IContentService contentService,
IElementService elementService,
IContentTypeRepository repository,
IAuditService auditService,
IDocumentTypeContainerRepository entityContainerRepository,
Expand All @@ -47,7 +50,39 @@
contentTypeFilters)
{
_templateService = templateService;
ContentService = contentService;
_contentService = contentService;
_elementService = elementService;
}

[Obsolete("Use the non-obsolete constructor. Scheduled for removal in Umbraco 19.")]
public ContentTypeService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IContentService contentService,
IContentTypeRepository repository,
IAuditService auditService,
IDocumentTypeContainerRepository entityContainerRepository,
IEntityRepository entityRepository,
IEventAggregator eventAggregator,
IUserIdKeyResolver userIdKeyResolver,
ContentTypeFilterCollection contentTypeFilters,
ITemplateService templateService)
: this(
provider,
loggerFactory,
eventMessagesFactory,
contentService,
StaticServiceProvider.Instance.GetRequiredService<IElementService>(),
repository,
auditService,
entityContainerRepository,
entityRepository,
eventAggregator,
userIdKeyResolver,
contentTypeFilters,
templateService)
{
}

[Obsolete("Use the non-obsolete constructor. Scheduled for removal in Umbraco 19.")]
Expand Down Expand Up @@ -169,14 +204,45 @@
{
}

[Obsolete("Use the non-obsolete constructor instead. Scheduled removal in v19.")]
public ContentTypeService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IContentService contentService,
IElementService elementService,
IContentTypeRepository repository,
IAuditRepository auditRepository,
IAuditService auditService,
IDocumentTypeContainerRepository entityContainerRepository,
IEntityRepository entityRepository,
IEventAggregator eventAggregator,
IUserIdKeyResolver userIdKeyResolver,
ContentTypeFilterCollection contentTypeFilters,
ITemplateService templateService)
: this(
provider,
loggerFactory,
eventMessagesFactory,
contentService,
elementService,
repository,
auditService,
entityContainerRepository,
entityRepository,
eventAggregator,
userIdKeyResolver,
contentTypeFilters,
templateService)
{
}

protected override int[] ReadLockIds => ContentTypeLocks.ReadLockIds;

protected override int[] WriteLockIds => ContentTypeLocks.WriteLockIds;

protected override Guid ContainedObjectType => Constants.ObjectTypes.DocumentType;

Check warning on line 245 in src/Umbraco.Core/Services/ContentTypeService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v17/feature/global-elements)

❌ Getting worse: Code Duplication

introduced similar code in: ContentTypeService,ContentTypeService. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.
private IContentService ContentService { get; }

/// <summary>
/// Gets all property type aliases across content, media and member types.
/// </summary>
Expand Down Expand Up @@ -281,8 +347,9 @@
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
var typeIdsA = typeIds.ToArray();
ContentService.DeleteOfTypes(typeIdsA);
ContentService.DeleteBlueprintsOfTypes(typeIdsA);
_contentService.DeleteOfTypes(typeIdsA);
_contentService.DeleteBlueprintsOfTypes(typeIdsA);
_elementService.DeleteOfTypes(typeIdsA);
scope.Complete();
}
}
Expand Down
45 changes: 45 additions & 0 deletions src/Umbraco.Core/Services/ElementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Scoping;
Expand Down Expand Up @@ -94,6 +95,50 @@ public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportO

#endregion

#region Content Types

/// <inheritdoc/>
public void DeleteOfTypes(IEnumerable<int> contentTypeIds, int userId = Constants.Security.SuperUserId)
{
var changes = new List<TreeChange<IElement>>();
var contentTypeIdsA = contentTypeIds.ToArray();
EventMessages eventMessages = EventMessagesFactory.Get();

using ICoreScope scope = ScopeProvider.CreateCoreScope();
scope.WriteLock(WriteLockIds);

IQuery<IElement> query = Query<IElement>().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
IElement[] elements = _elementRepository.Get(query).ToArray();

if (elements.Length is 0)
{
scope.Complete();
return;
}

if (scope.Notifications.PublishCancelable(new ElementDeletingNotification(elements, eventMessages)))
{
scope.Complete();
return;
}

foreach (IElement element in elements)
{
// delete content
// triggers the deleted event
DeleteLocked(scope, element, eventMessages);
changes.Add(new TreeChange<IElement>(element, TreeChangeTypes.Remove));
}

scope.Notifications.Publish(new ElementTreeChangeNotification(changes, eventMessages));

Audit(AuditType.Delete, userId, Constants.System.Root, $"Delete element of type {string.Join(",", contentTypeIdsA)}");

scope.Complete();
}

#endregion

#region Abstract implementations

protected override UmbracoObjectTypes ContentObjectType => UmbracoObjectTypes.Element;
Expand Down
11 changes: 0 additions & 11 deletions src/Umbraco.Core/Services/IContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,17 +330,6 @@ IContent CreateBlueprintFromContent(IContent blueprint, string name, int userId
/// </remarks>
void DeleteOfType(int documentTypeId, int userId = Constants.Security.SuperUserId);

/// <summary>
/// Deletes all documents of given document types.
/// </summary>
/// <param name="contentTypeIds">The content type identifiers.</param>
/// <param name="userId">The identifier of the user performing the action.</param>
/// <remarks>
/// <para>All non-deleted descendants of the deleted documents are moved to the recycle bin.</para>
/// <para>This operation is potentially dangerous and expensive.</para>
/// </remarks>
void DeleteOfTypes(IEnumerable<int> contentTypeIds, int userId = Constants.Security.SuperUserId);

/// <summary>
/// Deletes versions of a document prior to a given date.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/Umbraco.Core/Services/IPublishableContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ public interface IPublishableContentService<TContent> : IContentServiceBase<TCon
/// </remarks>
OperationResult Delete(TContent content, int userId = Constants.Security.SuperUserId);

/// <summary>
/// Deletes all content of given types.
/// </summary>
/// <param name="contentTypeIds">The content type identifiers.</param>
/// <param name="userId">The identifier of the user performing the action.</param>
/// <remarks>
/// <para>All non-deleted descendants of the deleted content is moved to the recycle bin.</para>
/// <para>This operation is potentially dangerous and expensive.</para>
/// </remarks>
void DeleteOfTypes(IEnumerable<int> contentTypeIds, int userId = Constants.Security.SuperUserId);

/// <summary>
/// Gets publish/unpublish schedule for a content node.
/// </summary>
Expand Down
40 changes: 40 additions & 0 deletions src/Umbraco.Infrastructure/Persistence/Mappers/ElementMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;

namespace Umbraco.Cms.Infrastructure.Persistence.Mappers;

/// <summary>
/// Represents a <see cref="Element" /> to DTO mapper used to translate the properties of the public api
/// implementation to that of the database's DTO as sql: [tableName].[columnName].
/// </summary>
[MapperFor(typeof(Element))]
[MapperFor(typeof(IElement))]
public sealed class ElementMapper : BaseMapper
{
public ElementMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{
}

protected override void DefineMaps()
{
DefineMap<Element, NodeDto>(nameof(Element.Id), nameof(NodeDto.NodeId));
DefineMap<Element, NodeDto>(nameof(Element.Key), nameof(NodeDto.UniqueId));

DefineMap<Element, ContentVersionDto>(nameof(Element.VersionId), nameof(ContentVersionDto.Id));
DefineMap<Element, ContentVersionDto>(nameof(Element.Name), nameof(ContentVersionDto.Text));

DefineMap<Element, NodeDto>(nameof(Element.ParentId), nameof(NodeDto.ParentId));
DefineMap<Element, NodeDto>(nameof(Element.Level), nameof(NodeDto.Level));
DefineMap<Element, NodeDto>(nameof(Element.Path), nameof(NodeDto.Path));
DefineMap<Element, NodeDto>(nameof(Element.SortOrder), nameof(NodeDto.SortOrder));
DefineMap<Element, NodeDto>(nameof(Element.Trashed), nameof(NodeDto.Trashed));

DefineMap<Element, NodeDto>(nameof(Element.CreateDate), nameof(NodeDto.CreateDate));
DefineMap<Element, NodeDto>(nameof(Element.CreatorId), nameof(NodeDto.UserId));
DefineMap<Element, ContentDto>(nameof(Element.ContentTypeId), nameof(ContentDto.ContentTypeId));

DefineMap<Element, ContentVersionDto>(nameof(Element.UpdateDate), nameof(ContentVersionDto.VersionDate));
DefineMap<Element, ElementDto>(nameof(Element.Published), nameof(ElementDto.Published));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public MapperCollectionBuilder AddCoreMappers()
Add<AccessMapper>();
Add<AuditItemMapper>();
Add<ContentMapper>();
Add<ElementMapper>();
Add<ContentTypeMapper>();
Add<SimpleContentTypeMapper>();
Add<DataTypeMapper>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Services.OperationStatus;

namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
Expand Down Expand Up @@ -43,4 +45,40 @@ public async Task Cannot_Delete_Non_Existing()
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentEditingOperationStatus.NotFound, result.Status);
}

[Test]
public async Task Deleting_Element_Type_Deletes_All_Elements_Of_That_Type()
{
var elementType = await CreateInvariantElementType();

for (var i = 0; i < 10; i++)
{
var key = Guid.NewGuid();
await ElementEditingService.CreateAsync(
new ElementCreateModel
{
Key = key,
ContentTypeKey = elementType.Key,
ParentKey = null,
Variants = [new() { Name = $"Name {i}" }],
},
Constants.Security.SuperUserKey);

if (i % 2 == 0)
{
// move half of the created elements to trash, to ensure that also trashed elements are deleted
// when deleting the element type
await ElementEditingService.MoveToRecycleBinAsync(key, Constants.Security.SuperUserKey);
}
}

Assert.AreEqual(5, EntityService.GetRootEntities(UmbracoObjectTypes.Element).Count());
Assert.AreEqual(5, EntityService.GetPagedTrashedChildren(Constants.System.RecycleBinElementKey, UmbracoObjectTypes.Element, 0, 100, out _).Count());

var result = await ContentTypeService.DeleteAsync(elementType.Key, Constants.Security.SuperUserKey);
Assert.AreEqual(ContentTypeOperationStatus.Success, result);

Assert.AreEqual(0, EntityService.GetRootEntities(UmbracoObjectTypes.Element).Count());
Assert.AreEqual(0, EntityService.GetPagedTrashedChildren(Constants.System.RecycleBinElementKey, UmbracoObjectTypes.Element, 0, 100, out _).Count());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Tests.UnitTests.TestHelpers;

namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers;

[TestFixture]
public class ElementMapperTest
{
[Test]
public void Can_Map_Id_Property()
{
var column = new ElementMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map(nameof(Element.Id));
Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Node}].[id]"));
}

[Test]
public void Can_Map_Trashed_Property()
{
var column =
new ElementMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map(nameof(Element.Trashed));
Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Node}].[trashed]"));
}

[Test]
public void Can_Map_Published_Property()
{
var column =
new ElementMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map(nameof(Element.Published));
Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.Element}].[published]"));
}

[Test]
public void Can_Map_Version_Property()
{
var column =
new ElementMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map(nameof(Element.VersionId));
Assert.That(column, Is.EqualTo($"[{Constants.DatabaseSchema.Tables.ContentVersion}].[id]"));
}
}