Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce INavigationService for in-memory navigation data #16818

Merged
merged 107 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 95 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
8200944
Tests
elit0451 Jul 15, 2024
14b02a5
Remove props and use local vars
elit0451 Jul 15, 2024
e3135e7
Adding preliminary navigation service and content implementation
elit0451 Jul 24, 2024
dc785c0
Adding preliminary unit tests
elit0451 Jul 24, 2024
2f77612
Change from async methods
elit0451 Jul 24, 2024
b435de3
Refactor GetParentKey to TryGetParentKey
elit0451 Jul 24, 2024
45c800a
Refactor GetChildrenKeys to TryGetChildrenKeys
elit0451 Jul 24, 2024
d55586c
Refactor GetDescendantsKeys to TryGetDescendantsKeys
elit0451 Jul 24, 2024
d347a00
Refactor GetAncestorsKeys to TryGetAncestorsKeys
elit0451 Jul 24, 2024
1cade45
Refactor GetSiblingsKeys to TryGetSiblingsKeys
elit0451 Jul 24, 2024
2f646c3
Refactor TryGetChildrenKeys
elit0451 Jul 24, 2024
75d717c
Initial integration tests
elit0451 Jul 25, 2024
9a4565e
Use ContentEditingService instead of ContentService
elit0451 Jul 25, 2024
1b3d27d
Remove INavigationService.Copy implementation and unit tests
elit0451 Jul 25, 2024
fa89e23
Rename var
elit0451 Jul 25, 2024
6ffe6d5
Adding clarification
elit0451 Jul 25, 2024
083d522
Initial ContentNavigationRepository
elit0451 Jul 25, 2024
e949741
Initial NavigationFactory
elit0451 Jul 25, 2024
354834a
Remove filtering from factory
elit0451 Jul 25, 2024
f99a757
NavigationRepository and implementation
elit0451 Jul 25, 2024
e8a2169
InitializationService responsible for seeding the in-memory structure
elit0451 Jul 25, 2024
b416a5d
Register repository and service
elit0451 Jul 25, 2024
ab285ee
Adding NavigationDto and NavigationNode
elit0451 Jul 25, 2024
8d66ea7
Adding INavigationService dependency and Enlist updating navigation s…
elit0451 Jul 25, 2024
52f6f75
Documentation
elit0451 Jul 25, 2024
9142148
Adding tests for removing descendants as well
elit0451 Jul 25, 2024
0cd86c5
Changed to ConcurrentDictionary
elit0451 Jul 25, 2024
c68c130
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
elit0451 Jul 25, 2024
46868fb
Remove keys comments for tests
elit0451 Jul 25, 2024
b8071bd
Adding documentation
elit0451 Jul 25, 2024
4faacfd
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
elit0451 Jul 26, 2024
c51b453
Forgotten ConcurrentDictionary change
elit0451 Jul 26, 2024
17ebdb5
Merge remote-tracking branch 'origin/v14/feature/navigation' into v14…
elit0451 Jul 26, 2024
2b562c6
Isolating the operations on the model
elit0451 Aug 7, 2024
01faa03
Splitting the INavigationService to separate the querying from the ma…
elit0451 Aug 7, 2024
3808cac
Introducing specific navigation services for document, document recyc…
elit0451 Aug 7, 2024
d8d345d
Making ContentNavigationService into a base as the functionality will…
elit0451 Aug 7, 2024
e59c35d
Adding the implementations of document, document recycle bin, media a…
elit0451 Aug 7, 2024
c323d75
Fixing comments
elit0451 Aug 7, 2024
4916a30
Initializing all 4 collections
elit0451 Aug 7, 2024
3eb5d86
Adapting the navigation unit tests to the base now
elit0451 Aug 7, 2024
0dfe59a
Adapting integration tests to specific navigation service
elit0451 Aug 7, 2024
1ee8866
Adding test for rebuilding the structure
elit0451 Aug 7, 2024
df4d7ca
Adding implementation for Adding and Getting a node - needed for movi…
elit0451 Aug 7, 2024
733938f
Updating the document navigation structure from the ContentService
elit0451 Aug 7, 2024
ef48b1d
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
elit0451 Aug 7, 2024
3bd68db
Merge remote-tracking branch 'origin/v14/feature/navigation' into v14…
elit0451 Aug 7, 2024
7dff56a
Fix typo
elit0451 Aug 7, 2024
daea8c4
Adding trashed items implementation in base - currently managing 2 st…
elit0451 Aug 9, 2024
0eed393
Removing no longer relevant GetNavigationNode and AddNavigationNode
elit0451 Aug 9, 2024
7a1b9f6
Fix removing parent when child is removed supporting methods
elit0451 Aug 9, 2024
741b96c
Added restoring functionality
elit0451 Aug 9, 2024
aad1d79
Adding Bin functionality to DocumentNavigationService
elit0451 Aug 9, 2024
4120289
Removing Move signature from IDocumentNavigationService
elit0451 Aug 9, 2024
77a23db
Adding RecycleBin query and management services
elit0451 Aug 9, 2024
d85afff
Re-adding Move and removing GetNavigationNode and AddNavigationNode s…
elit0451 Aug 9, 2024
4851446
Rebuilding bin structure using _documentNavigationService, instead of…
elit0451 Aug 9, 2024
da082bf
Fixing test name
elit0451 Aug 9, 2024
81bb6d6
Adding more tests for remove
elit0451 Aug 9, 2024
4093903
Adding tests for restore and removing ones for GetNavigationNode and …
elit0451 Aug 9, 2024
4ce9f95
Remove comments
elit0451 Aug 9, 2024
ea0aea9
Removing document and media RecycleBinNavigationService and their int…
elit0451 Aug 9, 2024
3391b3f
Adding media rebuild bin
elit0451 Aug 9, 2024
b2493a6
Fixing initialization with correct interfaces
elit0451 Aug 9, 2024
3ad9ff0
Removing RecycleBinNavigationServices' registration
elit0451 Aug 9, 2024
e582e5f
Remove IDocumentRecycleBinNavigationService dependency
elit0451 Aug 9, 2024
602480d
Updating in-memory nav structure when content updates happen
elit0451 Aug 12, 2024
da0f2b6
Adding the rest of the integration tests
elit0451 Aug 12, 2024
0f06dd8
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
elit0451 Aug 12, 2024
bc8b102
Clean up IMediaNavigationService
elit0451 Aug 12, 2024
995fb5b
Fix comments
elit0451 Aug 12, 2024
4fc0150
Remove CustomTestSetup in integration tests as the structure is updat…
elit0451 Aug 12, 2024
34df56f
Adding and fixing comments
elit0451 Aug 23, 2024
312d0bd
Making RebuildBinAsync abstract as well
elit0451 Aug 23, 2024
cd74f1d
Adding DocumentNavigationServiceTestsBase
elit0451 Aug 23, 2024
99cbd10
Splitting DocumentNavigationServiceTests into partial test classes
elit0451 Aug 23, 2024
33678a8
Cleaning up DocumentNavigationServiceTests since tests have been move…
elit0451 Aug 23, 2024
b552b50
Reuse a method for creating content in tests
elit0451 Aug 26, 2024
83db666
Change type in test base
elit0451 Aug 26, 2024
4d0bbd2
Adding navigation structure updates in media service
elit0451 Aug 26, 2024
5ec8ecc
Adding MediaNavigationServiceTestsBase
elit0451 Aug 26, 2024
19404b3
Adding integration tests for media nav str
elit0451 Aug 26, 2024
fc338d5
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
elit0451 Aug 26, 2024
907bce6
Remove services as we will have more concrete ones
elit0451 Aug 26, 2024
6577bbc
Add document and media IXNavigationQueryService and IXNavigationManag…
elit0451 Aug 26, 2024
2aa39cb
Inject ManagementService in ContentService.cs and MediaService.cs
elit0451 Aug 26, 2024
d361cce
Change implementation to implement the new services + registration
elit0451 Aug 26, 2024
2c6f9fa
Make classes sealed
elit0451 Aug 26, 2024
695e5ad
Inject correct services in InitializationService
elit0451 Aug 26, 2024
7c5e71e
Using the right services in integration tests
elit0451 Aug 26, 2024
08beb05
Adding comments
elit0451 Aug 26, 2024
bb14942
Removing bin interfaces from main navigation ones
elit0451 Aug 26, 2024
7215edf
Rename Remove to MoveToBin
elit0451 Aug 26, 2024
4321ed4
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
elit0451 Aug 26, 2024
6851113
V14 QA added block list editor tests (#16862)
andr317c Aug 29, 2024
0d00fd1
Merge remote-tracking branch 'origin/v14/dev' into v14/feature/naviga…
bergmania Aug 29, 2024
c277005
improve missingProperties data returned for missing propertie values …
Migaroez Aug 29, 2024
590b281
update backoffice submodule
iOvergaard Aug 29, 2024
9321774
Merge branch 'v14/dev' of github.com:umbraco/Umbraco-CMS into v14/dev
iOvergaard Aug 29, 2024
21d2bb6
Rename initialization service to initialization hosted service
elit0451 Sep 2, 2024
b3f32aa
Refactor repository to return a collection
elit0451 Sep 2, 2024
2694f3a
Add interface for the NavigationDto
elit0451 Sep 2, 2024
6d5a0f9
Add constants to bind property names between DTOs
elit0451 Sep 2, 2024
d5c3dc2
Move factory and fix input type
elit0451 Sep 2, 2024
7bde9be
Use constants for column names
elit0451 Sep 2, 2024
1378307
Use factory from base
elit0451 Sep 2, 2024
ab32ba6
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
elit0451 Sep 2, 2024
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
10 changes: 8 additions & 2 deletions src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services.FileSystem;
using Umbraco.Cms.Core.Services.ImportExport;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Services.Querying.RecycleBin;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Core.Telemetry;
Expand Down Expand Up @@ -338,20 +339,25 @@
factory.GetRequiredService<ICoreScopeProvider>(),
factory.GetRequiredService<ILoggerFactory>(),
factory.GetRequiredService<IEventMessagesFactory>(),
factory.GetRequiredService<IExternalLoginWithKeyRepository>()
));
factory.GetRequiredService<IExternalLoginWithKeyRepository>()));
Services.AddUnique<ILogViewerService, LogViewerService>();
Services.AddUnique<IExternalLoginWithKeyService>(factory => factory.GetRequiredService<ExternalLoginService>());
Services.AddUnique<ILocalizedTextService>(factory => new LocalizedTextService(
factory.GetRequiredService<Lazy<LocalizedTextServiceFileSources>>(),
factory.GetRequiredService<ILogger<LocalizedTextService>>()));

Services.AddUnique<IEntityXmlSerializer, EntityXmlSerializer>();

Services.AddSingleton<ConflictingPackageData>();
Services.AddSingleton<CompiledPackageXmlParser>();
Services.AddUnique<IPreviewTokenGenerator, NoopPreviewTokenGenerator>();
Services.AddUnique<IPreviewService, PreviewService>();
Services.AddUnique<DocumentNavigationService, DocumentNavigationService>();
Services.AddUnique<IDocumentNavigationQueryService>(x => x.GetRequiredService<DocumentNavigationService>());
Services.AddUnique<IDocumentNavigationManagementService>(x => x.GetRequiredService<DocumentNavigationService>());
Services.AddUnique<MediaNavigationService, MediaNavigationService>();
Services.AddUnique<IMediaNavigationQueryService>(x => x.GetRequiredService<MediaNavigationService>());
Services.AddUnique<IMediaNavigationManagementService>(x => x.GetRequiredService<MediaNavigationService>());

Check warning on line 360 in src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ Getting worse: Large Method

AddCoreServices increases from 188 to 193 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.

// Register a noop IHtmlSanitizer & IMarkdownSanitizer to be replaced
Services.AddUnique<IHtmlSanitizer, NoopHtmlSanitizer>();
Expand Down
30 changes: 30 additions & 0 deletions src/Umbraco.Core/Models/Navigation/NavigationNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Umbraco.Cms.Core.Models.Navigation;

public sealed class NavigationNode
{
private List<NavigationNode> _children;

public Guid Key { get; private set; }

public NavigationNode? Parent { get; private set; }

public IEnumerable<NavigationNode> Children => _children.AsEnumerable();

public NavigationNode(Guid key)
{
Key = key;
_children = new List<NavigationNode>();
}

public void AddChild(NavigationNode child)
{
child.Parent = this;
_children.Add(child);
}

public void RemoveChild(NavigationNode child)
{
_children.Remove(child);
child.Parent = null;
}
}
21 changes: 21 additions & 0 deletions src/Umbraco.Core/Persistence/Repositories/INavigationRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Collections.Concurrent;
using Umbraco.Cms.Core.Models.Navigation;

namespace Umbraco.Cms.Core.Persistence.Repositories;

public interface INavigationRepository
{
/// <summary>
/// Retrieves a dictionary of content nodes based on the object type key.
/// </summary>
/// <param name="objectTypeKey">The unique identifier for the object type.</param>
/// <returns>A dictionary of navigation nodes where the key is the unique identifier of the node.</returns>
public ConcurrentDictionary<Guid, NavigationNode> GetContentNodesByObjectType(Guid objectTypeKey);

/// <summary>
/// Retrieves a dictionary of trashed content nodes based on the object type key.
/// </summary>
/// <param name="objectTypeKey">The unique identifier for the object type.</param>
/// <returns>A dictionary of navigation nodes where the key is the unique identifier of the node.</returns>
public ConcurrentDictionary<Guid, NavigationNode> GetTrashedContentNodesByObjectType(Guid objectTypeKey);
}
175 changes: 160 additions & 15 deletions src/Umbraco.Core/Services/ContentService.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/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Lines of Code in a Single File

The lines of code increases from 2194 to 2302, improve code health by reducing it to 1000. The number of Lines of Code in a single file. More Lines of Code lowers the code health.

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

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Number of Functions in a Single Module

The number of functions increases from 107 to 109, threshold = 75. This file contains too many functions. Beyond a certain threshold, more functions lower the code health.

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

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Overall Code Complexity

The mean cyclomatic complexity increases from 4.19 to 4.19, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.

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

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ Getting better: Primitive Obsession

The ratio of primitive types in function arguments decreases from 52.54% to 50.16%, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
Expand All @@ -12,6 +12,7 @@
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;

Expand All @@ -33,39 +34,75 @@
private readonly IShortStringHelper _shortStringHelper;
private readonly ICultureImpactFactory _cultureImpactFactory;
private readonly IUserIdKeyResolver _userIdKeyResolver;
private readonly IDocumentNavigationManagementService _documentNavigationManagementService;
private IQuery<IContent>? _queryNotTrashed;

#region Constructors

public ContentService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IDocumentRepository documentRepository,
IEntityRepository entityRepository,
IAuditRepository auditRepository,
IContentTypeRepository contentTypeRepository,
IDocumentBlueprintRepository documentBlueprintRepository,
ILanguageRepository languageRepository,
Lazy<IPropertyValidationService> propertyValidationService,
IShortStringHelper shortStringHelper,
ICultureImpactFactory cultureImpactFactory,
IUserIdKeyResolver userIdKeyResolver)
: base(provider, loggerFactory, eventMessagesFactory)
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IDocumentRepository documentRepository,
IEntityRepository entityRepository,
IAuditRepository auditRepository,
IContentTypeRepository contentTypeRepository,
IDocumentBlueprintRepository documentBlueprintRepository,
ILanguageRepository languageRepository,
Lazy<IPropertyValidationService> propertyValidationService,
IShortStringHelper shortStringHelper,
ICultureImpactFactory cultureImpactFactory,
IUserIdKeyResolver userIdKeyResolver,
IDocumentNavigationManagementService documentNavigationManagementService)
: base(provider, loggerFactory, eventMessagesFactory)
{
_documentRepository = documentRepository;
_entityRepository = entityRepository;
_auditRepository = auditRepository;
_contentTypeRepository = contentTypeRepository;
_documentBlueprintRepository = documentBlueprintRepository;
_languageRepository = languageRepository;
_propertyValidationService = propertyValidationService;
_shortStringHelper = shortStringHelper;
_cultureImpactFactory = cultureImpactFactory;
_userIdKeyResolver = userIdKeyResolver;
_documentNavigationManagementService = documentNavigationManagementService;

Check notice on line 69 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Constructor Over-Injection

ContentService increases from 13 to 14 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
_logger = loggerFactory.CreateLogger<ContentService>();
}

[Obsolete("Use non-obsolete constructor. Scheduled for removal in V16.")]
public ContentService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IDocumentRepository documentRepository,
IEntityRepository entityRepository,
IAuditRepository auditRepository,
IContentTypeRepository contentTypeRepository,
IDocumentBlueprintRepository documentBlueprintRepository,
ILanguageRepository languageRepository,
Lazy<IPropertyValidationService> propertyValidationService,
IShortStringHelper shortStringHelper,
ICultureImpactFactory cultureImpactFactory,
IUserIdKeyResolver userIdKeyResolver)
: this(
provider,
loggerFactory,
eventMessagesFactory,
documentRepository,
entityRepository,
auditRepository,
contentTypeRepository,
documentBlueprintRepository,
languageRepository,
propertyValidationService,
shortStringHelper,
cultureImpactFactory,
userIdKeyResolver,
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationManagementService>())
{
}

[Obsolete("Use constructor that takes IUserIdKeyResolver as a parameter, scheduled for removal in V15")]
public ContentService(
ICoreScopeProvider provider,
Expand Down Expand Up @@ -93,7 +130,8 @@
propertyValidationService,
shortStringHelper,
cultureImpactFactory,
StaticServiceProvider.Instance.GetRequiredService<IUserIdKeyResolver>())
StaticServiceProvider.Instance.GetRequiredService<IUserIdKeyResolver>(),
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationManagementService>())

Check notice on line 134 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Constructor Over-Injection

ContentService increases from 12 to 13 arguments, threshold = 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 134 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ New issue: Constructor Over-Injection

ContentService has 12 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
{
}

Expand Down Expand Up @@ -1034,6 +1072,11 @@
// have always changed if it's been saved in the back office but that's not really fail safe.
_documentRepository.Save(content);

// Updates in-memory navigation structure - we only handle new items, other updates are not a concern
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.Save-with-contentSchedule",
() => _documentNavigationManagementService.Add(content.Key, GetParent(content)?.Key));

if (contentSchedule != null)
{
_documentRepository.PersistContentSchedule(content, contentSchedule);
Expand Down Expand Up @@ -1097,6 +1140,11 @@
content.WriterId = userId;

_documentRepository.Save(content);

// Updates in-memory navigation structure - we only handle new items, other updates are not a concern
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.Save",
() => _documentNavigationManagementService.Add(content.Key, GetParent(content)?.Key));
}

scope.Notifications.Publish(
Expand Down Expand Up @@ -2288,6 +2336,26 @@
}

DoDelete(content);

if (content.Trashed)
{
// Updates in-memory navigation structure for recycle bin items
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.DeleteLocked-trashed",
() => _documentNavigationManagementService.RemoveFromBin(content.Key));
}
else
{
// Updates in-memory navigation structure for both documents and recycle bin items
// as the item needs to be deleted whether it is in the recycle bin or not
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.DeleteLocked",
() =>
{
_documentNavigationManagementService.MoveToBin(content.Key);
_documentNavigationManagementService.RemoveFromBin(content.Key);
});
}
}

// TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way
Expand Down Expand Up @@ -2512,54 +2580,83 @@
// trash indicates whether we are trashing, un-trashing, or not changing anything
private void PerformMoveLocked(IContent content, int parentId, IContent? parent, int userId, ICollection<(IContent, string)> moves, bool? trash)
{
// Needed to update the in-memory navigation structure
var cameFromRecycleBin = content.ParentId == Constants.System.RecycleBinContent;
content.WriterId = userId;
content.ParentId = parentId;

// get the level delta (old pos to new pos)
// note that recycle bin (id:-20) level is 0!
var levelDelta = 1 - content.Level + (parent?.Level ?? 0);

var paths = new Dictionary<int, string>();

moves.Add((content, content.Path)); // capture original path

// need to store the original path to lookup descendants based on it below
var originalPath = content.Path;

// these will be updated by the repo because we changed parentId
// content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id;
// content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId);
// content.Level += levelDelta;
PerformMoveContentLocked(content, userId, trash);

// if uow is not immediate, content.Path will be updated only when the UOW commits,
// and because we want it now, we have to calculate it by ourselves
// paths[content.Id] = content.Path;
paths[content.Id] =
(parent == null
? parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString
: parent.Path) + "," + content.Id;

const int pageSize = 500;
IQuery<IContent>? query = GetPagedDescendantQuery(originalPath);
long total;
do
{
// We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
IEnumerable<IContent> descendants =
GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path"));

foreach (IContent descendant in descendants)
{
moves.Add((descendant, descendant.Path)); // capture original path

// update path and level since we do not update parentId
descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
descendant.Level += levelDelta;
PerformMoveContentLocked(descendant, userId, trash);
}
}
while (total > pageSize);

if (parentId == Constants.System.RecycleBinContent)
{
// Updates in-memory navigation structure for both document items and recycle bin items
// as we are moving to recycle bin
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.PerformMoveLocked-to-recycle-bin",
() => _documentNavigationManagementService.MoveToBin(content.Key));
}
else
{
if (cameFromRecycleBin)
{
// Updates in-memory navigation structure for both document items and recycle bin items
// as we are restoring from recycle bin
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.PerformMoveLocked-restore",
() => _documentNavigationManagementService.RestoreFromBin(content.Key, parent?.Key));
}
else
{
// Updates in-memory navigation structure
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.PerformMoveLocked",
() => _documentNavigationManagementService.Move(content.Key, parent?.Key));
}
}

Check warning on line 2659 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ New issue: Bumpy Road Ahead

PerformMoveLocked has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.
}

private void PerformMoveContentLocked(IContent content, int userId, bool? trash)
Expand Down Expand Up @@ -2663,99 +2760,122 @@
{
EventMessages eventMessages = EventMessagesFactory.Get();

// keep track of updates (copied item key and parent key) for the in-memory navigation structure
var navigationUpdates = new List<Tuple<Guid, Guid?>>();

IContent copy = content.DeepCloneWithResetIdentities();
copy.ParentId = parentId;

using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
// FIXME: Pass parent key in constructor too when proper Copy method is implemented
if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, eventMessages)))
{
scope.Complete();
return null;
}

// note - relateToOriginal is not managed here,
// it's just part of the Copied event args so the RelateOnCopyHandler knows what to do
// meaning that the event has to trigger for every copied content including descendants
var copies = new List<Tuple<IContent, IContent>>();

scope.WriteLock(Constants.Locks.ContentTree);

// a copy is not published (but not really unpublishing either)
// update the create author and last edit author
if (copy.Published)
{
copy.Published = false;
}

copy.CreatorId = userId;
copy.WriterId = userId;

// get the current permissions, if there are any explicit ones they need to be copied
EntityPermissionCollection currentPermissions = GetPermissions(content);
currentPermissions.RemoveWhere(p => p.IsDefaultPermissions);

// save and flush because we need the ID for the recursive Copying events
_documentRepository.Save(copy);

// store navigation update information for copied item
navigationUpdates.Add(Tuple.Create(copy.Key, GetParent(copy)?.Key));

// add permissions
if (currentPermissions.Count > 0)
{
var permissionSet = new ContentPermissionSet(copy, currentPermissions);
_documentRepository.AddOrUpdatePermissions(permissionSet);
}

// keep track of copies
copies.Add(Tuple.Create(content, copy));
var idmap = new Dictionary<int, int> { [content.Id] = copy.Id };

// process descendants
if (recursive)
{
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while (page * pageSize < total)
{
IEnumerable<IContent> descendants =
GetPagedDescendants(content.Id, page++, pageSize, out total);
foreach (IContent descendant in descendants)
{
// if parent has not been copied, skip, else gets its copy id
if (idmap.TryGetValue(descendant.ParentId, out parentId) == false)
{
continue;
}

IContent descendantCopy = descendant.DeepCloneWithResetIdentities();
descendantCopy.ParentId = parentId;

// FIXME: Pass parent key in constructor too when proper Copy method is implemented
if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, eventMessages)))
{
continue;
}

// a copy is not published (but not really unpublishing either)
// update the create author and last edit author
if (descendantCopy.Published)
{
descendantCopy.Published = false;
}

descendantCopy.CreatorId = userId;
descendantCopy.WriterId = userId;

// save and flush (see above)
_documentRepository.Save(descendantCopy);

// store navigation update information for descendants
navigationUpdates.Add(Tuple.Create(descendantCopy.Key, GetParent(descendantCopy)?.Key));

copies.Add(Tuple.Create(descendant, descendantCopy));
idmap[descendant.Id] = descendantCopy.Id;
}
}
}

if (navigationUpdates.Count > 0)
{
// Updates in-memory navigation structure
UpdateInMemoryNavigationStructure(
"Umbraco.Cms.Core.Services.ContentService.Copy",
() =>
{
foreach (Tuple<Guid, Guid?> update in navigationUpdates)
{
_documentNavigationManagementService.Add(update.Item1, update.Item2);
}
});
}

Check warning on line 2878 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ New issue: Bumpy Road Ahead

Copy has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check warning on line 2878 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ Getting worse: Complex Method

Copy increases in cyclomatic complexity from 13 to 15, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
// not handling tags here, because
// - tags should be handled by the content repository
// - a copy is unpublished and therefore has no impact on tags in DB
Expand Down Expand Up @@ -3697,4 +3817,29 @@
DeleteBlueprintsOfTypes(new[] { contentTypeId }, userId);

#endregion

/// <summary>
/// Enlists an action in the current scope context to update the in-memory navigation structure
/// when the scope completes successfully.
/// </summary>
/// <param name="enlistingActionKey">The unique key identifying the action to be enlisted.</param>
/// <param name="updateNavigation">The action to be performed for updating the in-memory navigation structure.</param>
/// <exception cref="NullReferenceException">Thrown when the scope context is null and therefore cannot be used.</exception>
private void UpdateInMemoryNavigationStructure(string enlistingActionKey, Action updateNavigation)
{
IScopeContext? scopeContext = ScopeProvider.Context;

if (scopeContext is null)
{
throw new NullReferenceException($"The {nameof(scopeContext)} is null and cannot be used.");
}

scopeContext.Enlist(enlistingActionKey, completed =>
{
if (completed)
{
updateNavigation();
}
});
}
}
Loading
Loading