diff --git a/SECURITY.md b/SECURITY.md index a6c88ae..f77cc60 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,6 +11,7 @@ The following table outlines the versions of the project that are currently supp |-----------------|-----------------|----------------------|-------------| | [13.x](https://github.com/jcdcdev/jcdcdev.Umbraco.ReadingTime/tree/v13) | 13 | 2025-12-14 | 2026-12-14 | | [17.x](https://github.com/jcdcdev/jcdcdev.Umbraco.ReadingTime/tree/v17) | 17 | 2027-11-27 | 2028-11-27 | +| [18.x](https://github.com/jcdcdev/jcdcdev.Umbraco.ReadingTime/tree/v18) | 18 | 2027-03-25 | 2027-06-25 | ## Unsupported Versions diff --git a/src/jcdcdev.Umbraco.ReadingTime/Core/IReadingTimeService.cs b/src/jcdcdev.Umbraco.ReadingTime/Core/IReadingTimeService.cs index d920fd9..b91097f 100644 --- a/src/jcdcdev.Umbraco.ReadingTime/Core/IReadingTimeService.cs +++ b/src/jcdcdev.Umbraco.ReadingTime/Core/IReadingTimeService.cs @@ -9,6 +9,7 @@ public interface IReadingTimeService Task ScanAll(); Task Process(IContent item); Task DeleteAsync(Guid key); + Task DeleteAsync(IEnumerable keys); Task GetAsync(Guid key, Guid dataTypeKey); Task GetAsync(Guid key, int dataTypeId); } diff --git a/src/jcdcdev.Umbraco.ReadingTime/Core/ReadingTimeNotificationHandler.cs b/src/jcdcdev.Umbraco.ReadingTime/Core/ReadingTimeNotificationHandler.cs index 64b311c..cdc0d20 100644 --- a/src/jcdcdev.Umbraco.ReadingTime/Core/ReadingTimeNotificationHandler.cs +++ b/src/jcdcdev.Umbraco.ReadingTime/Core/ReadingTimeNotificationHandler.cs @@ -4,7 +4,6 @@ using jcdcdev.Umbraco.ReadingTime.Core.PropertyEditors; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -18,18 +17,41 @@ public class ReadingTimeNotificationHandler : { private readonly ILocalizedTextService _localizedTextService; private readonly IReadingTimeService _readingTimeService; + private readonly IContentService _contentService; - public ReadingTimeNotificationHandler(IReadingTimeService readingTimeService, ILocalizedTextService localizedTextService) + private const int ChildrenPageSize = 1000; + + public ReadingTimeNotificationHandler(IReadingTimeService readingTimeService, ILocalizedTextService localizedTextService, IContentService contentService) { _readingTimeService = readingTimeService; _localizedTextService = localizedTextService; + _contentService = contentService; } + // Both ContentDeletingNotification and ContentEmptyingRecycleBinNotification are called during emptying a global Recycle Bin empty operation + // Since individual deletes from within the Bin trigger just the ContentDeletingNotification and not the latter, this should cover both use cases public async Task HandleAsync(ContentDeletingNotification notification, CancellationToken cancellationToken) { - foreach (var content in notification.DeletedEntities) + foreach (var item in notification.DeletedEntities) { - await _readingTimeService.DeleteAsync(content.Key); + var toDelete = new List { item.Key }; + var pageIndex = 0; + + List pagedDescendants; + + do + { + pagedDescendants = _contentService + .GetPagedDescendants(item.Id, pageIndex, ChildrenPageSize, out _) + .Select(x => x.Key) + .ToList(); + + toDelete.AddRange(pagedDescendants); + + pageIndex++; + } while (pagedDescendants.Count == ChildrenPageSize); + + await _readingTimeService.DeleteAsync(toDelete); } } diff --git a/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/IReadingTimeRepository.cs b/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/IReadingTimeRepository.cs index 0495ef9..001d856 100644 --- a/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/IReadingTimeRepository.cs +++ b/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/IReadingTimeRepository.cs @@ -6,6 +6,7 @@ namespace jcdcdev.Umbraco.ReadingTime.Infrastructure.Persistence; public interface IReadingTimeRepository { Task DeleteAsync(Guid key); + Task DeleteAsync(IEnumerable key); Task GetOrCreate(Guid key, IDataType dataType); Task PersistAsync(ReadingTimeDto dto); Task Get(Guid key, int dataTypeId); diff --git a/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/ReadingTimeRepository.cs b/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/ReadingTimeRepository.cs index 3704ff4..84a28a3 100644 --- a/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/ReadingTimeRepository.cs +++ b/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/Persistence/ReadingTimeRepository.cs @@ -31,6 +31,22 @@ public Task DeleteAsync(Guid key) return Task.FromResult(data); } + public Task DeleteAsync(IEnumerable keys) + { + using var scope = _scopeProvider.CreateScope(); + + var sql = scope.SqlContext + .Sql() + .Delete() + .WhereIn(x => x.Key, keys); + + var data = scope.Database.Execute(sql); + + scope.Complete(); + + return Task.FromResult(data); + } + public async Task GetOrCreate(Guid key, IDataType dataType) { var dto = await Get(key, dataType.Id); diff --git a/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/ReadingTimeService.cs b/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/ReadingTimeService.cs index 890596c..040da83 100644 --- a/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/ReadingTimeService.cs +++ b/src/jcdcdev.Umbraco.ReadingTime/Infrastructure/ReadingTimeService.cs @@ -42,6 +42,12 @@ public async Task DeleteAsync(Guid key) return await _readingTimeRepository.DeleteAsync(key); } + public async Task DeleteAsync(IEnumerable keys) + { + _logger.LogDebug("Deleting reading time for {Keys}", keys); + return await _readingTimeRepository.DeleteAsync(keys); + } + public async Task ScanTree(int homeId) { var content = _contentService.GetById(homeId);