Merge branch 'v15/dev' into v16/merge-from-15

# Conflicts:
#	src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs
#	src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
#	src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs
#	src/Umbraco.Core/Services/ContentEditingService.cs
#	src/Umbraco.Core/Services/DataTypeService.cs
#	src/Umbraco.Core/Services/IContentEditingService.cs
#	src/Umbraco.Core/Services/IDataTypeService.cs
#	src/Umbraco.Core/Services/ITrackedReferencesService.cs
#	src/Umbraco.Core/Services/RelationService.cs
#	src/Umbraco.Core/Services/TrackedReferencesService.cs
#	src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs
#	src/Umbraco.Infrastructure/Examine/DeliveryApiIndexingHandler.cs
#	src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
#	src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
#	src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts
#	src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.controller.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.interface.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/route/router-slot.element.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/model.ts
#	src/Umbraco.Web.UI.Client/src/packages/data-type/reference/repository/data-type-reference.server.data.ts
#	src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts
#	src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/rollback.action.ts
#	tests/Umbraco.Tests.AcceptanceTest/package-lock.json
#	tests/Umbraco.Tests.AcceptanceTest/package.json
#	tests/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs
#	tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TemporaryFileServiceTests.cs
This commit is contained in:
Andy Butland
2025-04-09 22:05:59 +02:00
91 changed files with 2817 additions and 368 deletions

View File

@@ -2,6 +2,7 @@ using Examine;
using Examine.Search;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.HostedServices;
using Umbraco.Cms.Core.HostedServices;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Changes;

View File

@@ -20,6 +20,72 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a page of items used in any kind of relation from selected integer ids.
/// </summary>
public IEnumerable<RelationItem> GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize,
bool filterMustBeIsDependency, out long totalRecords)
{
Sql<ISqlContext> innerUnionSql = GetInnerUnionSql();
Sql<ISqlContext>? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql()
.SelectDistinct(
"[x].[id] as nodeId",
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
"[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
"[x].[alias] as relationTypeAlias",
"[x].[name] as relationTypeName",
"[x].[isDependency] as relationTypeIsDependency",
"[x].[dual] as relationTypeIsBidirectional")
.From<NodeDto>("n")
.InnerJoinNested(innerUnionSql, "x")
.On<NodeDto, UnionHelperDto>((n, x) => n.NodeId == x.Id, "n", "x")
.LeftJoin<ContentDto>("c")
.On<NodeDto, ContentDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "n",
aliasRight: "c")
.LeftJoin<ContentTypeDto>("ct")
.On<ContentDto, ContentTypeDto>(
(left, right) => left.ContentTypeId == right.NodeId,
aliasLeft: "c",
aliasRight: "ct")
.LeftJoin<NodeDto>("ctn")
.On<ContentTypeDto, NodeDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "ct",
aliasRight: "ctn")
.LeftJoin<DocumentDto>("d")
.On<NodeDto, DocumentDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "n",
aliasRight: "d");
if (ids.Any())
{
sql = sql?.Where<NodeDto>(x => ids.Contains(x.NodeId), "n");
}
if (filterMustBeIsDependency)
{
sql = sql?.Where<RelationTypeDto>(rt => rt.IsDependency, "x");
}
// Ordering is required for paging
sql = sql?.OrderBy<RelationTypeDto>(x => x.Alias, "x");
Page<RelationItemDto>? pagedResult = _scopeAccessor.AmbientScope?.Database.Page<RelationItemDto>(pageIndex + 1, pageSize, sql);
totalRecords = Convert.ToInt32(pagedResult?.TotalItems);
return pagedResult?.Items.Select(MapDtoToEntity) ?? Enumerable.Empty<RelationItem>();
}
private Sql<ISqlContext> GetInnerUnionSql()
{
if (_scopeAccessor.AmbientScope is null)
@@ -91,6 +157,148 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
return innerUnionSql;
}
/// <summary>
/// Gets a page of the descending items that have any references, given a parent id.
/// </summary>
public IEnumerable<RelationItem> GetPagedDescendantsInReferences(
int parentId,
long pageIndex,
int pageSize,
bool filterMustBeIsDependency,
out long totalRecords)
{
SqlSyntax.ISqlSyntaxProvider? syntax = _scopeAccessor.AmbientScope?.Database.SqlContext.SqlSyntax;
// Gets the path of the parent with ",%" added
Sql<ISqlContext>? subsubQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql()
.Select(syntax?.GetConcat("[node].[path]", "',%'"))
.From<NodeDto>("node")
.Where<NodeDto>(x => x.NodeId == parentId, "node");
// Gets the descendants of the parent node
Sql<ISqlContext>? subQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql()
.Select<NodeDto>(x => x.NodeId)
.From<NodeDto>()
.WhereLike<NodeDto>(x => x.Path, subsubQuery);
Sql<ISqlContext> innerUnionSql = GetInnerUnionSql();
Sql<ISqlContext>? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct(
"[x].[id] as nodeId",
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
"[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
"[x].[alias] as relationTypeAlias",
"[x].[name] as relationTypeName",
"[x].[isDependency] as relationTypeIsDependency",
"[x].[dual] as relationTypeIsBidirectional")
.From<NodeDto>("n")
.InnerJoinNested(innerUnionSql, "x")
.On<NodeDto, UnionHelperDto>((n, x) => n.NodeId == x.Id, "n", "x")
.LeftJoin<ContentDto>("c")
.On<NodeDto, ContentDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "n",
aliasRight: "c")
.LeftJoin<ContentTypeDto>("ct")
.On<ContentDto, ContentTypeDto>(
(left, right) => left.ContentTypeId == right.NodeId,
aliasLeft: "c",
aliasRight: "ct")
.LeftJoin<NodeDto>("ctn")
.On<ContentTypeDto, NodeDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "ct",
aliasRight: "ctn")
.LeftJoin<DocumentDto>("d")
.On<NodeDto, DocumentDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "n",
aliasRight: "d");
sql = sql?.WhereIn((System.Linq.Expressions.Expression<Func<NodeDto, object?>>)(x => x.NodeId), subQuery, "n");
if (filterMustBeIsDependency)
{
sql = sql?.Where<RelationTypeDto>(rt => rt.IsDependency, "x");
}
// Ordering is required for paging
sql = sql?.OrderBy<RelationTypeDto>(x => x.Alias, "x");
Page<RelationItemDto>? pagedResult = _scopeAccessor.AmbientScope?.Database.Page<RelationItemDto>(pageIndex + 1, pageSize, sql);
totalRecords = Convert.ToInt32(pagedResult?.TotalItems);
return pagedResult?.Items.Select(MapDtoToEntity) ?? Enumerable.Empty<RelationItem>();
}
/// <summary>
/// Gets a page of items which are in relation with the current item.
/// Basically, shows the items which depend on the current item.
/// </summary>
public IEnumerable<RelationItem> GetPagedRelationsForItem(int id, long pageIndex, int pageSize,
bool filterMustBeIsDependency, out long totalRecords)
{
Sql<ISqlContext> innerUnionSql = GetInnerUnionSql();
Sql<ISqlContext>? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct(
"[x].[otherId] as nodeId",
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
"[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
"[x].[alias] as relationTypeAlias",
"[x].[name] as relationTypeName",
"[x].[isDependency] as relationTypeIsDependency",
"[x].[dual] as relationTypeIsBidirectional")
.From<NodeDto>("n")
.InnerJoinNested(innerUnionSql, "x")
.On<NodeDto, UnionHelperDto>((n, x) => n.NodeId == x.OtherId, "n", "x")
.LeftJoin<ContentDto>("c")
.On<NodeDto, ContentDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "n",
aliasRight: "c")
.LeftJoin<ContentTypeDto>("ct")
.On<ContentDto, ContentTypeDto>(
(left, right) => left.ContentTypeId == right.NodeId,
aliasLeft: "c",
aliasRight: "ct")
.LeftJoin<NodeDto>("ctn")
.On<ContentTypeDto, NodeDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "ct",
aliasRight: "ctn")
.LeftJoin<DocumentDto>("d")
.On<NodeDto, DocumentDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "n",
aliasRight: "d")
.Where<UnionHelperDto>(x => x.Id == id, "x");
if (filterMustBeIsDependency)
{
sql = sql?.Where<RelationTypeDto>(rt => rt.IsDependency, "x");
}
// Ordering is required for paging
sql = sql?.OrderBy<RelationTypeDto>(x => x.Alias, "x");
Page<RelationItemDto>? pagedResult = _scopeAccessor.AmbientScope?.Database.Page<RelationItemDto>(pageIndex + 1, pageSize, sql);
totalRecords = Convert.ToInt32(pagedResult?.TotalItems);
return pagedResult?.Items.Select(MapDtoToEntity) ?? Enumerable.Empty<RelationItem>();
}
public IEnumerable<RelationItemModel> GetPagedRelationsForItem(
Guid key,
long skip,
@@ -388,6 +596,91 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
return _umbracoMapper.MapEnumerable<RelationItemDto, RelationItemModel>(pagedResult);
}
public IEnumerable<RelationItemModel> GetPagedDescendantsInReferences(
int parentId,
long skip,
long take,
bool filterMustBeIsDependency,
out long totalRecords)
{
SqlSyntax.ISqlSyntaxProvider? syntax = _scopeAccessor.AmbientScope?.Database.SqlContext.SqlSyntax;
// Gets the path of the parent with ",%" added
Sql<ISqlContext>? subsubQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql()
.Select(syntax?.GetConcat("[node].[path]", "',%'"))
.From<NodeDto>("node")
.Where<NodeDto>(x => x.NodeId == parentId, "node");
// Gets the descendants of the parent node
Sql<ISqlContext>? subQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql()
.Select<NodeDto>(x => x.NodeId)
.From<NodeDto>()
.WhereLike<NodeDto>(x => x.Path, subsubQuery);
Sql<ISqlContext> innerUnionSql = GetInnerUnionSql();
Sql<ISqlContext>? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct(
"[x].[id] as nodeId",
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
"[x].[alias] as relationTypeAlias",
"[x].[name] as relationTypeName",
"[x].[isDependency] as relationTypeIsDependency",
"[x].[dual] as relationTypeIsBidirectional")
.From<NodeDto>("n")
.InnerJoinNested(innerUnionSql, "x")
.On<NodeDto, UnionHelperDto>((n, x) => n.NodeId == x.Id, "n", "x")
.LeftJoin<ContentDto>("c").On<NodeDto, ContentDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "n",
aliasRight: "c")
.LeftJoin<ContentTypeDto>("ct")
.On<ContentDto, ContentTypeDto>(
(left, right) => left.ContentTypeId == right.NodeId,
aliasLeft: "c",
aliasRight: "ct")
.LeftJoin<NodeDto>("ctn")
.On<ContentTypeDto, NodeDto>(
(left, right) => left.NodeId == right.NodeId,
aliasLeft: "ct",
aliasRight: "ctn");
sql = sql?.WhereIn(
(System.Linq.Expressions.Expression<Func<NodeDto, object?>>)(x => x.NodeId),
subQuery,
"n");
if (filterMustBeIsDependency)
{
sql = sql?.Where<RelationTypeDto>(rt => rt.IsDependency, "x");
}
// find the count before ordering
totalRecords = _scopeAccessor.AmbientScope?.Database.Count(sql!) ?? 0;
RelationItemDto[] pagedResult;
//Only to all this, if there is items
if (totalRecords > 0)
{
// Ordering is required for paging
sql = sql?.OrderBy<RelationTypeDto>(x => x.Alias, "x");
pagedResult =
_scopeAccessor.AmbientScope?.Database.SkipTake<RelationItemDto>(skip, take, sql).ToArray() ??
Array.Empty<RelationItemDto>();
}
else
{
pagedResult = Array.Empty<RelationItemDto>();
}
return _umbracoMapper.MapEnumerable<RelationItemDto, RelationItemModel>(pagedResult);
}
private class UnionHelperDto
{
[Column("id")] public int Id { get; set; }