* Lowercase OpenAPI document name on registration to match AddOpenApi internal behaviour
AddOpenApi lowercases the document name when registering its keyed services, so
ReplaceOpenApiSchemaService must receive the same lowercased key or the lookup
throws. BackOfficeOpenApiDocumentBuilder now computes a normalised registration
name and uses it for all DI calls, while keeping DocumentName in its original
casing. ShouldInclude matches [MapToApi] case-insensitively to align with how
documents are registered, and the UI dropdown label falls back to DocumentName
(original casing) rather than the lowercased registration key.
AddUmbracoOpenApiDocument applies the same normalisation for its apiName parameter.
* Add regression tests for mixed-case OpenAPI document name registration
Covers the bug scenario where AddBackOfficeOpenApiDocument with a mixed-case
name and WithJsonOptions threw InvalidOperationException at startup, and verifies
that ShouldInclude matches [MapToApi] case-insensitively.
* SonarCloud: allow unit test failures without failing the analysis
Test failures should not block SonarCloud analysis - coverage data is
still collected by dotnet-coverage regardless of test outcome. The
regular CI pipeline is the correct gate for test pass/fail.
* SonarCloud: install Java 21 explicitly and skip JRE provisioning
- Add actions/setup-java@v5 (temurin-21) so JAVA_HOME always points to Java 21
- Pass sonar.scanner.skipJreProvisioning=true in the begin command since Java 21 is installed explicitly, removing the need for the scanner to download a JRE at runtime
* SonarCloud: clear SONARQUBE_SCANNER_PARAMS after begin
Prevents the End analysis step from re-applying sonar params that begin already wrote to the analysis config, eliminating the "Ignoring property from env variable" warning.
* SonarCloud: always cancel in-progress runs on new push
* TEMP: add failing test to verify pipeline resilience — revert before merge
* Revert "TEMP: add failing test to verify pipeline resilience — revert before merge"
This reverts commit 828a7510cb.
* Revert "SonarCloud: clear SONARQUBE_SCANNER_PARAMS after begin"
This reverts commit 1911b65db1.
* Make SonarCloud workflow resilient to build and test failures
* Fix inaccurate warning message when unit tests fail
* Revert build step resilience, keep test failure warning
* Improve test failure warning with coverage file check
* Temporary: add failing test to verify SonarCloud workflow resilience
* Revert "Temporary: add failing test to verify SonarCloud workflow resilience"
This reverts commit 71ecd61034.
* Delivery API: return inline {} schema for unconstrained property types
ContentTypeSchemaTransformer now checks the raw STJ schema via JsonSchemaExporter before
calling GetOrCreateSchemaAsync. STJ generates boolean true for unconstrained types (JsonNode,
object, types with custom converters), which the pipeline converts to {}. When the raw schema
is true, an inline {} is returned without registering a named component - a named component
adds no value and misleads API consumers into thinking a concrete model shape exists.
* Delivery API: add Plain JSON property to contract test sample types
Adds a Plain JSON property to the sample article page content type used by the OpenAPI contract
tests. This exercises the unconstrained-type fix: the property should appear as inline {} in the
schema, not as a named JsonNode component. Updates the expected contract to reflect the new
property.
* Re-generate typed-schemas-with-sample-types.json
For some reason the previous change got formatted differently, so it was displaying more changes than it should.
* Simplify comments
* Delivery API: guard unconstrained type check with JsonTypeInfoKind.None
Disable OpenAPI XML documentation source generator
Due to the amount of XML documentation in this solution, the OpenAPI XML documentation source generator produces too many lines of code in a single method (GenerateCacheEntries), which causes a StackOverflowException when running on IIS. The fix disables the analyzer globally via Directory.Build.props.
* Add SonarCloud CI workflow
Adds a manual-dispatch GitHub Actions workflow for SonarQube Cloud
analysis (build, unit test coverage, scan). Moves file_header_template
and SA1636/SA1633 suppression from .editorconfig comments and
.globalconfig into the active .editorconfig .NET language conventions
section, removing the duplicated suppression from .globalconfig.
* Remove branch filter from pull_request trigger in SonarCloud workflow
Runs analysis on all PRs regardless of target branch.
* Adjust sonarcloud gh action based on feedback
* Add .sonarqube to .gitignore
* Attempt to split build and analysis in order to be able to run in PRs from forks
* Adjust SonarCloud workflows
* Rename SonarCloud workflows to reflect their actual purpose
* Remove sonar.coverage.exclusions
* Include .github in sonar analysis
* Include build directory in sonar analysis
* Apply sonarcloud workflow fixes from test branch
* Remove setup-dotnet step from upload workflow
* Use default branch from context instead of hardcoded main in analysis workflow
* Update checkout action to v6 in upload workflow
* Add actions: read permission to upload workflow
* Enable SCM integration in upload workflow
* Extension template: Configure BackOffice JSON options and replace IUser response with WhoAmIResponseModel
Sets the extension template's backoffice API to use the BackOffice named JsonOptions so the extension's serializer is insulated from consumer-level overrides.
The sample whoAmI endpoint previously returned IUser directly. IUser is a Umbraco.Core domain interface, not an API contract - it has no JSON polymorphism configuration and its nested interface properties (e.g. IReadOnlyUserGroup) are not designed to be serialized as part of an HTTP response. Once the BackOffice JsonOptions activated UmbracoJsonTypeInfoResolver for the extension's OpenAPI document, schema generation produced incomplete output (no type information on the Groups property).
Replaces the return type with a flat WhoAmIResponseModel exposing only the fields the dashboard UI consumes (name, email, groups). Domain interfaces should not be exposed directly on a controller - always project into a dedicated response model.
* Extension template: Fully-qualify Cms.Core references and drop Umbraco.Extensions import
The composer and controller base referenced `Cms.Core.Constants...` in short form, which relied on namespace fallback from `Umbraco.Extension.Controllers` finding `Umbraco.Cms.Core`. When consumers instantiate the template with a non-Umbraco root namespace, that fallback breaks. References are now fully qualified as `Umbraco.Cms.Core.Constants...`.
Additionally, the `whoAmI` controller's `using Umbraco.Extensions;` was getting mangled by the template engine's token substitution of `Umbraco.Extension` into the consumer's name. Replaces `WhereNotNull()` with the BCL-only `OfType<string>()` so the controller no longer depends on the `Umbraco.Extensions` namespace.
* Extension template: Tighten whoAmI 204 guard in dashboard
The generated client returns a truthy empty data object (or null body) for a 204 response, so the previous `if (data)` check could pass and render `undefined` values in the notification. Checks `data?.email` instead - it's a required field on a real 200 response and absent in the 204 fallback.
* Extension template: Return 401 Unauthorized from whoAmI and simplify dashboard handling
When `BackOfficeSecurity.CurrentUser` is null, the sample `whoAmI` endpoint now returns `Unauthorized()` instead of `NoContent()`, matching the `GetCurrentUserController` pattern in the Management API. Drops the 204 ProducesResponseType so the OpenAPI spec only advertises 200 plus the framework-emitted 401.
The dashboard collapses its empty-data check into a single `error || !data` guard, moves the notification into the success branch, and regenerates the client to drop the now-unused 204 response.
* Add helper for registering custom backoffice OpenAPI documents
Bundles AddOpenApi, the [MapToApi]-aware ShouldInclude predicate, the
Umbraco schema reference ID convention, and AddOpenApiDocumentToUi
behind a single IUmbracoBuilder.AddBackOfficeOpenApiDocument call.
Authors pass documentName, an optional title (used both as Info.Title
and the UI dropdown label), and an optional configure callback that
runs last so it can override anything the helper sets. An optional
jsonOptionsName is forwarded to ReplaceOpenApiSchemaService for
documents that need schema-time JSON serialization aligned to a named
JsonOptions.
Schema reference ID logic moves out of ConfigureUmbracoOpenApiOptionsBase
into UmbracoSchemaIdGenerator.CreateSchemaReferenceId so both the new
helper and the base class share one source of truth. The extension
template's composer collapses to a single AddBackOfficeOpenApiDocument
call, with document Info.Version, backoffice security, and the operation
ID transformer staying in the configure callback.
* Refactor backoffice OpenAPI helper into a fluent builder
Replace the parameter-list AddBackOfficeOpenApiDocument helper with a
callback-based form that yields a BackOfficeOpenApiDocumentBuilder. The
builder owns its state and applies it to the IUmbracoBuilder once the
user callback returns, so authors don't need to remember a terminal
Build call. Extension methods can layer on (e.g.
WithBackOfficeAuthentication in Umbraco.Cms.Api.Management) without the
core helper carrying every opinion.
Defaults stay sensible: filtering by [MapToApi(documentName)], the
Umbraco schema reference IDs, and the tag/sort transformers that v17's
global Swashbuckle pipeline applied. UI dropdown registration is
opt-out via ExcludeFromUi rather than opt-in. JSON options for schema
generation are an opt-in via WithHttpJsonOptions (instance or factory),
described purely in terms of the schema effect.
Move UmbracoSchemaIdGenerator's CreateSchemaReferenceId wrapper out of
ConfigureUmbracoOpenApiOptionsBase so both the base config class and
the new builder share one source of truth, and update the
ContentTypeSchemaTransformer / unit test callsites accordingly. Refresh
the extension template to use the new shape.
* Rename WithHttpJsonOptions to WithJsonOptions
The Http qualifier was naming the .NET type rather than the intent.
The parameter type carries the disambiguation; the method name is now
intent-focused and the XML doc explains the use case (matching the
serialization conventions of the API endpoints the document describes).
* Add WithJsonOptions(string) overload for named HTTP JsonOptions
Convenience overload that accepts the registered name and resolves the
matching Microsoft.AspNetCore.Http.Json.JsonOptions via IOptionsMonitor.
Documents on all three WithJsonOptions overloads now explicitly name
the HTTP JsonOptions type so consumers know which framework type they
are configuring.
* Migrate Management API OpenAPI registration to AddBackOfficeOpenApiDocument
Replaces the AddUmbracoOpenApiDocument<ConfigureUmbracoManagementApiOpenApiOptions>
call with the new fluent builder. The custom config class becomes dead
code and is deleted; all per-document opinions (Info metadata, security
requirements, transformers, JSON options) move into the configuration
callback alongside the document registration.
Behavior preserved: same ShouldInclude (now via [MapToApi]-only since
all Management controllers carry the attribute through their base class),
same schema reference IDs, same operation IDs via UmbracoOperationIdTransformer,
same backoffice security requirements, same schema/operation transformers,
same named JSON options for schema generation.
* Cleanup unused usings
* Address PR review feedback on AddBackOfficeOpenApiDocument
Make UmbracoOperationIdTransformer part of the builder's defaults instead of
the Management API adding it explicitly, and expand the XML docs on
AddBackOfficeOpenApiDocument to spell out the defaults a caller opts into.
Add tests covering the new builder and its defaults:
- Unit tests for BackOfficeOpenApiDocumentBuilder defaults (CreateSchemaReferenceId,
ShouldInclude, ConfigureOpenApiOptions composition, WithTitle/WithUiTitle UI
dropdown handling, ExcludeFromUi).
- Integration tests that register sample controllers, fetch the generated OpenAPI
document and verify the defaults end-to-end: Info.Title from WithTitle,
MapToApi filtering, Umbraco operation-id and schema-id conventions (including
the version-suffix branch), tag-by-group-name and tag-first path sorting.
- Integration tests for the three WithJsonOptions overloads (instance, factory,
named) confirming the configured JsonOptions reach schema generation.
* Remove redundant operation-id override from extension template
UmbracoOperationIdTransformer is now part of the AddBackOfficeOpenApiDocument
defaults, so the template's custom action-name transformer would only overwrite
the work the default just did. Drop it, and consolidate the documentation
pointer to a single link.
* Narrow MimeTypesTransformer to JSON-equivalent variants and register it in AddBackOfficeOpenApiDocument
Filter only removes redundant JSON-equivalent MIME types (text/json,
application/*+json, text/plain) when application/json is present.
Non-JSON types like application/xml are preserved. Register the
transformer as a default in AddBackOfficeOpenApiDocument so custom
backoffice documents get the same treatment as Umbraco's own APIs.
* Register RequireNonNullablePropertiesSchemaTransformer in AddBackOfficeOpenApiDocument
* Apply review notes
- Drop RequireNonNullablePropertiesSchemaTransformer and MimeTypesTransformer
from the Management API's ConfigureOpenApiOptions block — both are now
defaults on the builder.
- Expand MimeTypesTransformer XML docs to reflect its broader role (it now
applies to every backoffice document, not just the Management API) and
correct the response-side inline comment.
- Move MimeTypesTransformerTests from the Delivery test folder/namespace to
the Api.Common test folder/namespace, since the transformer is no longer
Delivery-specific.
- Rename BackOfficeOpenApiDocumentExtensionTests to
UmbracoBuilderOpenApiExtensionsTests so the test fixture name matches the
concrete class under test.
Skip operation ID generation for non-controller endpoints
UmbracoOperationIdTransformer is registered globally for the default
OpenAPI document, so any minimal API endpoint that lands there ran
through it. The transformer threw "This handler operates only on
ControllerActionDescriptor" because its conventions (route prefix
stripping, MapToApiVersion lookup) only make sense for MVC actions.
Return null from the generator and skip the assignment when the action
descriptor isn't a ControllerActionDescriptor. The framework's default
operation ID applies in that case.
Disable inaccessible parent folders in element tree
When an element start node is configured to a child folder, the backend
returns ancestor folders flagged with NoAccess so they show as breadcrumbs.
The element folder tree item used the default tree item element, which
does not observe noAccess, so parent folders rendered as enabled and
clickable in the Library section tree. Added a custom
element-folder-tree-item element that observes the context's noAccess and
forwards it to the base, which already handles disabling the menu item.
* Delivery API: Fix broken discriminator mapping refs for polymorphic schemas
Microsoft.AspNetCore.OpenApi's MapPolymorphismOptionsToDiscriminator builds each ref as callback(base) + callback(derived), but our typed-schema flow registers the derived schemas without the base prefix. The auto-built mapping refs end up pointing at non-existent schemas, which crashes strict client generators like orval.
Strip the base schema id from the front of each broken ref to recover the registration key the derived schema actually uses.
* Delivery API: Add integration test coverage for the polymorphic discriminator mapping fix
Adds a test-only property editor whose Delivery API value type is a polymorphic interface declared with [JsonDerivedType], wired into the existing typed-schema integration test fixture. The OpenApiContract_HasExpectedSchemas test verifies that the auto-built discriminator mapping refs resolve to the registered derived schema names, providing end-to-end regression coverage for the fix.
Also extends AssertSchemaIsPolymorphicUnion to accept either oneOf (used by our typed schema unions) or anyOf (used by framework-built unions for [JsonDerivedType] interfaces).
* Use a captured schemas local in FixAutoBuiltDiscriminatorMapping
Move the null check for document.Components.Schemas into the top-of-method guard and use the captured non-null local in the loop body. Avoids both the null-conditional ?. operators and the null-forgiving ! operator at the use sites.
* Delivery API: Generate typed OpenAPI schemas per content type
* Honour Delivery API allow/deny list in typed OpenAPI schemas
ContentTypeSchemaTransformer now filters DocumentTypes through
DeliveryApiSettings.IsAllowedContentType so document types blocked
by AllowedContentTypeAliases / DisallowedContentTypeAliases no longer
leak into the polymorphic union or discriminator mapping.
* Stop registering media derived types in the JSON resolver
ContentJsonTypeResolverBase.GetDerivedTypes goes back to returning
empty. Previously it registered ApiMediaWithCrops and
ApiMediaWithCropsResponse as derived types of their interfaces, which
made every consumer of the resolver (the Delivery API and webhooks)
emit a $type discriminator on media payloads, even when the typed
schema feature was disabled.
The Delivery API still needs a base schema for the typed media
schemas to extend via allOf. Since the concrete media classes are
internal to Umbraco.Infrastructure and cannot be referenced from
[JsonDerivedType] in Core, ContentTypeSchemaTransformer now builds
that base from the interface's own properties when the interface has
no [JsonDerivedType] entries. Content/element interfaces are
unaffected and keep using their declared concrete derived types.
Snapshots regenerated.
* Drop default JsonDerivedType registrations from Delivery API interfaces
Removes the [JsonDerivedType] attributes from IApiContent,
IApiContentResponse, and IApiElement. Without them System.Text.Json
configures no polymorphism by default, so wire payloads stop carrying
$type fields and the OpenAPI spec stops emitting a discriminator on
the generic schemas - matching v17 Delivery API behaviour. Consumers
that need polymorphic serialization can still register derived types
via ContentJsonTypeResolverBase.
Snapshots regenerated.
* Allows nulls at property reference sites without mutating any shared component schema.
Avoid unnecessary re-get of the JsonTypeInfo for the default case.
* Updated expected contracts following code adjustments
* Drop additionalProperties: false from typed schemas
JSON Schema 2020-12 (mandated by OpenAPI 3.1) does not let additionalProperties look through allOf, so a strict validator rejects every inherited field on the composed *ResponseModel/*Model/*PropertiesModel schemas. Most code generators silently ignore it, but the document is technically invalid and the constraint would be a lie anyway since Umbraco can grow new properties in non-major releases.
Removed from all four schema construction sites (response, content type, properties, and the interface-based fallback) and regenerated the affected snapshots.
* Preserve casing of content type aliases in OpenAPI schema IDs
Replaces the legacy ModelsBuilder-style ToCleanString tokenizer with
ToFirstUpperInvariant. The tokenizer split aliases on case boundaries
and mangled capital-letter runs (e.g. "xMLSitemap" -> "XMlsitemap"),
making the typed schema names harder to read for OpenAPI consumers.
Since content type aliases are already valid identifiers, only the
first character needs uppercasing.
Also adds an "xMLSitemap" sample type to the integration tests to
cover the casing-preservation behavior.
* Qualify properties model schema IDs by item type
Document, element, and media types share the same alias namespace
across content/media (a doc-type and a media-type can use the same
alias), so a "{Schema}PropertiesModel" naming scheme could collide.
Properties model schemas now follow the same Content/Element/Media
suffix as their parent *Model schema:
- Document type: ArticlePageContentPropertiesModel
- Element type: TestElementElementPropertiesModel
- Media type: VideoMediaPropertiesModel
Composition references look up each composition's own IsElement so
that a doc-type composing an element-type (allowed in the UI) still
references the correct ElementPropertiesModel schema.
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Delivery API: Drop $type discriminator from response payloads
Removed [JsonDerivedType] from IApiContent and IApiContentResponse so
System.Text.Json stops emitting $type on collection endpoints and the
OpenAPI spec stops requiring a discriminator on the generic schemas,
restoring v17 behaviour. Consumers that need polymorphic responses can
still register derived types via ContentJsonTypeResolverBase.
Snapshot regenerated.
* Delivery API: Preserve cultures property order on collection responses
Added [JsonPropertyOrder(100)] to IApiContentResponse.Cultures so the
property is serialized last when the static type is the interface
(collection endpoints), matching the existing attribute on the concrete
ApiContentResponse class. Mirrors the [JsonPropertyOrder(-100)] pattern
already used for ContentType on IApiElement / ApiElement.
Snapshot regenerated.
* Backoffice Element Search: add global search provider for elements
Adds an "Elements" category to the backoffice global search, scoped to
the Library section.
Server: SearchElementItemController exposes
GET /umbraco/management/api/v1/item/element/search
backed by IEntitySearchService (DB-backed name match, mirrors the
DataType search pattern). Maps results via IElementPresentationFactory.
Client: new src/packages/elements/search/ module with a search provider,
repository, server data source, search-result-item element, and
globalSearch manifest (alias Umb.GlobalSearch.Element). Wired into the
elements package manifests. Backend SDK regenerated from OpenApi.json.
* Backoffice Element Search: surface ancestors, trashed and draft state
- New ancestors endpoint at /item/element/ancestors so result items can
render a parent breadcrumb (uses NamedItemResponseModel to cover
element folder ancestors).
- ElementItemResponseModel.IsTrashed added and populated by the
presentation factory, flowing through search and item responses.
- Frontend search result item renders breadcrumb, Trashed tag with
strike-through, and Draft tag (mirrors document search result item).
* Address PR review feedback
- Add integration test for AncestorsElementItemController (mirrors
AncestorsDocumentItemControllerTests).
- UmbElementSearchItemModel: declare `name: string` (the search result
contract requires it; mirrors UmbDocumentSearchItemModel).
- Element search data source: drop the empty-string fallback on `name`
and add the same TODO comment used in the document data source.
- Add JSDoc to UmbElementSearchProvider, UmbElementSearchRepository and
UmbElementSearchServerDataSource (matches document equivalents).
* Backoffice Element Search: export search consts and unblock isTrashed on item endpoint
- Re-export ./search/constants.js from the elements package barrel so
UMB_ELEMENT_SEARCH_PROVIDER_ALIAS and UMB_ELEMENT_GLOBAL_SEARCH_ALIAS
are reachable as the export-consts test expects.
- element-item.server.data-source.ts: stop hardcoding isTrashed to false
- now that ElementItemResponseModel exposes the flag, item-based UIs
reflect the actual trashed state.
* Refactor element tree controllers to use start node filter service
Move user start node filtering logic from UserStartNodeFolderTreeControllerBase
and ElementTreeControllerBase into a dedicated ElementStartNodeTreeFilterService,
matching the pattern established for document and media trees in PR #22486.
Add a virtual TreeObjectTypes property to UserStartNodeTreeFilterService so
element trees can query both Element and ElementContainer object types.
* Address PR review feedback
* Apply PR review feedback
Replace TreeObjectType (singular) with abstract TreeObjectTypes (array).
Use static readonly arrays in concrete implementations to avoid
allocations.
Move multi-object-type test into UserStartNodeTreeFilterServiceTests
since it exercises base class behavior, not the element service
specifically.
* Add element-level extension methods for variance, culture, fallback support
Published Element Extensions now support the same culture, type-checking,
equality, and creator/writer methods that were previously only available
on Published Content Extensions. Content extensions delegate to the
element versions, preserving backwards compatibility.
New element extensions (Core):
- HasCulture, IsInvariantOrHasCulture, CultureDate
- IsDocumentType (both overloads)
- IsEqual, IsNotEqual
- GetCreatorName, GetWriterName
- HasValue with IPublishedValueFallback and Fallback support
New friendly element wrappers (Web.Common):
- Name, CultureDate, CreatorName, WriterName
New non-friendly element extensions (Web.Common):
- CreatorName, WriterName (with IUserService parameter)
* Add unit tests for PublishedElement extension methods
Tests for core extensions (HasCulture, IsInvariantOrHasCulture,
CultureDate, IsDocumentType, IsEqual/IsNotEqual, GetCreatorName,
GetWriterName, HasValue with fallback) and friendly wrappers (Name,
CultureDate, CreatorName, WriterName). All mocks use MockBehavior.Strict.
* Fix empty XML doc param tag for variationContextAccessor in CultureDate
* Delegate content CreatorName/WriterName to element friendly extensions, remove redundant UserService field
* Address PR review: restore StaticServiceProvider in TearDown, use case-insensitive culture dictionary in tests
* Add remarks note about Fallback.ToAncestors not being supported at element level
* Clarify the casting for readability
---------
Co-authored-by: kjac <kja@umbraco.dk>
* Update Umbraco extension template for OpenAPI route changes
Following the migration from Swashbuckle to Microsoft.AspNetCore.OpenApi
in #21058, the extension template still pointed at the old Swagger URL
pattern and used outdated terminology in code comments.
- generate-client npm script now points at /umbraco/openapi/{name}.json
instead of /umbraco/swagger/{name}/swagger.json
- generate-openapi.js renames swaggerUrl to openApiUrl and updates the
example URL in the missing-argument error message
- UmbracoExtensionApiComposer.cs comments updated from "Swagger" to
"OpenAPI"
* Scope custom OpenAPI document to extension's own endpoints
Without an explicit ShouldInclude predicate, Microsoft.AspNetCore.OpenApi
only includes endpoints whose ApiExplorer GroupName equals the document
name. The template's controller declared a different group name, so the
custom document was created but stayed empty (paths: []), which in turn
made npm run generate-client produce an empty TypeScript SDK.
Filter by the [MapToApi] attribute already present on the extension's
controller base, mirroring the pattern used by the Management and
Delivery API options.
* Add Microsoft.AspNetCore.OpenApi reference to Central package management
The PerProject mode of the umbraco-extension template took a direct
dependency on Microsoft.AspNetCore.OpenApi (with a long comment
explaining why) but the Central mode did not, so default Central
scaffolds failed to build with the source-generator interceptors
error. Mirror the dependency in the Central csproj block and
Directory.Packages.props.
* Uninstall `Swashbuckle.AspNetCore` and install `Microsoft.AspNetCore.OpenApi`
Also installed `Swashbuckle.AspNetCore.SwaggerUI` for now to use as UI only.
* Registered UI and removed or commented out Swashbuckle specific code
* Started configuring the different Open API documents
* Started moving configuration
* Simplifying configuration
* Added missing configuration for the Delivery API
* Added missing configurations for Management API
Still missing polymorphism settings for both APIs
* Adjust Umbraco Extension template with OpenApi changes
* Handle sub types in open api document generation
* Renaming mime types transformer to align with others
* Added discriminator configuration
* Reference Umbraco.Cms.DevelopmentMode.Backoffice from integration tests project to avoid models mode exception being logged in tests
* Now configuring and using the HTTP json options instead of having custom transformers for handling enums and polymorphism
* Fixes to examples
* Update OpenAPI packages
* Mark most transformers as internal
* Simplify adding backoffice security requirements to your API
* Fix missing required properties
* Re-order transformers to fix missing notification headers
* Fix most build errors after regenerating client
* Fix mime types transformer being applied to Management API
* Additional fixes
* Additional fixes to file response types
* Configure Swagger UI documents
* Clear server list
* Sort APIs in UI
* Re-introduce schema handlers and fix issue with nullable enum schema name
* Simplify examples
* Small optimization
* Simplify nullability check in RequireNonNullablePropertiesSchemaTransformer
* Remove unused property
* Small fixes suggested by Claude
* Undo unintended space changes
* Add unit tests for OpenAPI transformers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add unit tests for additional OpenAPI transformers
- RequireNonNullablePropertiesSchemaTransformer (7 tests)
- BackOfficeSecurityRequirementsTransformer (10 tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Rename SwaggerGen classes to OpenApi for consistency
- Rename ConfigureUmbracoDeliveryApiSwaggerGenOptions to ConfigureUmbracoDeliveryApiOpenApiOptions
- Rename ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions to ConfigureUmbracoMemberAuthenticationDeliveryApiOpenApiOptions
- Rename ConfigureUmbracoManagementApiSwaggerGenOptions to ConfigureUmbracoManagementApiOpenApiOptions
- Rename SwaggerRouteTemplatePipelineFilter to OpenApiRouteTemplatePipelineFilter
- Update DI registrations to use new class names
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update OpenAPI contract test for Microsoft.AspNetCore.OpenApi
Update expected Delivery API OpenAPI contract to reflect changes from
the migration to Microsoft.AspNetCore.OpenApi:
- OpenAPI version 3.0.4 → 3.1.1
- Nullable types now use type array format (OpenAPI 3.1 style)
- Polymorphic types use anyOf with discriminator
- Security moved from header parameter to securitySchemes
- Removed unnecessary oneOf wrappers around single $ref
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Disable Models Builder in integration tests by default
* Rename Swagger references to OpenApi for consistency
- Rename SwaggerIsEnabled to OpenApiIsEnabled
- Rename SwaggerRouteTemplate to OpenApiRouteTemplate
- Rename SwaggerUiRoutePrefix to OpenApiUiRoutePrefix
- Rename SwaggerUiConfiguration to ConfigureOpenApiUI
- Rename swaggerPipelineFilter variable to openApiPipelineFilter
- Update code comments from "Swagger JSON" to "OpenAPI specification"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Re-generate Management API open api doc and UI client after merge
* Add reference in comment to additional PR to fix file return types schema
* Fix Open API validation errors
* OpenAPI: Replace ISchemaIdHandler/ISchemaIdSelector with static UmbracoSchemaIdGenerator
Remove the DI-based schema ID handler/selector pattern and replace with a
static UmbracoSchemaIdGenerator utility class. This allows both Umbraco code
and external consumers to call the schema ID generation logic directly, which
is useful since the Microsoft OpenAPI package's schema selectors only apply
to Umbraco's own OpenAPI documents.
- Remove ISchemaIdHandler, ISchemaIdSelector interfaces and implementations
- Add static UmbracoSchemaIdGenerator.Generate() method
- Update ConfigureUmbracoOpenApiOptionsBase to use UmbracoSchemaIdGenerator directly
- Remove constructor dependencies from API options classes
- Add unit tests for UmbracoSchemaIdGenerator and CreateSchemaReferenceId
* Rename CustomOperationIdsTransformer to UmbracoOperationIdTransformer and make public
- Rename class to better reflect its purpose as Umbraco's operation ID transformer
- Change visibility from internal to public so it can be used by external consumers
- Update XML documentation to clarify usage for custom OpenAPI configurations
* OpenAPI: Update Delivery API contract test for new document format
Update expected OpenAPI output to include explicit empty values in
examples and consistent array formatting in security requirements.
* OpenAPI: Remove obsolete DocumentInclusionSelector abstraction
The document inclusion logic is now handled directly by
ConfigureUmbracoOpenApiOptionsBase.ShouldInclude(), making
the separate IDocumentInclusionSelector abstraction unnecessary.
* OpenAPI: Reorganize Management API OpenApi folder structure
- Move transformers to OpenApi/Transformers subfolder
- Move OpenApiOptionsExtensions from Extensions to OpenApi folder
- Update namespaces accordingly:
- Umbraco.Cms.Api.Management.OpenApi.Transformers (transformers)
- Umbraco.Cms.Api.Management.OpenApi (extensions)
* OpenAPI: Add ExcludeFromDefaultOpenApiDocument attribute
- Add [ExcludeFromDefaultOpenApiDocument] attribute for excluding controllers from the default OpenAPI document
- Make ShouldInclude method protected virtual in ConfigureUmbracoOpenApiOptionsBase for extensibility
- Override ShouldInclude in ConfigureDefaultApiOptions to check for the exclusion attribute
* OpenAPI: Add UmbracoOpenApiOptions for configuring OpenAPI routes
Add UmbracoOpenApiOptions configuration class to allow customizing:
- Enabled: Enable/disable OpenAPI and Swagger UI (default: non-production)
- RouteTemplate: Route template for OpenAPI JSON documents
- UiRoutePrefix: Route prefix for Swagger UI
Umbraco sets defaults via Configure, users can override via PostConfigure.
Simplify OpenApiRouteTemplatePipelineFilter to use options directly.
* Pipeline filters: Add OnPreMapEndpoints and rename OnEndpoints to OnPreEndpoints
- Add OnPreMapEndpoints method to IUmbracoPipelineFilter for registering
endpoints inside UseEndpoints without calling UseEndpoints twice
- Rename OnEndpoints to OnPreEndpoints (with backward-compatible default)
- Add PreMapEndpoints and PreEndpoints properties to UmbracoPipelineFilter
- Mark OnEndpoints and Endpoints as obsolete (removal in Umbraco 19)
- Update UmbracoApplicationBuilder to call OnPreMapEndpoints inside UseEndpoints
- Remove redundant UseEndpoints() call from BackOfficeManagementApiFilter
- Update LoadTestController to use PreMapEndpoints instead of Endpoints
Co-Authored-By: Claude <noreply@anthropic.com>
* OpenAPI: Move MapOpenApi to PreMapEndpoints hook
Move OpenAPI endpoint mapping from PostPipeline to PreMapEndpoints
to avoid calling UseEndpoints twice in the pipeline.
* OpenAPI: Rename URL paths from swagger to openapi
- Change OpenAPI UI and document URLs from /umbraco/swagger to /umbraco/openapi
- Rename OAuth client constant from Swagger to OpenApiUi (value kept as
umbraco-swagger for backwards compatibility with existing DB registrations)
- Update display name to "Umbraco OpenAPI access"
- Add DefaultUiEnabled option to allow disabling the default UI while
keeping OpenAPI documents available (enables use of alternative UIs)
- Update MiniProfiler ignored path
Co-Authored-By: Claude <noreply@anthropic.com>
* OpenAPI: Update Microsoft.AspNetCore.OpenApi to 10.0.2
* OpenAPI: Add AddOpenApiDocumentToUi extension method
Adds a public extension method to simplify adding OpenAPI documents to the
UI document selector. This respects the configured UmbracoOpenApiOptions
route template, so users don't need to hardcode paths.
The documentTitle parameter is optional and defaults to the documentName.
Also updates the UmbracoExtension template to use the new method and
fixes the documentation URL reference.
* OpenAPI: Make OpenApiRouteTemplatePipelineFilter internal
The class has no extension points (all methods are private static) and
customization is now done via UmbracoOpenApiOptions instead.
* OpenAPI: Rename DeliveryApiSecurityFilter to DeliveryApiSecurityTransformer
Aligns naming with other OpenAPI transformers for consistency.
* OpenAPI: Simplify Delivery API member authentication configuration
Replace ConfigureUmbracoMemberAuthenticationDeliveryApiOpenApiOptions with
a simpler AddDeliveryApiOpenApiMemberAuthentication() extension method on
IServiceCollection. This hides implementation details and provides a cleaner
API for users to enable member authentication in the Delivery API OpenAPI document.
* OpenAPI: Add reference to proposal for custom JSON options support
* Move Delivery API transformers to OpenApi/Transformers folder
Aligns the folder structure with the Management API project.
* Update OpenAPI contract tests to use new URL format
Changed from /swagger/{name}/swagger.json to /openapi/{name}.json
* Apply suggestions from code review
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Update src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Fix IAuthorizationService injection detection in BackOfficeSecurityRequirementsTransformer
- Fix bug where parameter.GetType() was used instead of parameter.ParameterType,
causing the IAuthorizationService injection check to always return false
- Replace magic number with BaseAuthorizeAttributeCount constant
- Improve comments explaining the 403 response logic
- Add test for IAuthorizationService injection detection
* Remove unnecessary InterceptorsNamespaces from API projects
* Remove default implementations from IUmbracoPipelineFilter methods
* Update documentation for Microsoft.AspNetCore.OpenApi migration
- Update CLAUDE.md files to reflect the migration from Swashbuckle to Microsoft.AspNetCore.OpenApi for document generation
- Update URL paths from /umbraco/swagger/ to /umbraco/openapi/
- Rename swaggerPath variables to openApiPath in test files
- Update references to removed types (SchemaIdHandler, OperationIdHandler, etc.) with their new equivalents (UmbracoSchemaIdGenerator, UmbracoOperationIdTransformer)
- Remove outdated technical debt reference to deleted SwaggerDocumentationFilterBase
* Update Swashbuckle.AspNetCore.SwaggerUI to 10.1.2
Fixes browser caching behavior and document URL serialization issues.
* Refactor OpenAPI contract tests with validation
- Add OpenAPI spec validation for both Delivery and Management APIs
- Delivery API: Store expected contract in external JSON file for regression testing
- Management API: Compare generated contract against expected contract endpoint
- Organize Delivery API tests under OpenApi/ subdirectory
- Auto-generate Delivery API contract file if it doesn't exist
* Update ElementReferenceResponseModel type reference after OpenAPI regeneration
* Add discriminator values to Delivery API polymorphic JSON serialization
ConfigureJsonPolymorphismOptions now passes derivedType.Name as the
discriminator value for each JsonDerivedType, ensuring the $type property
is present in responses and the OpenAPI schema is valid.
* Move Delivery API OpenAPI contract tests to Umbraco.Api.Delivery folder
* Update Microsoft.AspNetCore.OpenApi to 10.0.3 and Swashbuckle.AspNetCore.SwaggerUI to 10.1.4
* Use JsonDerivedType attributes for Delivery API polymorphic serialization
Move discriminator configuration from ContentJsonTypeResolverBase to
JsonDerivedType attributes on the interfaces. This is the standard STJ
approach and keeps the resolver available for custom overrides only.
* Add OpenAPI test for custom derived type extensibility
Extract shared test infrastructure into OpenApiTestBase and add
OpenApiCustomDerivedTypeTest to verify the OpenAPI spec remains valid
when a consumer registers a custom derived type via
ContentJsonTypeResolverBase.GetDerivedTypes.
* Fix OpenAPI contract test failing on CI due to ContinuousIntegrationBuild path normalization
[CallerFilePath] embeds a compile-time source path that gets normalized to /_/... on Azure DevOps
agents when ContinuousIntegrationBuild=true. At runtime the expected contract file is not found at
that path, causing the test to attempt Directory.CreateDirectory("/_/...") which fails with
permission denied.
Fix by reading contract files from the output directory (CopyToOutputDirectory) instead of the
compile-time source path. The [CallerFilePath] approach is kept only for writing new contracts
during local development, wrapped in a try/catch so it fails gracefully on CI.
* Bump Swashbuckle.AspNetCore.SwaggerUI to 10.1.7
* Remove duplicate InternalsVisibleTo for Umbraco.Tests.UnitTests
* Extract ReplaceOpenApiSchemaService into shared Api.Common helper
Deduplicates the internal OpenApiSchemaService replacement logic
between Management API and Delivery API into a single internal
extension method in Umbraco.Cms.Api.Common. Uses assembly and type
name checks derived from a public type (OpenApiOptions) instead of
hardcoded strings for safer matching.
* Tighten visibility and improve DI extension structure
- Mark FixFileReturnTypesTransformer as internal (temporary workaround)
- Mark AddUmbracoApiOpenApiUI and AddUmbracoApi as internal
- Rename AddUmbracoApi to AddUmbracoOpenApiDocument on IUmbracoBuilder
- Move AddOpenApiDocumentToUi to OpenApiServiceCollectionExtensions
- Encapsulate ReplaceOpenApiSchemaService inside AddUmbracoOpenApiDocument
as an optional jsonOptionsName parameter
* Regenerate OpenApi.json to fix duplicate document patch endpoint
* Move MimeTypesTransformer to shared base and respect [Consumes]
Moves the MIME type filtering from a Delivery API-only document
transformer to a shared operation transformer in Api.Common. When
[Consumes] is present, replaces content types with exactly what it
declares (fixing application/json-patch+json on the patch endpoint).
Otherwise strips non-application/json types. Regenerates OpenApi.json
and client SDK.
* Move Umbraco-specific transformers from shared base to API configs
RequireNonNullablePropertiesSchemaTransformer, FixFileReturnTypes
Transformer, and MimeTypesTransformer are now registered only in
the Management and Delivery API configs. The default API document
(used for consumer endpoints) no longer applies these opinionated
transformers.
* Clarify XML doc for UmbracoOpenApiOptions.Enabled
* Use alphabetically-first tag across all operations for stable path sorting
* Update MimeTypesTransformer tests for operation transformer interface
* Reference dotnet/aspnetcore#66340 in ReplaceOpenApiSchemaService docs
* Add unit tests to verify OpenApiSchemaServiceExtensions usage of internal types.
* Bumped Microsoft.AspNetCore.OpenApi from 10.0.4 to 10.0.6 to match Directory.Packages.props and removed unnecessary Swashbuckle reference.
* Fix indentation.
* Defensively handle a non-integer status code response key in ResponseHeaderTransformer.
* Use TryGetValue in RequireNonNullablePropertiesSchemaTransformer to avoid potential KeyNotFoundException.
* Additional tests and clarifying comments.
* Revert accidental local dev changes to Program.cs, Web.UI.csproj and StaticAssets.csproj.
* Tighten visibility of OpenAPI configuration and transformer classes to internal
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Reduce user start node tree filtering code duplication
Extract shared start node filtering logic from UserStartNodeTreeControllerBase
into a dedicated service hierarchy (IUserStartNodeTreeFilterService and
domain-specific implementations for documents and media).
Existing constructor signatures and protected members are preserved as
obsolete to maintain backward compatibility for external consumers.
* Disambiguate DI constructor resolution for tree controllers
Adds obsolete constructors accepting both the legacy dependencies and the new IDocument/IMediaStartNodeTreeFilterService to the eight concrete tree controllers and to MediaTreeControllerBase. These serve as a superset constructor that lets the DI container unambiguously resolve a single constructor, since the new and existing obsolete constructors have non-subset parameter sets and [ActivatorUtilitiesConstructor] is not honoured by CallSiteFactory at ServiceProvider validation time.
* Address review feedback
- Change constructors on DocumentStartNodeTreeFilterService and
MediaStartNodeTreeFilterService from public to internal (classes are
already internal).
- Add [EditorBrowsable(Never)] to the disambiguation constructors so
IDEs hide them from autocomplete.
- Add inline comments explaining the empty-array fallback in the
obsolete GetUserStartNodeIds/GetUserStartNodePaths overrides.
* Revert filter service constructors to public
DI container requires public constructors for activation, even on
internal classes. Reverts the internal change from the previous commit.
* Add unit tests for UserStartNodeTreeFilterService
Tests ShouldBypassStartNodeFiltering (root access, data type ignore,
no access), MapWithAccessFiltering (access/no-access/missing entities),
and delegation to IUserStartNodeEntitiesService for root, child and
sibling filtering including mixed access scenarios.
* Simplify obsolete-ctor path on document and media tree controllers (#22546)
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Implement ElementCacheService with HybridCache backing and database cache support
Fully implements ElementCacheService as the elements equivalent of DocumentCacheService,
backed by Microsoft HybridCache (L1 in-memory + L2 distributed) with database cache table
persistence via cmsContentNu.
Key changes:
- ElementCacheService: full implementation with HybridCache, draft/published separation,
converted element L0 cache, cache tagging, preview service support, and seeding infrastructure
- IDatabaseCacheRepository: added element CRUD methods (Get/Refresh/Rebuild) with SQL queries
using ElementDto/ElementVersionDto
- IContentCacheService: extracted common base interface shared by Document, Media and Element
cache services (8 shared methods including Seed, Rebuild, memory cache operations)
- CacheRefreshingNotificationHandler: added element notification handling, content type changes
now route to element or document service based on IsElement, refactored to single-pass
classification with shared RefreshCacheForContentTypeChanges method
- ElementRefreshNotification: new notification wired to ElementRepository.OnUowRefreshedEntity
- Renamed document-specific methods for clarity (GetContentSource -> GetDocumentSource,
RefreshContent -> RefreshDocument, CreateContentNodeKit -> CreateDocumentNodeKit,
RebuildContentDbCache -> RebuildDocumentDbCache)
- Renamed shared DTOs (CacheRebuildDocumentDto -> CacheRebuildPublishableContentDto) since
they're used by both documents and elements
- Extracted shared RebuildPublishableDbCache method to eliminate duplication between document
and element rebuild logic
* Add element navigation service, publish status tracking, and breadth-first seeding
Adds the infrastructure needed for element cache seeding:
- ElementNavigationService: provides tree traversal for elements, following the
same pattern as DocumentNavigationService/MediaNavigationService
- Split PublishStatusService into an abstract base class with DocumentPublishStatusService
and ElementPublishStatusService subclasses, each with their own interfaces
(IDocumentPublishStatusQueryService, IElementPublishStatusQueryService, etc.)
- ElementBreadthFirstKeyProvider: seeds the element cache on startup by traversing
the element tree breadth-first, filtering out unpublished elements
- Element publish status is initialized on startup and kept in sync via
ElementCacheRefresher
- Old IPublishStatusQueryService/IPublishStatusManagementService interfaces kept
as obsolete for backward compatibility
- Non-breaking constructor changes for ContentCacheRefresher, DocumentUrlService,
ApiContentRouteBuilder via obsolete constructor overloads
* Fix element CacheNodeFactory to set IsDraft from preview parameter
CacheNodeFactory.ToContentCacheNode(IElement, bool preview) was hardcoding
IsDraft = false instead of using the preview parameter. This caused
RefreshElementAsync to never write draft cmsContentNu rows, because
DatabaseCacheRepository.RefreshElementAsync skipped the draft write when
IsDraft was false.
* Use ElementTree lock instead of ContentTree in ElementCacheService
RefreshMemoryCacheAsync was using Constants.Locks.ContentTree instead of
Constants.Locks.ElementTree for the read lock.
* Add ElementCacheServiceTests and fix PublishStatusServiceTests for abstract base
- ElementCacheServiceTests: 9 integration tests covering draft/published retrieval,
rebuild, delete, and RefreshElementAsync behavior
- Updated PublishStatusServiceTests to use DocumentPublishStatusService instead of
the now-abstract PublishStatusService
* Add IPublishedElementCache facade for public element cache access
Introduces the public-facing element cache interface and implementation,
following the same pattern as IPublishedContentCache/IPublishedMediaCache.
- IPublishedElementCache: async-only interface (no legacy sync methods)
- ElementCache: facade delegating to IElementCacheService
- Added Elements property to ICacheManager, IUmbracoContext, and their
implementations
* Add ElementHybridCacheTests and ElementHybridCacheElementTypeTests
Integration tests exercising the full element cache pipeline via
IPublishedElementCache:
ElementHybridCacheTests (7 tests):
- Draft/published retrieval by key
- Unpublished element not accessible without preview
- Draft of published element accessible
- Updated draft element reflects changes
- Deleted element removed from cache
- Element name accessible
ElementHybridCacheElementTypeTests (3 tests):
- Structural type change removes property from cached element
- Non-structural type change preserves property values
- Element removed from cache when element type is deleted
* Fix element navigation to include containers and support breadth-first seeding
The element tree contains both elements and containers (folders) with different
object types. The navigation service now queries both object types to build
the full tree hierarchy.
- Added multi-objectType overloads to INavigationRepository and
ContentNavigationRepository using LEFT JOIN to support nodes without
content rows (containers)
- Single-objectType methods now delegate to the multi-objectType implementation
- ElementNavigationService queries both Element and ElementContainer object types
- ElementBreadthFirstKeyProvider traverses containers without seeding them,
only counting published elements toward the seed limit
- Added ElementBreadthFirstKeyProviderTests (9 tests) including container
traversal scenarios
* Add ElementContentTypeSeedKeyProvider for content-type-based element seeding
Seeds elements whose content types match the configured CacheSettings.ContentTypeKeys,
mirroring the existing ContentTypeSeedKeyProvider for documents. Both providers read
from the same configuration list — document type keys seed documents, element type
keys seed elements.
* Fix ContentNavigationServiceTest mocks for multi-objectType repository overload
The single-type GetContentNodesByObjectType(Guid) now delegates to the
multi-type overload. Updated test mocks to match the IEnumerable<Guid>
signature, verifying exactly one key containing Constants.ObjectTypes.Document.
* Skip Cannot_Get_Published_Again_After_Trashing test
Trashing does not clear the published cache — this is a pre-existing issue
that also affects documents. When a cached item is trashed, the HybridCache
entry remains because RefreshMemoryCacheAsync does not remove entries when
the database returns null for trashed items.
* Replace unsafe casts with StaticServiceProvider in obsolete constructors
The obsolete constructors in ApiContentRouteBuilder and DocumentUrlService
were using direct casts from IPublishStatusQueryService to
IDocumentPublishStatusQueryService, which would fail at runtime for
external consumers compiled against pre-v18 binaries. Use
StaticServiceProvider.Instance.GetRequiredService instead, consistent
with the pattern in ContentCacheRefresher.
* Remove duplicate XML doc summary in GetElementCultureDataForNodes
* Add obsolete constructors for backward compatibility
Preserve the old constructor signatures for CacheManager,
NavigationInitializationNotificationHandler, and
PublishStatusInitializationNotificationHandler so that external
consumers compiled against pre-element-cache versions don't break.
New dependencies are resolved via StaticServiceProvider.
* Pass cancellationToken to ExistsAsync in ElementCacheService.SeedAsync
* Fix DocumentUrlServiceTests to use IDocumentPublishStatusQueryService
* Trigger Build
* Address PR review feedback
- Rename HandlePublishedAsync to HandlePublishStatusAsync in
ContentCacheRefresher for consistency with ElementCacheRefresher
- Make ElementCacheRefresher.HandlePublishStatusAsync async to align
with ContentCacheRefresher's pattern
- Replace inline comments with #region blocks in IDatabaseCacheRepository
- Fix double enumeration in DocumentCacheService.SeedAsync and
ElementCacheService.SeedAsync by materializing to List before logging
* Invalidate element cache entries when trashed
Apply the same fix from #22451 (documents/media) to elements:
- ElementCacheService.RefreshElementAsync: early-return for trashed
elements, deleting from the database cache and removing from memory.
- ElementCacheService.RefreshMemoryCacheAsync: add symmetric else
branches so memory cache entries are removed when the database cache
has no corresponding draft or published node (self-healing).
- Re-enable Cannot_Get_Published_Again_After_Trashing integration test.
* Move element trash cache tests to ElementHybridCacheTests
Move Cannot_Get_Trashed_As_Published and
Cannot_Get_Published_Again_After_Trashing from
ElementPublishingServiceTests to ElementHybridCacheTests where they
belong — these test cache invalidation, not publishing behavior.
Add Cannot_Get_Published_Elements_After_Folder_Trashed to verify that
trashing an element folder clears its child elements from the published
cache.
* Add element hybrid cache variant tests
Add ElementHybridCacheVariantsTests covering culture variant behavior
for the element cache: variant property values per culture, invariant
property consistency across cultures, single culture updates, single
culture publishing, and draft access to both cultures.
Add isElement parameter to
CreateContentTypeWithTwoPropertiesOneVariantAndOneInvariant to support
creating variant element types without a separate builder method.
* Rename IPublishedElementCache.GetByIdAsync to GetByKeyAsync
Align with the codebase convention where Id refers to integer
identifiers and Key refers to GUID identifiers.
* Align IDocumentPublishStatusQueryService method names with element equivalent
Add IsPublished and IsPublishedInAnyCulture to
IDocumentPublishStatusQueryService to match
IElementPublishStatusQueryService naming.
Keep IsDocumentPublished and IsDocumentPublishedInAnyCulture as obsolete
default implementations delegating to the new methods, since
IPublishStatusQueryService (which exposes these names) ships on main.
Update all internal callers to use the new names.
* Keep INNER JOIN for document/media navigation queries
Only use LEFT JOIN when the query includes container types (e.g.
element containers) which don't have umbracoContent rows. Documents
and media always have content rows, so INNER JOIN preserves query
optimizer hints for those queries.
* Consolidate breadth-first seed key provider logic into base class
Make GetSeedKeys virtual on BreadthFirstKeyProvider and introduce
ShouldSeed and ShouldTraverseChildren hooks so subclasses only need
to override filtering logic instead of duplicating the entire
traversal.
- Document: overrides ShouldSeed to filter unpublished nodes
- Element: overrides ShouldSeed + ShouldTraverseChildren (always
traverse, since containers may have published children)
- Media: uses base defaults (seed and traverse everything)
Removes the 'new' hiding pattern and the V16 TODO.
* Revert "Rename IPublishedElementCache.GetByIdAsync to GetByKeyAsync"
This reverts commit 139d66776f.
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Document Types: Prevent disabling isElement when elements of that type exist
Mirrors the existing document-to-element guard: switching an element type
to a document type is now blocked when elements of that type exist. Adds
ElementToDocumentHasNoContentAsync to IElementSwitchValidator and a new
ContentTypeOperationStatus.InvalidElementFlagElementHasContent mapped to
a BadRequest in the document type controller.
* Address PR review feedback for isElement guard
Extract shared HasNoContentNodesAsync helper in ElementSwitchValidator
to deduplicate DocumentToElement and ElementToDocument checks. Make
WithAllowedInLibrary conditional on isElement in test setup.
* Add end-to-end integration tests for element switch validation
Add three tests to ContentTypeEditingServiceTests that verify
UpdateAsync returns the correct operation status when element
flag changes are blocked: document-to-element with existing
content, element-to-document with existing elements, and
element-to-document when used in block structures.
* Remove default interface implementation for ElementToDocumentHasNoContentAsync
Per review feedback: custom implementations of IElementSwitchValidator are unlikely, and a default implementation hides the fact that changes to the real implementation would need to be mirrored here. Accept the small breaking change for a clearer upgrade path.
* Cache: Invalidate published cache entries when content or media is trashed
Trashed content and media were remaining in the published cache because
ContentRefreshNotification/MediaRefreshNotification wrote the trashed
entities back into the cache, and ContentCacheRefresher.HandleMemoryCache
could not resolve the branch descendants after HandleNavigation had moved
them to the recycle bin.
- DocumentCacheService.RefreshContentAsync / MediaCacheService.RefreshMediaAsync:
early-return for trashed entities, deleting from the database cache and
removing from the local memory cache.
- DocumentCacheService.RefreshMemoryCacheAsync / MediaCacheService.RefreshMemoryCacheAsync:
added symmetric else branches so memory cache entries are removed when the
database cache has no corresponding draft or published node (self-healing).
- ContentCacheRefresher.HandleMemoryCache: added a bin fallback to
TryGetDescendantsKeys so broadcasted RefreshBranch payloads can resolve
descendants moved to the recycle bin on load-balanced servers.
- Integration tests covering trashed content and media cache invalidation.
* Cache: Add tests for restoring trashed content and media
Verifies that restored content is back in the draft cache (but not the
published cache, since restore does not republish) and that restored
media is back in the cache.
* Address PR review feedback
- Add bin fallback to MediaCacheRefresher.HandleMemoryCache for
consistency with ContentCacheRefresher.
- Remove redundant [Test] attributes alongside [TestCase].
* Apply suggestions from code review
Co-authored-by: Andy Butland <abutland73@gmail.com>
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
Fix ElementPickerValueConverterTests build by passing IPropertyRenderingContextAccessor
The PublishedProperty constructor was updated to take an
IPropertyRenderingContextAccessor as its 4th argument, but
ElementPickerValueConverterTests was not updated, breaking the
Umbraco.Tests.UnitTests build.
MediaBreadthFirstSeedCount was initialized with StaticDocumentBreadthFirstSeedCount
instead of StaticMediaBreadthFirstSeedCount, mismatching its [DefaultValue] attribute.
* Add PublishedCultures and UnpublishedCultures to ElementCacheRefresher.JsonPayload
Adds culture-specific publishing details to the element cache refresher payload,
matching the existing ContentCacheRefresher.JsonPayload structure. Also replicates
the performance optimization from #21415 by only clearing partial view cache when
there are actual publish/unpublish culture changes, and fixes the Remove change
type check to use HasType instead of equality (flags enum).
* Reuse content cache logic for partial view cache clearing
---------
Co-authored-by: kjac <kja@umbraco.dk>
* Resolve and persist element start node IDs when updating a user
The UpdateAsync method in UserService only resolved Document and Media
start node keys to IDs, completely ignoring ElementStartNodeKeys from
the update model. This caused element start node configuration to be
silently lost on user save.
* Add ElementStartNodeNotFound status and fix XML doc for MapUserUpdate
Introduces a dedicated ElementStartNodeNotFound operation status to
distinguish missing element start nodes from missing element items in
other operations, consistent with ContentStartNodeNotFound and
MediaStartNodeNotFound. Also adds the missing XML doc param for
startElementIds on MapUserUpdate.
* Add blank line to re-trigger the build.
---------
Co-authored-by: kjac <kja@umbraco.dk>
Remove unused recycleBin keys from XML language files
The recycleBin area contained keys (contentTrashed, mediaTrashed,
elementTrashed, elementContainerTrashed, itemCannotBeRestored,
itemCannotBeRestoredHelpText, wasRestored) that are no longer
referenced by any backend code since the audit logging was removed
from RelateOnTrashNotificationHandler in #21481.
* Add flag support for pending changes and scheduled publish
Add entity sign manifests, tree item rendering, and flag provider
support so element tree items display pending changes (pencil) and
scheduled publish (clock) icons, mirroring the existing document
behavior.
Refactor flag providers and presentation factories to reduce
duplication, and move shared IHasFlags implementation into
PublishableVariantResponseModelBase.
* Fix HasScheduleFlagProvider test mocks to match refactored per-item lookups
* Extract PublishableVariantItemResponseModelBase to deduplicate variant item models
* Extract shared base class from Document/Element presentation factories
Introduce PublishableContentPresentationFactoryBase to eliminate code
duplication between DocumentPresentationFactory and ElementPresentationFactory.
Add async alternatives (CreateVariantsItemResponseModelsAsync,
CreateItemResponseModelAsync, PopulateFlagsAsync) and migrate callers in
async contexts to use them. Sync callers in tree/recycle bin controllers
use .GetAwaiter().GetResult() to avoid breaking changes in base classes.
Add IPublishableContentEntitySlim overload to DocumentVariantStateHelper
to unify the identical IDocumentEntitySlim/IElementEntitySlim overloads.
Make RelationTypePresentationFactory properly async with Task.WhenAll.
* Fix flags fallback to use empty array instead of empty string
* Acceptance Tests: Fix element tree item locator to match both elements and folders
The element tree renders umb-element-tree-item for elements but
umb-default-tree-item for folders. Update the E2E test helper locator
to use :is() to match both custom element types.
* Split HasScheduleFlagProvider into document and element providers
Address PR review feedback:
- Split HasScheduleFlagProvider into HasDocumentScheduleFlagProvider and
HasElementScheduleFlagProvider with a shared HasScheduleFlagProviderBase
- Fix N+1 query: use batch GetContentSchedulesByKeys instead of per-item
GetContentScheduleByContentId
- Add GetContentSchedulesByKeys to IPublishableContentService and implement
in PublishableContentServiceBase, removing the duplicate from IContentService
and ContentService
- Inject TimeProvider into base class, replacing DateTime.Now with
_timeProvider.GetUtcNow()
- Split tests to match new provider structure and verify batch retrieval
* Make tree and recycle bin mapping methods async
Remove .GetAwaiter().GetResult() calls introduced by the element flag
support changes. Rename MapTreeItemViewModel to MapTreeItemViewModelAsync
and MapRecycleBinViewModel to MapRecycleBinViewModelAsync across all
tree and recycle bin controllers, properly awaiting async factory calls.
* Extract Task.WhenAll select expressions into named variables
* Add missing XML docs to async methods on IDocumentPresentationFactory
* Fix DateTime vs DateTimeOffset comparison in schedule flag provider
Compare schedule.Date against _timeProvider.GetUtcNow().UtcDateTime
instead of the DateTimeOffset directly, avoiding implicit conversion
issues with DateTimeKind.Unspecified.
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Add missing notifications to element container and element editing services
Add ElementDeletingNotification and ElementTreeChangeNotification to
ElementContainerService for EmptyRecycleBin, Move, MoveToRecycleBin,
and Delete operations, aligning with ContentService notification patterns.
Add ElementTreeChangeNotification to ElementEditingService for Move
and Copy operations.
Refactor DeleteDescendantsLocked to return deleted elements and
DeleteItem to return the deleted entity for use in tree change
notifications.
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
* Split content type validation for create and update to allow saving elements no longer permitted in library
* Add integration test for element update after AllowedInLibrary toggle
Verify that ElementEditingService.UpdateAsync succeeds when the content
type's AllowedInLibrary flag is set to false after the element was
created, covering the split validation introduced for create vs update.
* Move content type validation into TryGetAndValidateContentType override
Eliminate redundant content type lookups in CreateAsync and UpdateAsync
by moving the IsElement/AllowedInLibrary check into the
TryGetAndValidateContentType override, which distinguishes create from
update by checking if the model is a ContentCreationModelBase.
* Use Assert.Multiple for element property assertions in update test
* Extract IsAllowedLibraryElement static method for readability
* Add permission-based filtering to element tree endpoints
The element tree endpoints now filter results based on the current
user's browse permissions via a new IElementPermissionFilterService,
mirroring the existing document tree behavior.
Also extracts shared filtering logic from DocumentPermissionFilterService
into a PermissionFilterServiceBase to avoid duplication.
* Add unit tests for ElementPermissionFilterService
* Replace document-specific inheritdoc with neutral XML docs in PermissionFilterServiceBase
* Fix GetPermissionsAsync to use the provided objectTypes parameter instead of hardcoded Document type
---------
Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
Set AllowedInLibrary on element content type in permission tests
The GetElementPermissionsCurrentUserControllerTests were failing because the
test setup created an element content type without setting AllowedInLibrary
to true. The ElementEditingService.TryGetAndValidateContentType method now
requires both IsElement and AllowedInLibrary to be true for element creation.
* Handle element saving and copying notifications in complex property editors
Extend ComplexPropertyEditorContentNotificationHandler to also handle
ElementSavingNotification and ElementCopyingNotification, ensuring that
block property key replacement (BlockList, BlockGrid, RichText) is
applied to elements the same way it is for content.
* Add integration tests for element copy with block editors
Test that block keys are regenerated and block structure is preserved
when copying elements with BlockList, BlockGrid, and RichText editors,
for both invariant and culture-variant content.
* Add scheduled publishing support for elements
Move PerformScheduledPublish from IContentService to the shared
IPublishableContentService<T> interface so both documents and elements
support scheduled publishing.
Filter ClearSchedule and HasContentForRelease/Expiration queries in
PublishableContentRepositoryBase by NodeObjectTypeId to prevent document
and element schedules from interfering with each other.
Update ScheduledPublishingJob to process both document and element
schedules, and add integration tests verifying cross-entity isolation.
* Simplify ScheduledPublishingJob.ExecuteAsync
Extract duplicated scheduled publishing logic into a generic helper
method and include the entity type in the log message.
* Add element version cleanup to the content version cleanup background job
The existing ContentVersionCleanupJob only cleaned up document versions.
Element versions were left to accumulate despite having the same cleanup
service infrastructure available. This extends the job to also clean up
element versions using the same configuration toggle and schedule.
* Use PascalCase for structured logging names.
* Fixed code warnings and duplicate line breaks.
* Cleaned up usings.
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Add missing AuditType.Copy audit log for element copy operations
Make the abstract Copy method in ContentEditingServiceBase async and
accept a Guid userKey instead of int userId, allowing the element
copy implementation to use the audit service directly. Add the
missing _auditService.AddAsync(AuditType.Copy, ...) call in
ElementEditingService.CopyAsync to match the document equivalent
in ContentService.Copy.
* Fix copy audit log to record against the original element
The copy audit entry was being logged against the new copy's ID
instead of the original element's ID, inconsistent with how
documents handle copy audit logging.
* Add audit log retrieval endpoint for elements and wire up frontend
Add GET /{id:guid}/audit-log endpoint following the document audit
log pattern. Wire up the existing frontend data source to call the
new API, add element-specific localization strings, and remove the
unsupported sort audit type.
Fix GUID repository cache key prefix in PublishableContentRepositoryBase
The merge of the GUID cache key collision fix (9ea0520) applied
IContent-specific changes from DocumentRepository's nested class, but
in v18/dev this code lives in the generic base class. Two issues:
- EntityByGuidReadRepository.GetCacheKey used the "uRepo_" prefix
while GuidReadRepositoryCachePolicy looks up entries with "uRepoGuid_",
causing PopulateCacheByKey to insert under a key the policy never finds.
- PersistUpdatedItem cleared GetGuidKey<IContent> instead of
GetGuidKey<TEntity>, so ElementRepository would clear the wrong key.
* Add AllowedInLibrary flag to content types
Add a new boolean property AllowedInLibrary across all layers to
indicate whether a content type is allowed in the library. This is
only meaningful for element types (IsElement = true).
Changes span the core domain model, Management API request/response
models, persistence DTOs/mappers/factories, and a database migration
to add the column to the cmsContentType table.
* Enforce AllowedInLibrary in ElementEditingService.CreateAsync
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(api): add AllowedInLibrary filter to document type search endpoint
Add allowedInLibrary query parameter to GET /document-type/search,
following the same pattern as the existing isElement filter. The old
SearchAsync overload without the parameter is preserved as a default
interface method and marked obsolete (scheduled for removal in v19).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(api): regenerate OpenApi.json and backoffice client types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(api): make search query parameter nullable for document type search
Allow the document type search endpoint to be called without a text
query, enabling filter-only usage (e.g. filtering by isElement and
allowedInLibrary without requiring a search term).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(api): replace search allowedInLibrary filter with dedicated endpoint
Revert the search endpoint changes (IContentTypeSearchService, controller)
and instead add a dedicated GET /document-type/allowed-in-library endpoint
that follows the AllowedAtRoot pattern. This ensures IContentTypeFilter
support and a cleaner API separation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(api): add integration tests for GetAllAllowedInLibraryAsync and AllowedInLibraryDocumentTypeController
Add service-level tests verifying correct filtering by IsElement + AllowedInLibrary, pagination, and IContentTypeFilter integration. Add controller-level authorization tests for the allowed-in-library endpoint.
* Map allowedInLibrary through content type data sources
Add allowedInLibrary to UmbContentTypeModel and map it consistently
across document, media, and member type data sources for scaffold, read,
create, and update operations.
* Add AllowedInLibrary support to test builders and set it in all element tests
ElementEditingService.CreateAsync checks contentType.AllowedInLibrary and
returns NotAllowed if false. All element test types were missing this flag,
causing test failures. Adds IWithAllowedInLibraryBuilder interface, extension
method, and sets AllowedInLibrary=true on all element type creation in tests.
* Also enforce IsElement check when creating elements in the library
* Set IsElement and AllowedInLibrary on ElementPublishingServiceTests content types
* Remove AllowedInLibrary from document type tree item response model
The AllowedInLibrary property is not relevant for tree items and is not
used by the frontend. This removes it from the tree item model, its
mapping in the tree controller, and regenerates the OpenAPI spec and
TypeScript client accordingly.
* Refactor element content type validation into base class override
Make TryGetAndValidateContentType protected virtual in
ContentEditingServiceBase and override it in ElementEditingService to
check IsElement and AllowedInLibrary. This guards both create and update
paths (previously only create was guarded) and eliminates duplicate
ContentTypeNotFound handling.
Enable the previously-ignored
Cannot_Create_Element_Based_On_NonElement_ContentType test and add a new
test for the AllowedInLibrary check.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Fix EntityTypeContainerService.UpdateAsync using wrong AuditType
UpdateAsync was logging AuditType.New instead of AuditType.Save,
causing container update operations to be recorded as creations
in the audit log.
* Fix GetPermissionsAsync to use path-based permission inheritance
GetPermissionsAsync was querying only explicit per-node permissions,
ignoring the ancestor-based inheritance model. Nodes without explicit
permissions would get group defaults instead of inheriting from their
nearest ancestor with explicit permissions. This caused tree filtering
to hide child nodes that should have been visible.
Replace per-node permission queries with GetPermissionsForPath which
walks the entity path to resolve inherited permissions correctly. Also
pass object types through to enable batched entity lookups.
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Optimise GetPermissionsAsync.
* Add benchmark test.
* Add benchmark test.
* Add integration tests for default and isolated permission resolution
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Elements: Add DisableDeleteWhenReferenced support and fix delete notifications
- Add DisableDeleteWhenReferenced check to ElementContainerService delete operations
- Fire ElementDeletedNotification and EntityContainerDeletedNotification per item during descendant deletion
- Fix potential infinite loop when items are skipped due to being referenced
- Simplify EmptyRecycleBinAsync to use DeleteDescendantsLocked directly
- Use path descending ordering for consistent deletion order (children before parents)
- Add test for descendant delete notifications
* Elements: Fix EmptyRecycleBin pagination with DisableDeleteWhenReferenced
When DisableDeleteWhenReferenced is enabled and some items are skipped,
the standard skip/take pagination breaks. This change:
- Adds SqlLessThan/SqlGreaterThan SQL expression extensions for string
comparison in LINQ queries
- Uses path-based cursor pagination instead of skip/take
- Tracks protected paths to prevent deleting containers that have
referenced descendants
- Adds ElementRecycleBin to UmbracoObjectTypes enum
* Tests: Add DisableUnpublishWhenReferenced tests for elements
Verify that DisableUnpublishWhenReferenced works correctly for elements
(inherited from ContentPublishingServiceBase):
- Cannot unpublish an element that is being referenced
- Can unpublish an element that is doing the referencing
* Elements: Remove redundant Trashed filter from DeleteDescendantsLocked
The Trashed filter was redundant because:
- EmptyRecycleBinAsync only operates on items under the recycle bin root
- DeleteFromRecycleBinAsync requires containers to be trashed, and all
descendants are marked as trashed when moved to recycle bin
Removing the filter simplifies the query and handles edge cases better.
* Elements: Add proper ProblemDetails responses for publish/unpublish endpoints
Move ContentPublishingOperationStatusResult from DocumentControllerBase to
ContentControllerBase so it can be shared. Add ElementPublishingOperationStatusResult
to ElementControllerBase and update PublishElementController and
UnpublishElementController to return proper error responses instead of empty
BadRequest() when operations fail (e.g., when DisableUnpublishWhenReferenced is enabled).
* Refactor: Use abstract EntityName for content controller error messages
Replace hardcoded "document" terminology in shared ContentControllerBase
error messages with an abstract EntityName property, so each subclass
(document, element, media, member, etc.) provides context-appropriate
error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix: Check DisableUnpublishWhenReferenced when moving elements to recycle bin
ElementEditingService.MoveToRecycleBinAsync was missing the reference
check that ContentEditingService already performs for documents. This
allowed referenced elements to be moved to the recycle bin even when
DisableUnpublishWhenReferenced was enabled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Prevent moving container to recycle bin when descendants are referenced
Add server-side validation to ElementContainerService.MoveToRecycleBinAsync
that checks for referenced descendants when DisableUnpublishWhenReferenced
is enabled. Uses ITrackedReferencesService.GetPagedDescendantsInReferencesAsync
as an upfront check before any move processing begins.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Fix element delete blocked by trash-tracking relation
ElementEditingService was missing the RelateParentOnDeleteAlias
override, so the "relate parent on delete" relation created when
trashing was not excluded from the reference check. This caused
"Cannot delete a referenced content item" when
DisableDeleteWhenReferenced was enabled, even for unreferenced
elements.
The OpenAPI definition and backoffice TypeScript client were out of
sync with recent Management API changes already on v18/dev. Regenerated
to bring them up to date.
chore(api): regenerate OpenApi.json and backoffice client SDK
The OpenAPI definition and backoffice TypeScript client were out of
sync with recent Management API changes already on main. Regenerated
to bring them up to date.
* Elements: Add restore from recycle bin functionality
- Add RestoreAsync to IElementEditingService and ElementEditingService
- Add RestoreAsync to IElementContainerService and ElementContainerService
- Add RestoreElementRecycleBinController and RestoreElementFolderRecycleBinController API endpoints
- Add TryGetContainedObjectType to EntityContainer for graceful handling of non-container types
- Update EntityContainerRepository to return null instead of throwing for non-container entities
- Add comprehensive integration tests for element and container restore operations
* Refactor: Remove entity return from Move/Restore/MoveToRecycleBin methods
Simplify the return types of IElementEditingService and IElementContainerService
move operations to return only the operation status instead of the entity.
These operations don't meaningfully change entity data (just location/state),
and no consumers were using the returned entities. Callers can use GetAsync
if they need the updated entity afterward.
* Fix: Capture original path before move for restore relation cleanup
The MoveEventInfo.OriginalPath was incorrectly set to the element's path
after the move, causing DeleteOriginalParentRelationsOnRestore to fail
because the path no longer contained the recycle bin path prefix.
* Tiny little formatting
---------
Co-authored-by: kjac <kja@umbraco.dk>
* Tests: Fix permission controller tests to use correct entity keys
The GetDocumentPermissionsCurrentUserController, GetMediaPermissionsCurrentUserController,
and GetPermissionsCurrentUserController tests were incorrectly creating user data and
passing user keys to the GetPermissions method. These controllers expect document/media
keys, not user keys.
Updated the tests to create the appropriate content/media types and entities, then pass
the correct keys to properly test the permission endpoints.
* Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Elements: Add reference tracking and recycle bin query support
- Add Element reference tracking API endpoints (referenced-by, are-referenced, referenced-descendants)
- Add Element recycle bin original-parent and referenced-by endpoints
- Add ElementReferenceResponseModel and ElementContainerReferenceResponseModel
- Add IElementRecycleBinQueryService for querying original parents of trashed elements
- Add Element relation type constants for parent tracking on delete
- Add translation strings for Element recycle bin operations
- Refactor RelateOnTrashNotificationHandler to reduce code duplication using generic helper methods
- Add Element and ElementContainer support to RelateOnTrashNotificationHandler
* Elements: Register Element notification handlers for relation tracking
Add Element and EntityContainer notification handlers for:
- RelateOnTrashNotificationHandler (move to/from recycle bin)
- ContentRelationsUpdate (track element content relations)
* Elements: Remove ReferencedDescendantsElementController
Elements are leaf nodes in the folder structure and cannot have
descendants, making this endpoint unnecessary.
* Elements: Fix ElementPickerPropertyEditor reference extraction
The element picker stores element IDs as Guids, not as UDI strings.
Updated GetReferences to deserialize as Guid array and create UDIs
from the Guid values.
* Elements: Fix TrackedReferencesRepository to include Element published state
Add LEFT JOIN to ElementDto and use COALESCE to get the published state
from either DocumentDto or ElementDto, fixing the issue where Element
references returned published = null.
* Elements: Add ReferencedDescendantsElementFolderController
Add endpoint to get referenced descendants of an element folder.
Unlike elements (which are leaf nodes), folders can have descendants
that may be referenced elsewhere.
* Elements: Add integration tests for Element reference tracking
Add TrackedReferencesServiceElementTests covering:
- GetPagedRelationsForItemAsync for Elements
- GetPagedRelationsForRecycleBinAsync for Elements
- GetPagedKeysWithDependentReferencesAsync for Elements
- GetPagedDescendantsInReferencesAsync for Element containers
* Elements: Add management API controller permission tests for Element reference endpoints
- Add ReferencedByElementControllerTests for element referenced-by endpoint permissions
- Add AreReferencedElementControllerTests for element are-referenced endpoint permissions
- Add ReferencedDescendantsElementFolderControllerTests for folder descendants endpoint permissions
- Fix GetManagementApiUrl to respect [FromQuery(Name="...")] attribute for proper URL generation
* Elements: Split OriginalParentElementRecycleBinController into two controllers
Split the controller to follow the controller-per-operation pattern,
consistent with DeleteElementRecycleBinController and
DeleteElementFolderRecycleBinController.
- OriginalParentElementRecycleBinController: for elements
- OriginalParentElementFolderRecycleBinController: for folders
* Elements: Fix user fallback for audit logging in RelateOnTrashNotificationHandler
Update the handler to properly resolve user key for audit logging, using a switch expression
to handle different entity types. Also sets CreatorId on EntityContainer creation.
* Tests: Fix duplicate query parameter in ItemElementItemControllerTests
Remove the ClientRequest() override that was appending a duplicate id query
parameter to the URL. The base MethodSelector already includes the element key
which gets converted to the query parameter by GetManagementApiUrl, causing
the URL to become ?id=<guid>?id=<guid> and model binding to fail.
* Tests: Fix permission controller tests to use correct entity types
These tests were passing on the base branch only because the URL was being
constructed incorrectly (missing query parameters). After be399134f8 fixed
the GetManagementApiUrl helper to properly include FromQuery parameters,
the tests now correctly build URLs and revealed that they were using user
keys instead of the expected document/media/element node keys.
Updated tests to create the appropriate entity type (document, media, or
element) and pass its key to the permission endpoints.
* Tests: Update RelationTypeRepositoryTest for new element relation types
Update expected counts and fix hardcoded ID lookup after new element
reference tracking relation types were added to the system:
- umbElement (RelatedElement)
- relateParentElementContainerOnElementDelete
- relateParentElementContainerOnContainerDelete
Changes:
- Store created test relation type in field to use actual ID instead of
hardcoded ID 9 which shifted when built-in types were added
- Update GetAll expected count from 9 to 12 (9 built-in + 3 test data)
- Update Count query expected from 6 to 8 (aliases starting with "relate")
* Refactor: Rename methods in RelateOnTrashNotificationHandler for clarity
Rename methods and parameters to better describe their purpose:
- DeleteRelationsOnRestore → DeleteOriginalParentRelationsOnRestore
- CreateRelationsOnTrashAsync → CreateOriginalParentRelationOnTrashAsync
- relationTypeAlias → originalParentRelationTypeAlias
- relationTypeName → originalParentRelationTypeName
These names clarify that the methods handle "original parent" relations
used for restoring items from the recycle bin, not all relations.
* Tests: Fix ReferencedDescendantsElementFolderControllerTests expectations
- Use unique folder names to prevent conflicts between test runs
- Add assertion to verify folder creation succeeds
- Correct expected status codes for Editor and Writer to OK (not NotFound)
The NotFound responses were caused by folder creation failures due to
duplicate names, not actual permission restrictions.
* Tests: Add success assertions to Element controller test setup methods
Add Assert.IsTrue checks after service calls in test setup to ensure
test prerequisites are correctly established before running actual tests.
This prevents silent failures in setup from causing misleading test results.
Assertions added for:
- ElementContainerService.CreateAsync (9 tests)
- ElementEditingService.CreateAsync (19 tests)
- ElementEditingService.MoveToRecycleBinAsync (6 tests)
- ElementContainerService.MoveToRecycleBinAsync (3 tests)
* Elements: Fix MapReference to return un-enriched response when entity not found
Return the mapped response model instead of null when the matching entity
cannot be found for enrichment. This preserves basic reference information
even when variant data cannot be loaded, preventing valid references from
being silently dropped.
Also clean up ElementContainerReferenceResponseModel formatting.
* Fix: Guard GetSlimEntities against empty keys to prevent loading all entities
* Fix: Return ParentIsTrashed status when original parent is in recycle bin
* Breaking: Remove duplicate sync notification handler interfaces
Remove INotificationHandler<ContentMovedToRecycleBinNotification> and
INotificationHandler<MediaMovedToRecycleBinNotification> interfaces along
with their obsolete sync Handle methods. Only the async handlers should
be implemented.
* Tests: Simplify TrackedReferencesServiceElementTests
- Simplify assertions in Get_Descendants_In_References test
- Create Element3 after folder creation to avoid unnecessary update
* Revert: Remove changes to be moved to separate PRs
Revert EntityTypeContainerService.CreateAsync CreatorId change and
permission controller test changes - these should be addressed in
separate PRs.
* Revert: Remove GetMediaPermissionsCurrentUserControllerTests changes
This change should be addressed in a separate PR for v17.
* Refactor: Move recycle bin audit logging to services
Move audit logging for recycle bin operations from RelateOnTrashNotificationHandler
to the individual services (ContentService, MediaService, ElementEditingService,
ElementContainerService). This simplifies the notification handler and keeps audit
logging closer to the operations being performed.
- Simplify audit messages to "Moved to recycle bin from parent {parentId}"
- Add AuditMoveToRecycleBin helper methods to Content and Media services
- Add AuditMoveAsync helper methods to Element services
- Remove unused audit dependencies from RelateOnTrashNotificationHandler
- Add obsolete constructor bridge for backwards compatibility
* Refactor: Extract GetParentIdFromPath extension method
Add GetParentIdFromPath string extension to consolidate duplicate logic
for extracting parent ID from entity path strings. This replaces 5
instances of the same path parsing pattern across services and handlers.
- Add GetParentIdFromPath to StringExtensions.Parsing.cs
- Inline audit calls in ContentService, MediaService,
ElementEditingService, and ElementContainerService
- Update RelateOnTrashNotificationHandler to use the new extension
- Add unit tests for the new extension method
* Refactor: Make CreateOriginalParentRelationOnTrash synchronous
Remove unnecessary async from CreateOriginalParentRelationOnTrash since
the method contains no async operations. Update handlers to return
Task.CompletedTask directly.
* Elements: Implement validation for Element editing endpoints
Move ValidateCulturesAndPropertiesAsync and GetCulturesToValidate from
ContentEditingService to ContentEditingServiceBase, enabling reuse in
ElementEditingService.
- Implement ValidateCreateAsync and ValidateUpdateAsync in ElementEditingService
- Update Element API controllers to return validation results properly
- Update all inheriting services (Media, Member, Blueprint) with new params
* Elements: Add validation tests for ElementEditingService
- Add tests for ValidateUpdateAsync and ValidateCreateAsync
- Cover invariant, culture variant, and permission-based validation scenarios
* Fix bad merge
* Removed unused fields
* Removed old editor UI
---------
Co-authored-by: kjac <kja@umbraco.dk>
* Move MediaTree write lock before MediaSavingNotification to prevent deadlock
Fixes a deadlock that could occur when saving multiple media items in parallel
when a MediaSavingNotification handler acquires a MediaTree read lock. The
previous ordering allowed two threads to each acquire read locks in their
notification handlers, then both attempt to upgrade to write locks, causing
a classic lock upgrade deadlock in SQL Server.
By acquiring the write lock before publishing the notification, the deadlock
scenario is avoided. Since the write lock is lazy, it only materializes at the
database level when actual queries are made, so notification handlers doing
in-memory work won't hold the lock.
* Apply same fix to MediaService.Delete method
* Apply same fix to DeleteVersions, DeleteVersion, and Sort methods
* Apply same fix to ContentService methods
Move WriteLock before notifications in:
- Save (single and batch)
- Delete
- DeleteVersions
- DeleteVersion
- Copy
* Apply the same pattern to MemberService.
* Add integration tests to verify the fix.
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
Fix SQL Server deadlock during concurrent document updates
Add ReadLock on ContentTree in RefreshMemoryCacheAsync to prevent
deadlocks between cache refresh SELECT queries and concurrent document
UPDATE operations. The deadlock occurred because the operations accessed
umbracoNode and umbracoDocument tables in different orders.
* fix: add write lock at outer scope to prevent deadlocks in content publishing
Acquire a write lock on ContentTree at the start of PublishAsync to prevent
deadlocks. Previously, inner scopes would acquire read locks first (via
repository operations), then attempt to upgrade to write locks, causing
deadlocks when multiple transactions tried this simultaneously.
By acquiring the write lock at the outer scope, we ensure consistent lock
ordering and prevent the deadlock scenario.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Re-enable lazy locks
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Introduce new content type schema service and models
To be used in the future for content type schema generation.
* Do not fail when content type is not in cache, simply ignore
* Added unit and integration tests
* Fix failing unit tests
* Addressing comments from code review
fix: add write lock at outer scope to prevent deadlocks in content publishing
Acquire a write lock on ContentTree at the start of PublishAsync to prevent
deadlocks. Previously, inner scopes would acquire read locks first (via
repository operations), then attempt to upgrade to write locks, causing
deadlocks when multiple transactions tried this simultaneously.
By acquiring the write lock at the outer scope, we ensure consistent lock
ordering and prevent the deadlock scenario.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Adjust the document type creation flow so that a template can be created for a content type
Add id, name and alias to the request payload to allow creating multiple templates for the same document type
Small adjustments
Remove unused import and unnecessary async
Switched content type template creation to content type controller
Missing constant export
# Conflicts:
# src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts
* Add default implementation for CreateForContentTypeAsync
* Small adjustments from code review
* Introduce InvalidTemplateAlias content type operation status
* Add tests for CreateTemplateAsync and fix alias validation
- Add integration tests for ContentTypeService.CreateTemplateAsync:
- Success case with template association
- NotFound status for non-existent content type
- InvalidTemplateAlias for empty and too-long aliases
- Default template assignment verification
- Fix bug in TemplateService.CreateAsync where alias validation
occurred after GetViewContent call, causing ArgumentNullException
for invalid aliases instead of returning InvalidAlias status
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use EagerWriteLock for long running operations
Switch from WriteLock to EagerWriteLock when acquiring the lock for
long running operations to ensure proper lock acquisition timing.
The contributing documentation still referenced the old `contrib` branch,
which was causing AI tools to incorrectly use it as the base branch for
comparisons. Updated all references to use `main` instead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Use AddComponent for OpenAPI security scheme registration
Fixes security requirements being serialized as empty objects in the
OpenAPI document by using the document's AddComponent method instead
of directly manipulating the SecuritySchemes dictionary.
* Update Swashbuckle to v10
* Regenerate backoffice api client
* Add missing space for consistency
* Simplify nullability check
* Small improvement
Didn't notice that these classes were internal, so tried keeping compatibility, but it wasn't needed.
* Fix failing integration test
* Apply suggestions from code review
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Remove unnecessary comma
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Replace dependency track bom script with devops task
* Introduce new url variable in order to fix new task uri
The initial variable contained the api path (/api) in the URL.
* Generate BOM files on build
* Upload BOM to Dependency Track
* Move Backoffice BOM generation to right after install
The build and/or pack steps are deleting files that are needed for the BOM to be generated properly.
* Split the BOM uploads into different jobs
* Fix wrong usage of parameters
* Move order of dependency track stage
* Fix wrong umbracoVersion value
* Small fixes
* Log curl response headers
* Correct version sent to dependency track
* Adjusted curl flags
* Fix bom file path
* Fix dotnet bom file name
* Add Login UI to dependency track
* Generate BOM for E2E Tests
* Move dependency track stage
* Move acceptance test .env generation to e2e install template
Needed as the post install script is expecting this to exist.
* Use major version if public release
* Missing ')'
* Reverted npm install command changes in static assets project
* Generate BOM files on build
* Upload BOM to Dependency Track
* Move Backoffice BOM generation to right after install
The build and/or pack steps are deleting files that are needed for the BOM to be generated properly.
* Split the BOM uploads into different jobs
* Fix wrong usage of parameters
* Move order of dependency track stage
* Fix wrong umbracoVersion value
* Small fixes
* Log curl response headers
* Correct version sent to dependency track
* Adjusted curl flags
* Fix bom file path
* Fix dotnet bom file name
* Add Login UI to dependency track
* Generate BOM for E2E Tests
* Move dependency track stage
* Move acceptance test .env generation to e2e install template
Needed as the post install script is expecting this to exist.
* Use major version if public release
* Missing ')'
* Reverted npm install command changes in static assets project
* Generate BOM files on build
* Upload BOM to Dependency Track
* Move Backoffice BOM generation to right after install
The build and/or pack steps are deleting files that are needed for the BOM to be generated properly.
* Split the BOM uploads into different jobs
* Fix wrong usage of parameters
* Move order of dependency track stage
* Fix wrong umbracoVersion value
* Small fixes
* Log curl response headers
* Correct version sent to dependency track
* Adjusted curl flags
* Fix bom file path
* Fix dotnet bom file name
* Add Login UI to dependency track
* Generate BOM for E2E Tests
* Move dependency track stage
* Move acceptance test .env generation to e2e install template
Needed as the post install script is expecting this to exist.
* Use major version if public release
* Missing ')'
* Reverted npm install command changes in static assets project
* Store local time zone as UTC and do not throw validation error when stored time zone is different
* Additional fixes when switching between date time editors with and without time zone
* Additional fixes
* Ensure that an update is triggered when the expected value does not match the stored value
This will happen when switching between editors (with and without time zone) or switching between a specific time zone to the editor's local time zone.
* Fix inconsistencies with null and undefined
* Fix inconsistencies between date/time provided to the client and returned in the value converter (when switching between editors)
* Fix unit tests and small bug
* Adjust integration test
* Small improvement
* Update test data
* Adjust logic so that time zone offsets are updated every time the date value changes
* Do not pre-select time zone when switching between unspecified and time zone editors
* Remove Microsoft.CodeAnalysis.CSharp from Infrastructure project
This was only needed for runtime compilation and thus is no longer needed in Infrastructure.
It also caused dependency problems with EF Core Design in previous versions.
* Disable CPM for UI project to better reflect consumers
This will ensure that we face any potential dependency issues consumers are also likely to run into.
* Add `Microsoft.CodeAnalysis.CSharp` reference to `Umbraco.Cms.DevelopmentMode.Backoffice`
* Remove Microsoft.CodeAnalysis.CSharp from Infrastructure project
This was only needed for runtime compilation and thus is no longer needed in Infrastructure.
It also caused dependency problems with EF Core Design in previous versions.
* Disable CPM for UI project to better reflect consumers
This will ensure that we face any potential dependency issues consumers are also likely to run into.
* Add `Microsoft.CodeAnalysis.CSharp` reference to `Umbraco.Cms.DevelopmentMode.Backoffice`
* Add explicit references to Microsoft.CodeAnalysis.* packages to fix conflicts when installing Microsoft.EntityFrameworkCore.Design
This allows consumers to simply install Microsoft.EntityFrameworkCore.Design without having to manually install specific versions to deal with transitive dependency problems.
* Disable CPM for UI project to better reflect consumers
* Update src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Adjust the `JsonBlockValueConverter` to handle conflicts with 'values' property (due to old data schema)
* Simplify code
* Add unit test to verify change.
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
* Adjust the `JsonBlockValueConverter` to handle conflicts with 'values' property (due to old data schema)
* Simplify code
* Add unit test to verify change.
---------
Co-authored-by: Andy Butland <abutland73@gmail.com>
Fix failing SQLServer integration tests
Adjusted the tests so that the created content is retrieved again after creation, instead of using the returned IContent.
This is needed because SQLServer, when using datetime, rounds to the closest .000, .003, or .007, which would cause the comparisons to fail.
We should consider moving away from datetime to datetime2, as the former should be avoided according to Microsoft.
https://learn.microsoft.com/en-us/sql/t-sql/data-types/datetime-transact-sql?view=sql-server-ver17