Compare commits

...
Author SHA1 Message Date
Niels LyngsøandGitHub 246bb4c33d Merge branch 'main' into v17/feature/hook-api 2026-04-13 19:25:14 +02:00
Jacob OvergaardandClaude Sonnet 4.6 fcccd7da54 CI: Use pull_request_target so Claude review runs on fork PRs
pull_request events from forks cannot access OIDC tokens, causing the
job to fail. pull_request_target runs in the base repo context and has
access to secrets/OIDC while still reading the PR diff via the API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:27:18 +02:00
7170b45aec Migrations: Quote names when creating index (closes #22409) (#22410)
* fix raw sql

* clean up

* Use existing syntax property.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-13 11:56:26 +00:00
8f642f24fe Migrations: Consistently handle GUID casing when using SQLite (#22406)
* Ensure MigrationBase formats Guids consistently with NPoco for SQLite

SQLite is case sensitive and doesn't have the concept of uniqueidentifier - Guids are stored as uppercase strings

Add FormatGuid method to SqlSyntaxProvider to centralize the logic

* (Optional) Include default FormatGuid implementation in ISqlSyntaxProvider to make this change non-breaking

* Use ToUpperInvariant for Guids in SQLite

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Tidy up comments, fix existing indentation and add unit tests for GUID formatting.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-13 10:20:38 +00:00
Andy Butland 11fa8a45c8 Merge branch 'release/17.3.3' 2026-04-13 12:12:01 +02:00
Nhu DinhandGitHub bc477c94a9 E2E: QA Updated acceptance tests to match the recent UI changes (#22445)
* Updated tipTapSettings to match the recent changes

* Updated ui helper for insert value/dictionary/partial view button

* Updated api helper for media delivery

* Fixed api helper for verify width and height in vector graphic media
2026-04-13 09:13:50 +00:00
fbc8b605a9 RTE: Block Clipboard label Localization (closes #22412) (#22417)
* localize rte block clipboard entry label

* RTE Block Clipboard: reuse existing localization controller

Avoids alias collision from creating a new UmbLocalizationController on
hosts that already have one. Exposes the base class controller as
protected so subclasses can reuse it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:24:04 +02:00
Jacob OvergaardandClaude Opus 4.6 c56dcfd345 Docs: Clarify PR body must include closing keyword for GitHub auto-close
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:00:32 +02:00
Niels LyngsøandGitHub 1ff33e897c General: Add decoding="async" to relevant IMG-tags (#22428)
add decoding="async" to relevant imgs
2026-04-13 09:47:20 +02:00
Jacob Overgaard cbfbeb4272 devops: bump max-turns from 25 to 50 2026-04-13 09:30:15 +02:00
Niels Lyngsø ac811c2624 hook package + controller + tests 2026-04-11 20:49:42 +02:00
Andy Butland ade77ad00d User Service: Prevent fetching all permissions when no IDs are provided (#22424) 2026-04-11 12:53:34 +02:00
Andy Butland 3275541d92 Bump version to 17.3.3. 2026-04-11 12:50:02 +02:00
Andy ButlandandGitHub 0f4d0a6e67 Basic Authentication: Standalone login page for frontend-only deployments (closes #22144) (#22168)
* Add login for basic authentication without backoffice.

* Add 2FA to basic authentication flow.

* Accessibility improvements.

* Add tests for BasicAuthLoginController.

* Gate controller so only used when basic authentication is enabled.
Use 2FA view even when login page is not configured.

* Add tests for BasicAuthenticationMiddleware.

* Add support for external login providers.

* Addressed code review feedback.

* Applied suggestions from code review.

* Disable and change text on submit button when logging in.

* Add custom view support.
2026-04-11 08:55:08 +00:00
95bcd8fc14 Website Rendering: Add configurable output caching for template rendered pages (#22338)
* Configuration for website output cache settings.

* Interfaces and default implementation for extension points.

* Configure the output cache policy.

* Evict cached documents through updates to related documents, media and members.

* Feedback from code review.

* Update description of service registration in IWebsiteOutputCacheDurationProvider header comment.

Co-authored-by: Sven Geusens <sge@umbraco.dk>

* Use output cache over service provider.

* Optimise and DRY-up eviction handlers.

* Only register IWebsiteOutputCacheManager when the feature is enabled.

* Remove unnecessary check on applying output cache to Umbraco pipeline.

* Add extension point for determining if requests should be cached.

* Broken up large method in DocumentOutputCacheEvictionHandler, put enabled checks around debug logging, further unit test.

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
2026-04-11 06:52:03 +00:00
2b4dc2db9a User Service: Prevent fetching all permissions when no IDs are provided (#22424)
* User Service: Prevent fetching all permissions when no IDs are provided

Ensures that the UserService does not attempt to fetch permissions when the provided ID collection is empty, avoiding potentially expensive database queries that could return permissions for all nodes.

* Move guard into the shared private method and add an integration test to verify the fix.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-10 18:20:38 +00:00
AbdulazizandGitHub 3c50dc7f7b Templates: Fixes modal text styling in when inserting sections (closes #22358) (#22376)
* fixes modal text styling in Insert and Sections in Templates

* fixed review issues and added accesability for the cards so you can use keyboard

* fixing formatting changes

* fixed unused css and fixed accessability to match the card select & deselect

* fixed redundant key and click events

* fixed accessability for button and small bug with not being able to click it
2026-04-10 14:04:41 +00:00
Jacob OvergaardandGitHub 277bd8de62 Clipboard: Localize property labels when copying to clipboard (closes #21998) (#22412) 2026-04-10 13:27:25 +02:00
f9a70b799b Block Grid: Apply language fallback to block elements within layouts (closes #22195) (#22219)
* Apply language fallback to block element expose filtering.

* Handle code review feedback.

* Use builder instead of mocks in tests.

* Fixed failing unit tests.

* Revert previous approach and move fallback handling to the block property value creator.

* Include fallback policy in published property cache key

* Recreate block elements with resolved fallback culture.

* Use correct pattern for dispose.

* Introduce and use PropertyRenderingContext.

* Tidy up Fallback.

* Use core extensions for string comparison

* Less allocations

* Avoid fallback handling when no fallback policies are provided

---------

Co-authored-by: kjac <kja@umbraco.dk>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-04-10 13:10:29 +02:00
Niels LyngsøandGitHub e04cdc2030 Login: Update styles of login screen for better color customizations (#22389)
Update styles of login screen and enables better color customizations
2026-04-10 10:50:50 +00:00
Andy Butland 273f564571 Merge branch 'main' of https://github.com/umbraco/Umbraco-CMS 2026-04-10 12:05:35 +02:00
Andy Butland 08c65ef61f Merge branch 'release/17.3.2' 2026-04-10 12:05:21 +02:00
Jacob OvergaardandClaude Sonnet 4.6 68ec8223bd Docs: Update CLAUDE.md with final Claude workflow architecture [skip ci]
Reflects the two-workflow split, trigger phrase stripping behavior,
allowed tools, labeling for both PRs and issues, and implementation
gotchas discovered during setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:03:02 +02:00
Jacob OvergaardandClaude Sonnet 4.6 6ac48ef10f DevOps: Disable show_full_output now that interactive workflow works
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:32:49 +02:00
Jacob OvergaardandClaude Sonnet 4.6 2f2722258c DevOps: Remove redundant trigger_phrase (defaults to @claude)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:28:10 +02:00
Jacob OvergaardandClaude Sonnet 4.6 199beedaac DevOps: Pass PR/issue number explicitly to Claude prompt
Claude couldn't find the PR because checkout is on main and
gh pr view with no args returns nothing. Now the PR number is
injected directly into the prompt from the GitHub event context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:27:34 +02:00
Jacob OvergaardandClaude Sonnet 4.6 81c99c0ac8 DevOps: Explicitly prevent umb-review skill and git diff in interactive workflow
Claude discovered and invoked the umb-review skill which uses git diff
against origin/main — but checkout is on main so the diff was empty.
Prompt now explicitly says to use gh pr diff, not git diff or skills.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:24:34 +02:00
Jacob OvergaardandClaude Sonnet 4.6 f822b89e98 DevOps: Allow npm and dotnet in interactive Claude workflow
Needed for @claude fix scenarios where Claude builds/tests changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:23:17 +02:00
Jacob OvergaardandClaude Sonnet 4.6 48e9e6a814 DevOps: Pre-approve gh and git Bash commands for Claude workflows
The sandbox blocks multi-command Bash operations without approval.
Allow gh and git commands so Claude can read diffs, post comments,
and apply labels without permission errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:22:16 +02:00
Jacob OvergaardandClaude Sonnet 4.6 c900a5346b DevOps: Fix prompt to account for trigger phrase stripping
The action strips @claude from the comment before passing to Claude,
so commands arrive as just 'review', 'fix', etc. Updated prompt to
match. Also default empty messages to review (PR) or help (issue).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:18:24 +02:00
Jacob OvergaardandClaude Sonnet 4.6 6a0411fa40 DevOps: Restructure interactive prompt around user intent
Prompt now reads the user's message and acts accordingly instead of
prescribing behavior. Common patterns like review/help/fix/label
are listed as examples.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:15:24 +02:00
Jacob OvergaardandClaude Sonnet 4.6 b7d3236f92 DevOps: Fix interactive workflow prompt to act on PR context
Claude was treating @claude review as a greeting instead of acting
on the PR. Made prompt explicit about reviewing immediately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:13:17 +02:00
Jacob OvergaardandClaude Sonnet 4.6 acc99f420f DevOps: Enable show_full_output for debugging interactive workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:10:51 +02:00
Jacob OvergaardandClaude Sonnet 4.6 d64579f8e3 DevOps: Restore checkout in interactive Claude workflow
Action internally runs git fetch for trusted file restoration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:05:00 +02:00
Jacob OvergaardandClaude Sonnet 4.6 f98c2fd3d2 DevOps: Remove checkout from interactive Claude workflow
Action handles repo context via GitHub API — no local files needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:59:06 +02:00
Jacob OvergaardandClaude Sonnet 4.6 3bf5de2559 DevOps: Simplify interactive Claude workflow prompt
Remove umb-review skill reference — auto-review handles thorough reviews.
Interactive workflow gives quick feedback and general assistance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:58:39 +02:00
Jacob OvergaardandClaude Sonnet 4.6 f2ff1e850e DevOps: Remove reopened trigger and max-turns limit from auto-review
With only opened/ready_for_review triggers, volume is low enough to
let Claude run without a turn limit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:57:39 +02:00
Jacob OvergaardandClaude Sonnet 4.6 17cee849d3 DevOps: Increase auto-review max-turns to 50
25 turns was insufficient — the umb-review skill needs many turns to
read docs, references, changed files, and write the review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:56:37 +02:00
Jacob OvergaardandClaude Sonnet 4.6 7fc5a88c95 DevOps: Remove synchronize trigger from auto PR review
Only review on open/reopen/ready — use @claude review for re-reviews.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:56:16 +02:00
Jacob OvergaardandClaude Sonnet 4.6 f165a9cf3e DevOps: Switch to ANTHROPIC_API_KEY_03
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:49:08 +02:00
Jacob OvergaardandClaude Sonnet 4.6 3e119cb57f DevOps: Split Claude into two workflows (auto review + interactive)
- claude-review.yml: Auto PR review on open/push/ready (no trigger needed)
- claude.yml: Interactive — @claude comments, issue assignment/labeling

Follows anthropics/claude-code-action official examples pattern.
Full Option B gating on the interactive workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:46:09 +02:00
Jacob OvergaardandClaude Sonnet 4.6 427a7b264e DevOps: Add issue labeling instructions to Claude workflow
Include affected/*, area/*, and category/* labels for issues.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:42:17 +02:00
Jacob OvergaardandClaude Sonnet 4.6 52653f780a DevOps: Gate issue_comment events to avoid wasted runners
Only spin up a runner for issue_comment events that mention @claude.
All other event types pass through to the action for internal filtering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:41:20 +02:00
Jacob OvergaardandClaude Sonnet 4.6 408a8805c1 DevOps: Set base_branch to main for Claude review workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:25:02 +02:00
Jacob OvergaardandClaude Sonnet 4.6 1db3f2c8cc DevOps: Set max-turns 25 for Claude review workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:24:36 +02:00
Jacob OvergaardandClaude Sonnet 4.6 02b75c37e7 DevOps: Add checkout step — action needs git repo on disk
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:21:19 +02:00
Jacob OvergaardandClaude Sonnet 4.6 27ffb2671c DevOps: Restore prompt with review and issue instructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:19:12 +02:00
Jacob OvergaardandClaude Sonnet 4.6 02d9b50437 DevOps: Simplify Claude workflow to match official documentation
Strip all custom logic — let claude-code-action handle everything.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:18:40 +02:00
Jacob OvergaardandClaude Sonnet 4.6 98e2eba3fe DevOps: Add actions: read permission to Claude review workflow
Lets Claude see CI status when reviewing PRs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:14:19 +02:00
Jacob OvergaardandClaude Sonnet 4.6 a6fe4e6015 DevOps: Consolidate Claude workflows into single file
Merge auto and on-demand review workflows into claude-review.yml.
Add issue support via assignee_trigger and label_trigger.
Let claude-code-action handle permission gating and trigger matching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:12:05 +02:00
Jacob OvergaardandClaude Sonnet 4.6 9bfa56afae DevOps: Remove redundant permission check from on-demand review
claude-code-action gates on write permission by default — the manual
getCollaboratorPermissionLevel check was redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:06:26 +02:00
Jacob OvergaardandClaude Sonnet 4.6 005d65a49f DevOps: Revert trigger phrase to @claude review
Clearer attribution — identifies who is performing the review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:04:22 +02:00
Jacob OvergaardandClaude Sonnet 4.6 ed93b184fc DevOps: Add id-token: write permission for claude-code-action OIDC
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:03:56 +02:00
Jacob OvergaardandClaude Sonnet 4.6 5cc7d53ed2 DevOps: Change on-demand trigger to @umbraco review
Avoids collision with the claude-code-action bot's own @claude trigger.
Re-enables job-level filter to skip non-matching comments early.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:01:24 +02:00
87be4cfc03 DevOps: Add Claude automated PR review action (closes #AB66809) (#22407)
* DevOps: Add Claude automated PR review action (closes #AB66809)

Adds two GitHub Actions workflows that run the umb-review Claude skill on every non-draft PR and on demand via `@claude review` comments. Reviews are advisory-only and post inline comments per finding plus one summary comment per review run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* DevOps: Disable auto/on-demand triggers for initial testing

Remove pull_request_target trigger from auto workflow (workflow_dispatch only).
Disable on-demand job until auto workflow is validated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* enables task

* adds more categories

* DevOps: Address Copilot review feedback

- Checkout PR head ref (not base) so git diff works correctly
- Use fetch-depth: 0 for triple-dot diff merge base
- Fix SHA dedup: use full SHA and paginate comment listing
- Include 'maintain' permission in on-demand gate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Docs: Document Claude automated PR review workflows in CLAUDE.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 09:59:12 +02:00
Andreas Lykke BorgandGitHub 23f4b06cc8 Accessibility: Add label to member type filter dropdown (#22397)
Added missing label to dropdown
2026-04-10 08:00:50 +02:00
Andreas Lykke BorgandGitHub b6b9bc8bf2 Accessibility: Add label and localized placeholder to picker search field (#22402)
Added label and replaced placeholder with localized term
2026-04-10 08:00:27 +02:00
Andreas Lykke BorgandGitHub 06a1e82488 Accessibility: Add labels to member workspace toggles (#22403)
Added labels to toggles missing for accessibility
2026-04-10 08:00:24 +02:00
Laura Neto ea3b0d4d59 Use correct constant for MediaBreadthFirstSeedCount initializer
MediaBreadthFirstSeedCount was initialized with StaticDocumentBreadthFirstSeedCount
instead of StaticMediaBreadthFirstSeedCount, mismatching its [DefaultValue] attribute.
2026-04-09 16:21:50 +02:00
532d10d102 Content picker: Fix display for list items in content picker when pre-selected items exceed maximum (closes #22129) (#22395)
fix issue when display list items in content picker

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2026-04-09 15:35:18 +02:00
4441d6d843 Mock Server: Add missing batch handlers for content types (#22390)
* Fixes MemberType mock handler

* Fixes DocumentType mock handler

* Fixes DataType mock handler

* Fixes MediaType mock handler

* Add readBatch and refactor batch handlers

* Remove 400 response for empty doc-type batch ids

* Use type guard in filter to remove undefined

* remove unused

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-04-09 12:28:52 +00:00
Andy Butland 4b1f7e535d Management API: Fix OAuth client registration permanently skipped after transient failure (closes #22356) (#22368)
* Prevent OAuth client registration from being permanently skipped after transient failure.

* Addressed code review feedback.
2026-04-09 14:02:28 +02:00
Andy ButlandandGitHub 8d25312a1a Management API: Fix OAuth client registration permanently skipped after transient failure (closes #22356) (#22368)
* Prevent OAuth client registration from being permanently skipped after transient failure.

* Addressed code review feedback.
2026-04-09 14:00:31 +02:00
Andy ButlandandClaude Opus 4.6 79cf047103 Templating: Move production mode validation from service layer to Management API (#22383)
* Revert production mode validation for templates and partial views at the service layer, and move to management API.

* Remove unused ConfigureProductionMode helper from PartialViewServiceTests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add integration tests for UpdateTemplateController production mode behavior

Tests verify that the Management API correctly blocks template content
changes while allowing metadata-only updates in production mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Restore partial view service checks.
Add integration tests for template controllers with production mode.

* Align delete with create/update for file system changes in production mode.

* Restore partial view service tests.

* Add test for update to delete template repository.

* Refactored to use single test setup method.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 12:51:04 +02:00
e5de587721 Templating: Move production mode validation from service layer to Management API (#22383)
* Revert production mode validation for templates and partial views at the service layer, and move to management API.

* Remove unused ConfigureProductionMode helper from PartialViewServiceTests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add integration tests for UpdateTemplateController production mode behavior

Tests verify that the Management API correctly blocks template content
changes while allowing metadata-only updates in production mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Restore partial view service checks.
Add integration tests for template controllers with production mode.

* Align delete with create/update for file system changes in production mode.

* Restore partial view service tests.

* Add test for update to delete template repository.

* Refactored to use single test setup method.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 12:44:36 +02:00
Bjarne FyrstenborgandAndy Butland 5d6e3df473 Property Editor Dialog: Set height to 100% for umb-property-editor-ui-picker-modal (#22354)
Set height to 100% for ui-picker-modal element
2026-04-09 11:09:15 +02:00
Andy Butland 0cd35398be Migrations: Fix potential OptimizeInvariantUrlRecords timeout on SQL Server (closes #22377) (#22382)
Ensure parallel execution plans are not used for the OptimizeInvariantUrlRecords migration.
2026-04-09 10:52:56 +02:00
Andy Butland a9615aa729 Bumped version to 17.3.2. 2026-04-09 10:52:15 +02:00
Andy ButlandandGitHub f3863162c8 Migrations: Fix potential OptimizeInvariantUrlRecords timeout on SQL Server (closes #22377) (#22382)
Ensure parallel execution plans are not used for the OptimizeInvariantUrlRecords migration.
2026-04-09 10:48:25 +02:00
Andreas Lykke BorgandGitHub 54b6c22e84 Accessibility: Fix missing labels on uui-select elements causing console warnings (#22385) 2026-04-09 10:08:51 +02:00
Niels Lyngsø 4edfbf44e9 delay condition, good for testing 2026-04-09 09:52:41 +02:00
Niels Lyngsø abc6287008 corect existing usage of map to repeat 2026-04-09 08:05:13 +02:00
20b4529196 Languages: Exclude invariant culture from list of available cultures for language creation (closes #22380) (#22381)
* Exclude invariant culture from culture list endpoint

The Invariant Culture (CultureInfo.InvariantCulture) has an empty Name
property which is not a valid ISO code for Umbraco content. Filter it
out in IsoCodeValidator to prevent it appearing in the culture list.

Fixes #22380

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add unit tests for IsoCodeValidator.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-08 16:00:05 +00:00
a616e8dc48 Added length validation to change password modal element (#21781)
* feat(Change password modal): Integrate user configuration for minimum password length frontend validation on change-password-modal element.

* feat(change-password-modal): added user-friendly message for password length

* Update minlengthMessage property in change-password modal

* Fix password input minlength attribute syntax

* feat(change-password-modal): enhance password validation with dynamic configuration and feedback

* feat(change-password-modal): prevent form submission while loading configuration based on comment copilot

* Refactor password validation to input validators

* Extract password getter and clarify minimum length guard

* Refactor password validators into helper

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
Co-authored-by: engjlr <enl@umbraco.dk>
2026-04-08 13:35:17 +00:00
Andy ButlandandGitHub 681a510a08 Unit tests: Add test coverage for ContentPermissionService (#22373)
* Add unit tests for ContentPermissionService.

* Addressed code review feedback.

* Used constants for IDs and paths in tests.
2026-04-08 13:32:02 +00:00
88335f871d User group: Fix issue icons not show when different colour than black (closes #22352) (#22372)
Fix issue icons not show when different colour than black

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2026-04-08 15:17:43 +02:00
Andy ButlandandGitHub f0919792a1 Slider: Persist value updates on drag-and-drop (closes #22183) (#22276)
* Persist slider value updates on drag-drop.

* Addressed code review feedback.
2026-04-08 14:35:10 +02:00
3b7d2b9fa9 CSP: Add blob: to img-src for media upload previews (#22343)
Allow "blob:" in local CSP that is necessary for media uploads.

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-04-08 11:40:25 +00:00
b3d2cabd59 Claude: Agent MD files for manifestss (#22367)
* manifests.md

* refactor

* clean up unnesecary info

* update to architecture

* update

* Update src/Umbraco.Web.UI.Client/docs/manifests.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* update

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-08 13:18:08 +02:00
Engiber LozadaandGitHub bb95d74ba8 Content type design: Fix tab overflow with scrollable navigation (closes #20876) (#22294)
* Remove flex-shrink=0 from umb-body-layout

* Avoid collapsing tabs into the dropdown

* Add arrows left and right and bind a scroll

* Add a resizeObserver to keep track when the tabs container change

* Make the sort mode scrollable

* Move the add tab button inside the tabs list container

* Restore tab scrolling and detect hidden overflow

* Create a reusable scrollable container component

* Remove unused import

* Clean up

* Add HTMLElementTagNameMap to the scrollable container

* Always render the add tab button

* Observe slot children on slotchange

* Remove unused variable
2026-04-08 13:03:38 +02:00
1c92cacb83 Cache: Fix published content not immediately routable after PublishBranch (#22341)
* Re-order ContentCacheRefresher handlers so publish status is populated before memory cache refresh.

* Address code review feedback.

* Code tidy.

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-04-08 12:42:06 +02:00
4396c3fe4b Integration Tests: Fix raw SQL statements in DocumentUrlTests (closes issue #22360) (#22365)
* fix raw sql statements

* use ISqlSyntaxProvider methods

* Use constants for column names

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed incorrect SQL Count.

* Make SQL more readable.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-08 10:19:23 +00:00
Bjarne FyrstenborgandGitHub ac54cfd467 Property Editor Dialog: Set height to 100% for umb-property-editor-ui-picker-modal (#22354)
Set height to 100% for ui-picker-modal element
2026-04-08 12:04:08 +02:00
2f4d2351d0 Management API: Add document patch endpoint (#22104)
* Document patch, variant name only

* Multi variant tests

* Change to json-patch instead of merge to target nested properties

* Fix ManagementApiTest following PR 20820

* Segment suport for properties

* Verify non existing and trashed document patch behaviour

* Mostly working approuch for nested properties

* Fix endpoint route collision (Somehow...)

* Trying a custom way of doing things

* add escape support, more tests and cleanup

* remove unnecesary using

* Cleanup

* Restore things that are breaking

* cleanup

* Namespace cleanup

* Order cleanup

* More comment updates

* Add default implementations

* Improve modelbinding validation

* all string comparison

* Cleanup unused statuses

* Fix PatchPathResolver Filtering not accepting non string values

* Optimize path parsing

* Improve cookie token rework

* more cleanup

* Put AllowedValues on the correct property 🙈

* One more default implementation

* Add link to docs on endpoint swagger info

* PR review corrections

- Removed leftover affectedCultures & affectedSegments
- Extracted IDocumentPatcher interface
- Optimized serialization in patchEngine by moving it 1 level higher

* Update documentation urls

* Apply suggestions from code review

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Removed affected variance tracking that is nog longer being used

* Extract shared data class

* update claude patching namespace

* Remove no longer valid xml comment

* Fix unittests after refactoring patchengine.ApplyOperation(string,...) to patchengine.ApplyOperation(JsonNode,...)

* Refactor base classes

* Apply suggestions from code review

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Optimizations and refactoring of the patcher/engine/parser

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-08 11:55:22 +02:00
Andy Butland 3541b89380 Merge branch 'release/17.3.1' 2026-04-08 11:48:44 +02:00
530c861c2b Relations: Allow saving relation types without parent/child object types (closes #22359) (#22336)
* Allows save of a relation type without a child and/or parent object type.

* Addressed code review feedback and code health warnings.

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-04-08 07:19:14 +00:00
Jacob Overgaard ff60c8c1a8 build(deps-dev): bumps package lock 2026-04-08 08:28:31 +02:00
Nhu DinhandGitHub c3f959f536 E2E: QA Added acceptance tests for bulk actions (#22361)
* Updated ui helper for select content card

* Updated ui helper for select media card

* Added ui helper for clear selection button

* Added tests for bulk action in list view content

* Added more tests for clear selection button in list view media

* Make tests run in the pipeline

* Reverted npm command
2026-04-08 04:58:30 +00:00
426eaf1ba9 Performance: Batch backoffice media thumbnail URL requests to reduce N+1 API calls (#22329)
* Batch thumbnail URL requests to avoid N+1 API calls.

* Handle code review feedback.

* Remove extra newlines.

* chore: formats code

* Use @consumeContext decorator and remove await #init from imaging repository.

Replaces the blocking `await this.#init` pattern with the `@consumeContext`
decorator so the store is consumed opportunistically. This removes the async
gap before batchImagingRequest calls, allowing all thumbnails mounting in the
same Lit render pass to be collected into a single batched API request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Move imaging URL cache into the request batcher and deprecate UmbImagingStore.

The batcher now owns a module-level URL cache, eliminating the need for the
context-based UmbImagingStore. This removes all context-request events from
the imaging repository and thumbnail hot path. The repository delegates
entirely to the batcher for caching and fetching. UmbMediaDetailRepository
uses the new clearImagingCache() export directly instead of instantiating an
imaging repository. Items with no URL (non-image media) are cached as empty
strings to prevent unnecessary re-fetching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remove extra newlines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:23:02 +00:00
Andy Butlandandkjac 6c089db898 Media Picker: Fix folder selection regression for developer-configured media pickers (closes #22349) (#22350)
* Fixes "files/folders/files or folders" selections for the various media picker components, re-allowing folder selection from a media picker.

* Import and use enim instead of hardcoded enum value

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-04-07 16:26:37 +02:00
Andy Butland 70dd464346 Builder Extensions: Make AddWebComponents() idempotent (closes #22344) (#22347)
Ensure AddWebComponents is idempotent.
2026-04-07 16:26:27 +02:00
dependabot[bot]andJacob Overgaard 3b69a2fffa Bump lodash from 4.17.23 to 4.18.1 in /src/Umbraco.Web.UI.Login
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-07 13:56:25 +02:00
bfa3c3234b Media Picker: Fix folder selection regression for developer-configured media pickers (closes #22349) (#22350)
* Fixes "files/folders/files or folders" selections for the various media picker components, re-allowing folder selection from a media picker.

* Import and use enim instead of hardcoded enum value

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-04-07 10:33:23 +02:00
Engiber LozadaandGitHub 80f864f298 Search: Show ancestor breadcrumb path in items results (closes #21107) (#22240)
* Show ancestor path in document search results.

* show ancestor breadcrumb path in media search results

* Show the document ancestors name by culture variant

* Extract ancestor fetching to reduce cyclomatic complexity

* Add early return inside #fetchAncestors

* Handle errors from the api call.

* Add fallback title when the name doesn't exist

* Use full item models for search ancestor types
2026-04-07 09:38:27 +02:00
Andy ButlandandGitHub cd541b66f4 Builder Extensions: Make AddWebComponents() idempotent (closes #22344) (#22347)
Ensure AddWebComponents is idempotent.
2026-04-07 07:07:19 +02:00
Andy Butland 8c5c6a8870 Install: Ensure media directory exists before creating PhysicalFileProvider (closes #14877) (#22281)
* Ensure media directory exists before creating PhysicalFileProvider.

* Ensure file provider is disposed in test.
2026-04-06 13:02:45 +02:00
Andy Butland 2b3f468111 Document URL Service: Batch delete of obsolete URL segment records to avoid SQL Server parameter limit (closes #22339) (#22340)
* Batch delete in DocumentUrlRepository and DocumentUrlAliasRepository to avoid exceeding SQL Server's 2100 parameter limit.

* Address code review feedback.

* Remove the unnecessary trigger rebuild on startup statement in the SQL Server migration path.
2026-04-03 12:38:49 +02:00
Andy Butland 727dd02a9e Bumped version to 17.3.1. 2026-04-03 11:35:54 +02:00
Andy ButlandandGitHub 5127b97e2c Document URL Service: Batch delete of obsolete URL segment records to avoid SQL Server parameter limit (closes #22339) (#22340)
* Batch delete in DocumentUrlRepository and DocumentUrlAliasRepository to avoid exceeding SQL Server's 2100 parameter limit.

* Address code review feedback.

* Remove the unnecessary trigger rebuild on startup statement in the SQL Server migration path.
2026-04-03 10:51:04 +02:00
Andy Butland 9c309f6030 Merge branch 'release/17.3.0' 2026-04-02 07:41:44 +02:00
564068f61b Background Jobs: Fix period drift in RecurringHostedServiceBase (#22330)
* Compute next delay to compensate for time drift

* Addressed case flagged on code review following stopped service.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-02 05:19:26 +00:00
2a44169e0b Block Editors: Fix preset values for composition properties on non-varying element types (closes follow-up on #22320) (#22320)
Correct preset values for composition properties on non-varying element types

Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-04-01 15:05:34 +00:00
20a8749b0c CLAUDE.md: OpenAPI.json maintenance (#22326)
* Added instructions for maintaining the `OpenApi.json` file

* Updated client-side instruction docs

for clean code and style guide.

* Updated "Full API surface" point

* Update CLAUDE.md

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-01 14:45:13 +00:00
Andy Butland af4a94a908 Bump acceptance test version to 17.3.0. 2026-04-01 16:18:04 +02:00
Andy Butland 0c30d86b25 Merge branch 'release/17.3.0' of https://github.com/umbraco/Umbraco-CMS into release/17.3.0 2026-04-01 16:15:47 +02:00
Andy Butland 55eda0832d Bump version to 17.3.0. 2026-04-01 16:15:31 +02:00
f8ba5db5aa Redirects: Fix crash seen in Redirect URL Management dashboard when the redirect route does not contain '/' (closes #22308) (#22309)
* Handle invalid redirect routes without slash in GetUrlFromRoute

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Handle fragment-only routes before parsing node id

* Add unit tests verifying the fix (as well as expanding the test coverage of the URL provider in general).

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-01 14:11:43 +00:00
96766a80db Notifications: Surface ProblemDetails detail in error notifications (#22298)
* feat: surface ProblemDetails detail in error notifications

Pass the ProblemDetails detail field through to error notifications.
Short details (≤250 chars) are shown inline with CSS line-clamp.
Long details (>250 chars) are shown via a "See error" button that
opens the error viewer modal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review — rename detail/details ambiguity and remove as any cast

Rename local `details` variable to `errors` to avoid confusion with `detail`.
Change UmbErrorViewerModalData to a union type (UmbPeekErrorArgs | string)
matching what the modal actually handles at runtime, eliminating the as any cast.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: tighten types and overload _peekError with UmbPeekErrorArgs

- Document UmbPeekErrorArgs interface and its properties
- Add `errors` property to UmbPeekErrorArgs, deprecate `details`
- New _peekError overload: accepts UmbPeekErrorArgs directly
- Old _peekError overload: positional args, deprecated for removal in v19
- Update notification element and interceptor to use `errors`
- Widen UmbErrorViewerModalData to also accept Record<string, unknown>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract duplicate errors fallback to #validationErrors getter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove unnecessary null handling in interceptor #peekError

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve tsc errors from type tightening

- UmbErrorViewerModalData: use Record<string, unknown> interface to
  satisfy UmbModalToken's object constraint (string not allowed)
- Cast detail string through unknown when opening error viewer
  (modal handles strings at runtime, token type doesn't allow it)
- Fix interceptor errors Record to use string[] values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve eslint errors — unused import, prettier, jsdoc link

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: renames 'See error' button to 'Full Error Message'

* feat: renames Danish button 'Undtagelsesdetaljer' to 'Fejldetaljer'

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 13:59:36 +02:00
3dc61fea80 Agent Review: Prefer documentation over implementations (#22324)
* Docs-first review: load prefs & validate patterns

* Update .claude/skills/umb-review/SKILL.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-01 11:42:53 +00:00
Mads RasmussenandGitHub 4f6a5b1c2d Backoffice: Add client-side model guidance and repo rules for agents (#22321)
* Add client-side model guidance and repo rules

* fix paths

* Update data-flow.md
2026-04-01 13:22:25 +02:00
reabrandGitHub 7a5ed5ad00 Code Quality: Add 'new' keyword to 3 methods hiding inherited members resolving CS0114 warnings (#22317)
* Fix CS0114: Add 'new' keyword to 3 methods hiding inherited members

* docs: update TODO comments for 'new'/'new virtual' methods (V18 cleanup)

* docs: update TODO comments for 'new'/'new virtual' methods (V18 cleanup)
2026-04-01 11:38:15 +02:00
Andy ButlandandGitHub 63fff17759 BlockGrid: Protect against null columnSpan/rowSpan when rendering blocks (closes #22306) (#22311)
* Protect against null column or row span when rendering blocks.

* Addressed code review feedback.
2026-04-01 10:52:21 +02:00
1b15f51798 Backoffice: Add Repository documentation and create-repository skill for agents (#22310)
* Add workspaces docs, CLAUDE link, and skill

* Export workspace elements as element

* consolidate information

* adjust skill to make use of generic name component

* try to force the agent to follow docs and use skills

* Update SKILL.md

* clean up create package skill

* use data type package as reference

* add initial repository doc + skill

* Update src/Umbraco.Web.UI.Client/docs/workspaces.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/docs/workspaces.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update workspaces.md

* clean up

* Update SKILL.md

* Delete Repositories.md

* Create repositories.md

* Update repositories.md

* Normalize repositories doc links to lowercase

* Update src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix data flow link path casing

* fix casing

* export as api + inline store in manifest

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-01 09:26:38 +02:00
43ccd7a171 Application URL: Add ApplicationUrlDetection setting to control application URL auto-detection (#22307)
* Prevent Host header poisoning of ApplicationMainUrl.

* Introduce options for Umbraco application URL detection and handle situations where it can be undefined.

* Prevent email operations if the application URL is not detected or configured.
Improve log warnings.

* Addressed feedback from code review.

* Move startup application URL logging to a handler.

* Clean up ambiguous log message

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-04-01 09:18:43 +02:00
3b0972cd56 Document Blueprints: Add info workspace view (#21951)
* add info workspace view  into document blueprint

* Add history panel

* update document type route

* remove comment

* move time options format to ultils

* add blueprint auditlog model

* save move action and add authorization for audit log request

* add default implement

* update open api json

* Reused the `workspaceInfoApp: auditLog` kind

Added the manifest for the repository.
Removed the duplicated/unused code.

* UI tweaks + linting

* Renamed "Document Blueprint Workspace View Info Element" file/tag

* Restored the "UmbDocumentBlueprintAuditLog" types

* Add JSDoc to document blueprint audit log repository

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Export audit-log module from document-blueprints index

Adds the missing re-export so UMB_DOCUMENT_BLUEPRINT_AUDIT_LOG_REPOSITORY_ALIAS
is reachable from @umbraco-cms/backoffice/document-blueprint.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: leekelleher <leekelleher@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 15:51:34 +00:00
Andy ButlandandGitHub 5d17ead9db Build: Pin CycloneDX SBOM generation to spec version 1.5 (#22305)
Pin dotnet-CycloneDX to spec-version 1.5
2026-03-31 14:46:53 +00:00
021163f100 Review: Claude Skill for Review of Github PRs (#22245)
* claude review md files

* rename to review

* auto-detect target-branch via GH CLI

* Verify GH CLI is Available

* update table to fit github markdown format

* condensed the output to the essense

* State if the PR is too bad

* using the word `and´

* only relevant suggestions

* clean up

* narrow the scope for large PRs

* diff-first approach with selective reads

* specify that the header_only are amount of file where the only extra loaded is the header

* Complexity detection

* Classification of the PR

* improve other changes

* Ensure Types are kept intact in their type Hierarchy

* align test naming with project, and clean up instructions

* remove hardcoded Claude.md file table for a pattern

* improve skill description

* improved breaking change detection for front-end

* do not suggest breaking changes for PRs targeting main

* rename skill to umb-review

* less nit picky

* first version of skill evals

* Update .claude/skills/umb-review/references/coding-preferences.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .claude/skills/umb-review/references/impact-analysis.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* remove mentioning the skill action it self

* split out GH CLI guideline

* improve file loading strategy

* make feedback extremely concise

* improve skipped files output

* latests eval

* added further evals

* move summaries into references

* separate Complexity Assessment into a reference file

* Complexity Assessment: secure mixed is still check despite other rules it out

* dont include gen.ts files

* iter 9 evals

* latests eval of 4

* keep only one test for complexity-advisory

* adjusted skill and Evals to match expectations

* improve sibling lookups

* improve skill regarding nit picks and C# patterns

* remove insecure manifest check

* final eval run

* eval grading

* remove review workspace

* remove umb review workspace part 2

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-31 12:36:34 +02:00
33bf627602 Backoffice: Add Workspace documentation and create-workspace skill for agents (#22300)
* Add workspaces docs, CLAUDE link, and skill

* Export workspace elements as element

* consolidate information

* adjust skill to make use of generic name component

* try to force the agent to follow docs and use skills

* Update SKILL.md

* clean up create package skill

* use data type package as reference

* Update src/Umbraco.Web.UI.Client/docs/workspaces.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/docs/workspaces.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update workspaces.md

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-31 11:21:50 +02:00
Nhu DinhandGitHub 851cc79d2c Build: Publish test helper to Myget (#22156)
* Publish test helper to Myget

* Moved acceptance-test-helper to umbraco-cms

* Updated scope
2026-03-31 09:04:21 +00:00
81ef90dd27 BackOffice Document Editing: Fix pending changes status in variant selector (closes #22271) (#22290)
* Present only changed variants as selected by default when saving and publishing.

* Detect pending changes on document load to ensure language selector variant status reports correctly.

* Avoid concurrent loads.

* Fix issue where with two variants changed but only one saved, both would display with pending changes.

* Addressed code review feedback.

---------

Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-03-31 09:42:51 +02:00
Andy ButlandandGitHub 2e23ca5599 Performance: Optimize ContentTypeRepository deep-clone on cache reads (closes #22250) (#22263)
* Optimize ContentTypeRepository to avoid unnecessary deep-cloning on cache reads.

* Used lightweight benchmark and addressed code review comments.
2026-03-31 09:38:33 +02:00
c19e424cdd Tiptap RTE: Add width/height to edit image properties (AB#65981) (#22266)
* TipTap: Add width/height to edit image properties (AB#65981)

Add width and height input fields with aspect-ratio lock toggle to the
media caption/alt-text modal. Thread dimensions through the toolbar
action so existing image dimensions are preserved when editing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* TipTap: Add double-click to open edit modals for images and embeds

Move double-click detection into node extensions via addProseMirrorPlugins
(tiptap-native). Extensions dispatch a generic DOM event, input-tiptap
delegates to the toolbar, and the toolbar executes the active action.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* TipTap: Improve edit image properties, unify embed dimensions, fix figcaption bug (AB#65981)

- Add width/height fields with aspect-ratio lock and maxImageSize cap to image modal
- Unify embed modal dimensions UI with image modal (inline row, lock button, px postfix)
- Fix figcaption cursor bug: editing from inside caption no longer opens new image picker
- Pass user dimensions to imaging endpoint for valid HMAC-signed URLs
- Preview image updates aspect-ratio when dimensions change
- Slim down toolbar API: inline pass-through methods, remove dead code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing width: 100% to image modal dimension inputs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use display:block instead of width:100% on dimension inputs

Prevents the right border of the px affix from being clipped.
Applied to both image and embed modals for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove explicit sizing on dimension inputs, let flex handle it

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use @input instead of @change on embed dimension fields

Aligns with image modal behavior so constrained dimensions update
on keystroke. Preview fetch is debounced at 500ms to avoid spam.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Copilot review feedback

- Wrap imageSize() in try/catch so modal remains usable on broken URLs
- Recalculate aspect ratio on re-lock in image modal (matches embed)
- Change min="0" to min="1" on dimension inputs (both modals)
- Fix constrain truthiness check to use !== undefined

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* TipTap: Use maxImageSize config for embed defaults, update ratio to 16:9

Replaces hard-coded 360x240 (3:2) embed defaults with maxImageSize from
RTE config and a 16:9 aspect ratio matching modern video embeds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: select figure before replacing when editing from figcaption

When cursor was inside a figcaption, insertContent would insert a new
figure at the cursor instead of replacing the parent figure. Now selects
the figure node via setNodeSelection before proceeding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: export UMB_TIPTAP_NODE_DBLCLICK_EVENT from tiptap constants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: removes double-click handling (to be implemented later on)

* Apply suggestion from @AndyButland

Co-authored-by: Andy Butland <abutland73@gmail.com>

* feat: adds constants for default width and height and guards against 0-values

* feat: validates that width and height are larger than 1px

* refactor: Extract shared <umb-input-dimensions> component

Deduplicates the width/height dimension input logic that was repeated
in both the media caption/alt-text modal and the embedded media modal.

The new component supports aspect ratio locking, proportional resize,
disabled state, and an optional reset-to-natural-dimensions button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: embeds should be constrained by default

* feat: defaults embed constrain to true

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: always fetch natural dimensions so reset button appears when editing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: move reset button below dimensions and cap natural size to maxImageSize

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: cleanup

* fix: use general_clear localization key for reset button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: constrain embed preview to sidebar width using aspect-ratio

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: target any first-child element in embed preview, not just iframe

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add comment explaining generic selector for oEmbed markup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use height auto to let embed scale naturally from width

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use !important on width to override inline oEmbed attributes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use height 100% so iframe fills the aspect-ratio container

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: smooth embed preview aspect-ratio changes with CSS transition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: smooth image preview aspect-ratio changes with CSS transition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show Clear button on embed dimensions using default size as natural

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: use maxImageSize for embed natural dimensions and Clear button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: media-with-caption modal should be 'medium'

* feat: address review feedback on dimensions and preview

- Rename reset button label from general_clear to general_reset (new key)
- Fix embed preview: use pixel width + aspect-ratio + max-width for
  accurate proportional preview at any dimension
- Apply same width+aspect-ratio approach to image preview
- Add uui-box to media caption modal for consistent sidebar background
- Center image and embed previews in their containers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: simplify embed modal — honest dimensions, responsive iframe preview

Remove maxImageSize and naturalWidth/naturalHeight from embed modal since
oEmbed dimensions are hints (maxwidth/maxheight), not guarantees. Add
localized description explaining this to the user. Fix iframe preview
collapsing to 150px by reading width/height attributes and applying
aspect-ratio via JS (iframes lack intrinsic dimensions unlike images).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: recalculate aspect ratio when dimensions are set externally

When width/height properties are set from outside (e.g. after async
imageSize() resolves), the ratio was not recalculated — leaving it
undefined from connectedCallback. This caused locked mode to silently
fail on first appearance of the media caption/alt-text modal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-30 15:08:31 +00:00
b79639cb23 Document Editing: Fix unchanged variants selected in save and publish dialog (closes #22277) (#22285)
Present only changed variants as selected by default when saving and publishing.

Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-03-30 14:00:51 +00:00
934834b6f5 Rich Text Editor: Filter paste, drag&drop, and media picker to allowed media types (closes #21824) (#22267)
* RichTextEditor: Filter media picker to allowed media types (closes #21824)

Add allowedMediaTypes config to the RTE data type, filtering the media
picker tree to only show selectable media types. Also applies type-aware
validation to drag-and-drop uploads using UmbMediaTypeStructureRepository,
with a modal picker when multiple types match a dropped file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Review fixes: cache media type lookups, remove unnecessary localization keys, fix lint

- Cache requestMediaTypesOf results per extension to avoid redundant API calls
  when dropping multiple files with the same extension
- Add try/catch around API call to prevent unhandled rejections from crashing
  the upload loop
- Remove custom localization keys, reuse same plain strings as MNTP config
- Fix prettier formatting warnings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Auto-Pick in media type picker modal no longer silently fails

The modal returns `{ mediaTypeUnique: undefined }` for auto-pick, which
was treated as a cancellation. Now distinguished from cancel (rejected
promise) and falls back to the server's preferred type.

Fixed in both the media dropzone manager and TipTap drag-drop upload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: Add localization keys for allowedMediaTypes config, reorder weight

Move allowedMediaTypes next to mediaParentId (weight 91) as they are
related media config options. Use #rte_config_* localization pattern
matching other RTE config properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Show notification when pasting disallowed file types into RTE

The MIME-type pre-filter silently dropped non-image files on paste
(and drag-drop). Now shows the same disallowed file type notification
as the media type validation path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: Add server-side validation for RTE AllowedMediaTypes config

Validates that media items referenced via data-udi in RTE markup are of
an allowed media type. Follows the same pattern as MNTP's
AllowedTypeValidator. Includes unit tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Clean up validator tests: remove unused param and region markers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Use splitStringToArray for config parsing consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Include media name in validation error for disallowed media types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: Fix and add acceptance tests for RTE allowedMediaTypes config

* feat: Default RTE to Image and SVG allowed media types

Set allowedMediaTypes to Image and Vector Graphics (SVG) in the
default Rich Text Editor data type seed for new installs. Also
update the Vite mock data to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Address Copilot review feedback

Fix test helper that swallowed null allowedMediaTypes parameter,
masking the "no filter configured" test case.

Remove redundant upload failure toast that showed a misleading
"disallowed media type" message for non-validation failures
(the upload manager already handles its own error notifications).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Use constants for seed GUIDs, normalize file extension casing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Refactored media type checks into helper shared across RTE and media picker.
Resolved case insensitivity edge case.
Removed unnecessary obsolete constructor.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-30 14:17:55 +02:00
Mads RasmussenandGitHub 0bf0634d4f Templating: Add Production Mode condition to Partial View and Template Collection create actions (#22295)
Add production-mode condition to collection actions
2026-03-30 12:24:31 +02:00
c765ce6066 Accessibility: Include visible initials in name displayed on account menu button (closes #21942) (#22117)
* Fixed label in account menu button

The account menu button in the backoffice header was displaying user initials
visually (e.g., "AB") but the accessible name only showed "Profile options",
violating WCAG 2.5.3 which requires that when a UI component has visible text,
the accessible name must contain that visible text.

This fix ensures voice navigation software (e.g., Dragon NaturallySpeaking) can
properly recognize commands using the visible initials.

Changes:
- Added getInitials() utility function to extract first and last initial from user names
- Updated current-user-header-app component to include user name and initials in the
  button's accessible label (aria-label)
- Updated profileOptions localization term in all 15 language files to include
  placeholders for user name and initials using %0% and %1% format

Result:
- Visual display: "AB"
- Accessible label: "User profile for Andreas Lykke Borg (AB)"

The visible initials are now included in the accessible name, providing a
consistent experience for all users including those using assistive technologies.

Fixes #21942

* Update src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/get-initials.function.ts

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Added a fallback profile options label if name is null or empty

* Added test for get-initials function

* Added note about duplicate get-initials function

* Replicated the logic from the UUI avatar

* Add TODO to use utility exposed from UUI library for extracting the initials.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-30 07:30:07 +00:00
Andy ButlandandGitHub 71d9e34f20 Install: Ensure media directory exists before creating PhysicalFileProvider (closes #14877) (#22281)
* Ensure media directory exists before creating PhysicalFileProvider.

* Ensure file provider is disposed in test.
2026-03-30 14:56:24 +09:00
Andy ButlandandGitHub dbb492b6e8 User Service: Fix WhereIn subquery in PermissionRepository (closes #22288) (#22289)
* Correct WhereIn subquery in PermissionRepository.

* Addressed code review feedback.

* Relocated tests to permission specific file.
2026-03-30 14:01:07 +09:00
Jose MarcenaroandGitHub 753976bdcc User management: Show change password validation error (closes #22291) (#22292)
Fixes #22291

In order to show the right validation message:

 - the repository code always notifies the validation failure message
    (or a default failure message if none is received)
 - in the data-source code, tryExecute is called with the option
    to disable the default notification

Return the original error instead of faking success
2026-03-30 06:36:50 +02:00
6b9bd4c787 Media: Allow duplicating system media types (closes #22282) (#22284)
* Allow copying of system media types.

* feat: Improve error message for system media type alias change

Replace the generic "Operation not permitted" error with a specific
"Alias change not permitted" message that explains the constraint and
suggests using the duplicate operation instead.

Also adds an ordering comment in DeepCloneWithResetIdentities and
a test assertion verifying the copy's alias is mutable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 17:40:39 +00:00
Andy Butland 581c3ec64d Merge branch 'release/17.3.0' 2026-03-27 15:03:25 +01:00
Andy Butland 65a89244f0 Unattended Upgrades: Rebuild routing caches after background migrations to fix unroutable document URLs (#22269)
* Prevent HybridCache from caching null content entries.

* Revert change and use approach of ensuring null cached values are tagged.
2026-03-27 13:49:24 +01:00
Andy ButlandandGitHub 1cde598ded Unattended Upgrades: Rebuild routing caches after background migrations to fix unroutable document URLs (#22269)
* Prevent HybridCache from caching null content entries.

* Revert change and use approach of ensuring null cached values are tagged.
2026-03-27 13:46:40 +01:00
400fd5b0e0 Backoffice Agent Context: Add design philosophy, developer roles and skills for a few common extensions and infrastructure tasks (#22273)
* add frontend claude context for architecture, deprecation, package-development

* update with developer roles

* tighten up for llm consumption

* add information about localization

* add section about kinds

* include test priority

* add llm docs for core primitives and data flow

* add info about caching

* add skills

* organize in folders

* flat list of skills

* Update src/Umbraco.Web.UI.Client/docs/architecture.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/docs/package-development.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* update skill name

* format tech stack based on claude recommendations

* add context about entities

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-27 12:35:53 +01:00
a69ce5df2f Backoffice: Remove token cookie if decryption fails (mitigates #16107) (#22237)
* Remove token if decryption fails

* Update src/Umbraco.Cms.Api.Common/DependencyInjection/HideBackOfficeTokensHandler.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* inlcude namespace for suggested code change

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-27 12:02:39 +01:00
Andy Butland 6f152eae64 Migrations: Fix NPoco auto-select breaking retrust FK migration (#22270)
Prevent NPoco auto-select from breaking retrust migration.
2026-03-27 09:35:47 +01:00
Andy ButlandandGitHub dd7fb87534 Migrations: Fix NPoco auto-select breaking retrust FK migration (#22270)
Prevent NPoco auto-select from breaking retrust migration.
2026-03-27 09:17:52 +01:00
Andy Butland a619182bae Dependencies: Update Microsoft packages to latest patch and fix HybridCache ParseFault with Redis (#22278)
* Update Microsoft.Extensions.Caching.Hybrid to latest minor, and other Microsoft dependencies to latest patch.

* Align test and local web project dependency versions.
2026-03-27 06:29:06 +01:00
4940b28cc3 Tests: Remove dead KeepAlive config remnants (#22272)
chore(tests): remove dead KeepAlive config remnants

The KeepAlive feature was removed in b619399edb (#15891) but references
to the config remained in 8 acceptance test appsettings.json files and
2 CI pipeline env var definitions. These are no-ops since the setting
no longer exists — remove them.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
2026-03-27 04:27:55 +00:00
Andy ButlandandGitHub 0c294fb8bc Dependencies: Update Microsoft packages to latest patch and fix HybridCache ParseFault with Redis (#22278)
* Update Microsoft.Extensions.Caching.Hybrid to latest minor, and other Microsoft dependencies to latest patch.

* Align test and local web project dependency versions.
2026-03-27 08:09:03 +09:00
a03fb9b0e3 Media: Set width and height for uploaded SVGs (#22244)
* Added migration for SVG width/height

* #22114 worked on SVG width height implementation

* #22244 Code style fixes

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 XmlReaderSettings and using

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Cleanup

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Correction if statement

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Refactor log message

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Correction if statment

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Cleanup

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Cleanup

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Code style adjustments

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Adjust if statement

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Adjust documentation comments

Co-authored-by: Andy Butland <abutland73@gmail.com>

* #22244 Fix log comment

* #22244 Fallback to viewbox if width height attribute has other unit than numeric or px.

* #22244 Refactoring SVG parser, no support for decimals

* #22244 Migration, consistent logging

* #22244 Create vector umbracoWidth and umbracoHeight during clean install

* #22244 Remove SupportedImageType from ISvgDimensionsExtractor

* #22244 pass culture and segment to SetValue

* Add DtdProcessing.Prohibit security hardening to SvgDimensionExtractor.

* Addressed some code styling and robustness of the migration and extractor classes.

* Add further unit tests.

* Add logging to notification handler. Skip when properties don't exist to avoid unnecessary processing.

* Add unit tests for media saving handler.

* Move the dimensions extractor implementation into infrastructure.

---------

Co-authored-by: Markus Johansson <markus@obviuse.se>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-26 15:34:28 +01:00
867414629b Cache sync: append SiteName to machine identifier for same-host load balancing (#22257)
* fix(core): append SiteName to machine identifier for same-host load balancing

When multiple Umbraco instances run on the same machine (e.g. IIS AAR load
balancing or local LB simulation), they shared the same machineId key in the
umbracoLastSynced table, causing cache sync interference. If Umbraco:CMS:Hosting:SiteName
is configured, it is now appended to the machine name to produce a unique
identifier per instance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update tests/Umbraco.Tests.UnitTests/Umbraco.Core/Factories/MachineInfoFactoryTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Validate length

* Refactor to enable us to have a validator

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-26 10:00:26 +01:00
b292535cf9 Repositories: Fix Raw Sql Statements without Escaped Table, Column or Alias Names (closes #22259) (#22261)
* fix raw sql statements without escaped table, column or alias names.

* fix more raw sql statements without escaped table, column or alias names.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Improve variable naming.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-26 08:47:15 +01:00
Nhu DinhandGitHub 3238f2306a E2E: Added acceptance tests for block grid area (#22181)
* Added api helper for block grid area

* Updated ui helper for block grid area

* Updated tests for block grid area

* Updated json builder for blockGridSpecifiedAllowance

* Formatted code

* Updated ui helper for specifiedAllowance

* Updated tests

* Fixed ui helper for enterSpecifiedAllowanceMinByIndex

* Added ui helper for create content with a block area with specified allowance

* Added tests for create content with ablock grid area with specified allowance

* Format code

* Make tests run in the pipeline

* Fixed tests

* Fixed comments

* Reverted npm command
2026-03-26 05:44:27 +00:00
Andy ButlandandGitHub bb44bed058 Examine Dashboard: Support content node links from delivery API index (closes #22221) (#22225)
* Support link to document from backoffice examine index view for delivery API index.

* Address PR feedback.
2026-03-26 12:24:03 +09:00
5f684eaa10 Content Version Cleanup: Optimize for large datasets (closes #22224) (#22239)
* Extend and tidy up unit and integration test coverage.

* Add MaxVersionsToDeletePerRun configuration setting.

* Added overload to GetDocumentVersionsEligibleForCleanup to allow restricting results to older than a given date and with a maximum count.

* Use SQL date filter and per-run cap in content version cleanup.

* Handle deletes using optimised process using temp tables.

* Make maxCount nullable and add per-run cap integration test.

* Addressed code review feedback.

* Fix to reporting of cap reached.

* Additional unit tests for max date cut-off logic.

* Add TODOs for removal of default implementations from interfaces.

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>

* Revert timing for ContentVersionCleanupJob.

* Add index to versionDate on umbracoContentVersion.

* Ensure long command timeout for upgrade.

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-03-25 13:31:04 +01:00
Mads RasmussenandGitHub 2f7f8cf905 Backoffice: Fix Ctrl+C not terminating the example dev server (#22249)
Close readline before starting dev server

Close the readline interface before launching the Vite dev server so Ctrl+C can properly terminate the process.
2026-03-25 09:44:36 +00:00
Nhu DinhandGitHub 29f267338c E2E: Reverted npm command for smokeTest (#22246)
Reverted npm command for smokeTest
2026-03-25 09:03:51 +00:00
Andy Butland 949584afc2 Examine: Fix DocumentUrlService not initialized during Examine indexing after package upgrade (#22243)
* Revert to segment retrieval from content when document URL service isn't initialised.

* Add tests for ContentValueSetBuilder.
2026-03-25 06:29:40 +01:00
Andy Butland 9a7c8efbd0 Bump version to 17.3.0-rc3. 2026-03-25 06:29:23 +01:00
Andy ButlandandGitHub cef63cb07d Examine: Fix DocumentUrlService not initialized during Examine indexing after package upgrade (#22243)
* Revert to segment retrieval from content when document URL service isn't initialised.

* Add tests for ContentValueSetBuilder.
2026-03-25 06:25:41 +01:00
Nhu DinhandGitHub 276d12d963 E2E: QA Added acceptance tests for validating a mandatory multi URL picker (#22235)
* Added more constant variable for validation message

* Added api helper for creating multi url picker data type with min number

* Renamed

* Updated api helper for creating document with multi url picker

* Added tests for mandatory multi url picker

* Split out tests for content with a multi URL picker.

* Refactor and added tests for publish a block with empty mandatory multi url picker

* Make tests run in the pipeline

* Fixed comments
2026-03-25 10:51:32 +07:00
Nhu DinhandGitHub 88d8a5acb3 E2E: QA Added acceptance tests for moving media items (#22232)
* Added tests for moving media

* Renamed tests

* Make tests run in the pipeline

* Updated name

* Reverted npm command
2026-03-25 03:37:20 +00:00
b57aacc176 Localization: Update "MFA" label to "2FA" in language files (#22236)
* Update MFA label to 2FA in English language file

* Changed MFA to 2FA in all other language files.

* Revert "Changed MFA to 2FA in all other language files."

This reverts commit 203294e287.

* Changed MFA to 2FA in all other language files.

---------

Co-authored-by: Marc Love <marc@madebycrunch.com>
2026-03-24 17:29:52 +01:00
Andy ButlandandGitHub 23123adeaa Member Authorization: Return correct status codes for unauthenticated members (fixes #21638) (#22220)
* Handle API and surface controllers with correct status code and behaviour when a member isn't logged in.

* Addressed code review feedback.

* Further code review feedback.
2026-03-24 15:46:52 +01:00
Mads RasmussenandGitHub 851d96c2b8 Members: Fix Create Members based on Member Types in folders (#22241)
* utilize the member type structure repo to get member create options

* align member collection create action with other content types

* remove hardcoded icon

* Update constants.ts
2026-03-24 13:33:38 +00:00
Mads RasmussenandGitHub 70be022109 Backoffice: Migrate Templating, Language, Member Group, and Document Blueprint create entity actions to use entityCreateOptionAction extensions (#22214)
* register as create options

* restore label

* Remove ellipsis from document blueprint label

* Add collection create actions for tree item children

* Show ellipsis for labels with additional options

* Enable additional options for create actions

* Refactor language and member group create actions into create option actions

* Update UiBaseLocators.ts

* Add additionalOptions to create manifests

* Add ellipsis to names in create content modals

* Update DataTypeUiHelper.ts

* Update DocumentTypeUiHelper.ts

* Update creation action locators and tests

* Update LanguageUiHelper.ts
2026-03-24 11:41:40 +01:00
Andy Butland 124c01cd6e Merge branch 'release/17.3.0' 2026-03-24 10:40:48 +01:00
Lee KelleherandGitHub 282e3af6b8 Entity Data Picker: Adds start node support to tree data-sources (#22172)
* Adds optional `requestStartNode`

to Entity Data Picker tree source confguration

* Changes the example Document data-source

to use a Document Picker for the start node,
instead of the Content Picker source.
As that is targeted across Documents, Media or Members.

* Example Documents data-source: implemented "start node"

* Renamed `requestStartNode` to `requestTreeStartNode`

* Code tidy-up
2026-03-24 09:21:18 +01:00
Andy ButlandandGitHub fd81ade58b Migrations: Fix package migrations not running after fresh install with packages (closes #22202) (#22204)
* Run package migrations synchronously on runtime restart after a fresh install.

* Add unit tests verifying fix and existing functionality.
2026-03-24 08:28:11 +01:00
Andy ButlandandGitHub 27c926940f Migrations: Fix retrust constraints migration targeting non-Umbraco tables and transaction failure (closes #22227) (#22229)
* Retrust only umbraco tables and catch errors at SQL level.

* Code review feedback.
2026-03-24 06:39:48 +01:00
Andy ButlandandGitHub 186498ef39 Dynamic Root: Fix current origin resolution for new unsaved content (closes #22213) (#22216)
* Fix issue where dynamic node query based from current node does not resolve for new documents.

* Add tests verifying the fix. General cleanup of code warnings in dynamic node implementations and tests.

* Addressed failing integration test and code review feedback.
2026-03-24 12:55:17 +09:00
Andy ButlandandGitHub 2859cb808a Management API: Add endpoint to get all member types allowed at root (#22226)
* Add endpoint for retrieving all member types allowed at root.

* Addressed code review feedback.
2026-03-24 12:33:54 +09:00
Andy ButlandandGitHub 51b7fbf5ee Tree Picker: Fix root item not deselecting in single-selection picker (closes #22073) (#22099)
* Ensure single-select tree doesn't allow selection of root and item.

* Add tests verifying behaviour.
2026-03-23 13:59:22 +01:00
3119b3a8ef Blueprints: Allow saving document blueprints with partial variant names (closes #22190) (#22210)
* Allow saving document blueprints with partial variant names.

* Address code review feedback.

* Use shallow copies instead of in-place mutation when filtering unnamed variants before delegating to base class validation.

---------

Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-03-23 13:27:11 +01:00
Sven Geusens d428cf2d5b Add v18/dev to nightly build trigger 2026-03-23 12:00:30 +01:00
dc8941fefe EFCore Scoping: Preserve connection string before disposing EFCoreScope database (closes #22211) (#22212)
* preserve connectionString befor disposing EfCoreDatabase during dispose of EfCoreScope. Fixed by Claude Sonnet 4.6

* Add details of integration tests to memory files.

* Ensure original connection string is captured and remove unnecessary guard.

* Add further test verifying the fixed behaviour.

* Test clean-up.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-23 07:09:59 +00:00
Andreas ZerbstandGitHub eb33dcebdf E2E: QA: add acceptance tests for compositions (#22180)
* Updated helpers

* Moved to specific test files

* Added tests with compositions

* Updated helper

* Run tests on pipeline

* Fixed

* Updated helpers

* Added tests for variants

* Added tests

* Updated smoke

* Fixed

* Cleaned up

* Moved to before each

* Reverted test command
2026-03-23 06:50:46 +00:00
Nicklas KramerandGitHub 112250da90 Distributed Background Jobs: Preventing Jobs From Running When Database Is Read-Only (#22208)
* Disabling distributed background jobs when database is readonly

* Adding changes in accordance to code review
2026-03-20 13:15:26 +01:00
597300863a E2E: QA Updated acceptance tests for duplication action due to UI changes (#22206)
* Added ui helper for copy button

* Updated tests since the duplicate button is replaced by the copy button

* Update tests/Umbraco.Tests.AcceptanceTest/lib/helpers/UiBaseLocators.ts

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>

---------

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2026-03-20 10:19:11 +00:00
Nhu DinhandGitHub 4c8cf2146c E2E: QA Added acceptance tests for public access (#22158)
* Added constant variables for public access notification message

* Added ui helper for public access

* Added api helper for setup and delete public access

* Added api helper for create default member group

* Updated tests to use createDefaultMemberGroup instead of the directly create api

* Added tests for setting public access on content

* Added api helper for verify public access

* Updated ui helper for verify public access

* Updated tests for public access

* Make tests run in the pipeline

* Fixed comment

* Reverted npm command
2026-03-20 07:44:37 +00:00
Andy ButlandandGitHub e28de80212 Integration Tests: Avoid hidden BootFailedException in CoreConfigurationHttpTests (#22188)
Avoid hidden BootFailedException in CoreConfigurationHttpTests.
2026-03-20 07:55:24 +01:00
Nhu DinhandGitHub 3611a28966 E2E: QA Added acceptance test for HMAC secret key health check (#22141)
* Added constant variable for healthCheckMessage

* Added appsetting file for imaging setting config tests

* Updates name

* Added project for imagingSettingConfig

* Added ui helper for verify health check of Imaging HMAC Secret Key

* Updated tests for HMAC secret key health check with default settings

* Added tests for HMAC secret key health check is not configured

* Makes test run in the pipeline

* Fixed comment

* Clean code

* Reverted npm command
2026-03-19 15:03:59 +00:00
Nhu DinhandGitHub 21bde287bb Build: Serialize E2E stages and stagger branch schedules to reduce agent usage (#22164)
* Serialize E2E stages and stagger branch schedules to reduce agent usage

* Removed unused condition

* Added condition
2026-03-19 21:19:23 +07:00
d0072a572e EFCore Scoping: Clear stale connection on pooled DbContext before returning to pool (closes #22124) (#22132)
* Clear stale connection on pooled DbContext before returning to pool.

* style: apply linter comment punctuation fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Removed unnessary test.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 14:50:34 +01:00
Andy Butland 4822ddd889 Distributed Locking: Add ROWLOCK hint to prevent cross-row contention on umbracoLock table (closes #22113) (#22126)
Use row lock for lock table.
2026-03-19 13:08:04 +01:00
Matthew CareandAndy Butland 48222951f3 Application URLs: Prevent back office hosts being overwritten in a shared database setup (closes #16741) (#22160)
* Add to backoffice hosts

Add to backoffice hosts, rather than completely replacing the array

* Add unit tests verifying fix.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-19 12:50:18 +01:00
Andy ButlandandGitHub a986268d28 Distributed Locking: Add ROWLOCK hint to prevent cross-row contention on umbracoLock table (closes #22113) (#22126)
Use row lock for lock table.
2026-03-19 12:26:27 +01:00
Andy ButlandandSven Geusens 51ae5b66d7 Migrations: Fix property detection for invariant content types with culture-varying compositions (closes #22159) (#22167)
* Extract shared culture-resolution logic from ConvertBlockEditorPropertiesBase, ConvertLocalLinks, FixConvertLocalLinks, and MigrateSingleBlockList into PropertyDataCultureResolver, fixing a bug where NULL languageId (legitimate invariant data) was incorrectly treated as a deleted language reference.

Add unit tests covering all resolution paths including the bug scenario.

* Remove obsoletion on helper.

* Address code review feedback.

* Handle SetValue variation mismatch for invariant data on culture-varying compositions

* Fixed build error in tests.

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
2026-03-19 12:08:16 +01:00
Andy Butland 49b3c24c9f Bumped version to 17.3.0-rc2. 2026-03-19 12:07:26 +01:00
Mads RasmussenandGitHub d76493fa76 Backoffice: Add tree item children collection views for Partial Views, Stylesheets, Scripts, Templates, and Document Blueprints (#22146)
* init implementation

* Add template tree item-children collection and views

* add base class

* Use Settings section for document blueprint paths

* Inline customElement names and update typings

* Make table collection view buttons compact

* remove collection action again as they require create options to be registered first

* move file

* fix export

* fix const exports

* Extract template tree repository alias to constants
2026-03-19 09:18:41 +00:00
b7f8a62f0d Migrations: Fix property detection for invariant content types with culture-varying compositions (closes #22159) (#22167)
* Extract shared culture-resolution logic from ConvertBlockEditorPropertiesBase, ConvertLocalLinks, FixConvertLocalLinks, and MigrateSingleBlockList into PropertyDataCultureResolver, fixing a bug where NULL languageId (legitimate invariant data) was incorrectly treated as a deleted language reference.

Add unit tests covering all resolution paths including the bug scenario.

* Remove obsoletion on helper.

* Address code review feedback.

* Handle SetValue variation mismatch for invariant data on culture-varying compositions

* Fixed build error in tests.

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
2026-03-19 10:11:51 +01:00
Andy Butland 21c988309a Merge branch 'release/17.3.0' 2026-03-19 06:48:12 +01:00
f27e5a1917 Application URLs: Prevent back office hosts being overwritten in a shared database setup (closes #16741) (#22160)
* Add to backoffice hosts

Add to backoffice hosts, rather than completely replacing the array

* Add unit tests verifying fix.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-19 06:37:29 +01:00
Johannes LantzandGitHub 0e6ce7d691 Localization: Added missing for elements (#22079)
* umb-clipboard-entry-picker-modal: added missing localizations

* umb-trash-with-relation-confirm-modal: added missing localizations

* umb-bulk-delete-with-relation-confirm-modal: added missing localizations

* umb-bulk-trash-with-relation-confirm-modal: added missing localizations

* umb-duplicate-to-modal: added missing localizations

* umb-document-duplicate-to-modal: added missing localizations

* umb-sort-children-of-modal: added missing localizations

* umb-content-type-design-editor: added missing localizations

* umb-entity-user-permission-settings-modal: added missing localizations

* umb-clipboard-entry-picker-modal: Removed haredcoded close

* umb-clipboard-entry-picker-modal: Adjusted headline localize

* umb-entity-user-permission-settings-modal: Changed to correct headline key
2026-03-18 09:48:22 +00:00
Nicklas KramerandGitHub 67008d349c Last Synced: Adding A File System Approach to Subscriber Servers (#22145)
* Adding a file system approach to subscriber servers

* Adding tests

* Alternative lazy injection

* Adding delegate unit tests and making classes internal sealed.

* Adding a check to see if database is readonly

* Modifying DatabaseReadOnlyAccessor.cs
2026-03-18 10:34:36 +01:00
634b1eed88 Media Picker: Add Cards/Table view switcher (closes #22005) (#22138)
* Add table view to media picker modal

* Use unique id in media picker selection handlers

* Add dateTime formatter and use in media picker

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Add dateTime localization tests

* localize view labels

* Persist media picker view in interaction memory

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-17 15:38:12 +01:00
3ece7b1276 Upload Field: Fix image overflowing content container (closes #22106) (#22107)
* fix(media): prevent upload field image from overflowing content container

The image element used `height: 100%` which resolved to a definite value
when rendered in the old flex-row layout (parent's stretch gave it a height).
After #21887 restructured the wrapper to flex-column, the parent no longer
provides a definite height, so `height: 100%` falls back to `height: auto`
and the image renders at its natural (potentially huge) dimensions.

Fix by giving `img` direct constraints (`max-width: 100%`, `max-height: 400px`,
`height: auto`) so it constrains itself regardless of the parent layout context.

Closes #22106

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style(media): remove redundant max-height from :host, keep on img

The max-height: 400px is now on the img directly, so the :host constraint
is redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(media): apply same image overflow fix to SVG upload preview

Same root cause as #22106: img relied on height: 100% resolving via
parent flex-stretch, which breaks in the flex-column layout from #21887.
Move constraints to img directly (max-width: 100%, max-height: 400px,
height: auto) and remove redundant/ineffective host properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style(media): move min-height from :host to img in image and SVG previews

With height: auto on img, min-height on :host left an empty gap when the
image was shorter than the minimum. Moving min-height to img ensures the
checkerboard background fills the full minimum preview area consistently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(media): prevent image cropper focus setter from blinking on upload

The #image element had no CSS size constraints, causing it to render at
its natural dimensions briefly before the onload handler applied
width/height: 100% via inline styles. Adding max-width/max-height: 100%
ensures the image is already constrained on first paint, eliminating the
reflow blink when uploading a new image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(media): use File object name for extension in file upload preview

When a file is dragged in before saving, the path is a blob URL
(blob:http://...) which produces a garbage extension when split on '.'.
The File object is already passed as a prop via the interface but was
unused. Prefer file.name for extension extraction when available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-03-17 12:46:23 +00:00
cfa74db61a Routing: Resolve URL segment collision for siblings differing only in punctuation (closes #22070) (#22090)
* Routing: Resolve URL segment collision for siblings differing only in punctuation (closes #22070)

When sibling documents have names that differ only in punctuation
(e.g. "Title" vs "Title."), the URL segment provider strips punctuation
and produces identical segments, causing routing conflicts.

Add collision detection in DocumentUrlService.CreateOrUpdateUrlSegmentsAsync
that checks sibling segments (from both the in-memory cache and the current
batch) and appends a numeric suffix (-2, -3, etc.) when a collision is found.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Routing: Move URL segment collision detection to DocumentRepository name uniqueness (closes #22070)

Reverts the DocumentUrlService approach (URL-level `-2` suffixes) in favour of
detecting collisions at the document name level. When two sibling names produce
the same URL segment (e.g. "Title" and "Title." both clean to "title"), the
existing `(1)` naming convention is applied to the name itself, which then
yields a distinct URL segment.

Changes:
- Revert DocumentUrlService collision resolution logic
- Override EnsureUniqueNodeName in DocumentRepository to augment sibling names
  with phantom entries for URL segment collisions (via IShortStringHelper)
- Apply same augmentation in EnsureVariantNamesAreUnique for variant content
- Add IShortStringHelper constructor dependency (with obsolete compat pattern)
- Add unit tests verifying the phantom entry approach

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Routing: Refactor URL segment collision to direct segment comparison

Replace the indirect "phantom entries" approach with a clearer two-step
strategy as suggested in review:

1. Call base.EnsureUniqueNodeName() to handle literal name duplicates
2. Fetch siblings, compute URL segments, and increment (N) suffix until
   the resulting segment is unique

This is easier to reason about and avoids manipulating the SimilarNodeName
algorithm. The trade-off is a second sibling fetch (same indexed query),
which only runs on save.

- Replace AugmentNamesForUrlSegmentCollisions with EnsureUniqueUrlSegment
- Apply same pattern in EnsureVariantNamesAreUnique
- Remove phantom entry unit tests from SimilarNodeNameTests
- Add integration tests on ContentService for both invariant and
  culture-varying content with punctuation-only name differences

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Updated usages of obsolete constructors.

* Avoid second look-up of siblings data.

* Make EnsureUniqueUrlSegment unit testable, and add tests.

* Pass content.Id rather than 0 in variant unique name check.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-17 09:17:43 +01:00
Henrik GedionsenandJason Elkin 7945bd408c Use Array.ConvertAll instead of LINQ .Select .ToArray 2026-03-16 21:21:10 +00:00
Andy Butland af9577e792 Redirect Tracking: Fix segment change detection and optimise descendant traversal (#22091)
* Optimise redirect tracker by avoiding re-producing of descendant nodes and avoiding descendant traversal when there has been no change to the node's URL segment.

* Delete inadvertently added file

* Allow URL segment providers to ensure descendent traversal if needed.

* Pushed missing files.

* Refactors to reduce large method code smells.
2026-03-16 09:21:17 +01:00
Andy ButlandandGitHub 7363183ef6 Redirect Tracking: Fix segment change detection and optimise descendant traversal (#22091)
* Optimise redirect tracker by avoiding re-producing of descendant nodes and avoiding descendant traversal when there has been no change to the node's URL segment.

* Delete inadvertently added file

* Allow URL segment providers to ensure descendent traversal if needed.

* Pushed missing files.

* Refactors to reduce large method code smells.
2026-03-15 16:24:37 +01:00
marcloveUSNandGitHub 7f9570c671 Block Editors: Resolves incorrect "Discard unsaved changes" message when editing blocks with live editing (#22134)
Change setOneContent to setOneSettings for initialSettings

Line 661 calls setOneContent() with settings data instead of setOneSettings(). This pushes the settings element into the contentData array.
2026-03-13 20:21:46 +01:00
f56bad8989 E2E: QA: Added document segemented variant acceptance tests (#21957)
* Updated naming

* Updated path to test files

* created tests

* Reverted retries change

* Updated imports

* Added step

* updates based on comments and clean up

* Added vars

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-13 11:03:42 +00:00
Nhu DinhandGitHub 8ae64d26fd E2E: QA Updated acceptance tests for bulk trash content due to UI changes (#22118)
* Added verfication step to avoid flaky

* Updated tests due to UI changes

* Format code

* Added waits
2026-03-13 16:27:42 +07:00
862e8a6e0a Code Documentation: Add missing XML header documentation to the Umbraco.Cms.Api.Management project (#21785)
* Adding code comments to Umbraco.Cms.Api.Management

* Update src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/UpdateMemberGroupController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/MoveDocumentBlueprintController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CopyDocumentTypeController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/IsUsedDataTypeController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixing a missing closing brace on return docs.

* Fixing issue raised by copilot.

Issue was:

Inconsistent use of T: prefix in cref attribute. Other parameters in this PR use the interface name directly without the T: prefix (e.g., <see cref=\"IContentTypeService\"/>). Remove the T: prefix for consistency.

* Fix broken <returns> tags.

* Fixed incorrect descriptions.

* Added missing description.

* Fix positioning of comments.

* Fixed indentation.

* Use standard text for view model properties.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Emma L Garland <1649855+emmagarland@users.noreply.github.com>
2026-03-12 17:38:34 +01:00
Andy ButlandandGitHub 155c0c1d64 Sections: Sort sections by display name in user group assignment (closes #22094) (#22112)
* Order user group selected sections and sections for selection by name.

* Sort by weight rather than alphabetically.

* Feedback from code review.
2026-03-12 16:34:33 +01:00
104d5986a1 Code Documentation: Add missing XML header documentation to the Umbraco.Cms.Infrastructure project (#21782)
* Adding lots of missing documentation

* Update src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/Extensions/ScopeExtensions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixing small issues and adding some more missing docs.

* Fixed indentation, blank lines and moved inline header comments into remarks.

* Fixed messages in UserRepository.

* Fixed indents in file scope namespaced files.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-12 15:15:16 +01:00
Niels Lyngsø 35c6b46fdd update comments 2026-03-12 14:17:42 +01:00
502cab9ff2 Temporary File: Lowercase file extension before validation (closes #22096) (#22108)
* fix(core): lowercase file extension before validating against allowed/disallowed lists

Fixes case-sensitive comparison in UmbTemporaryFileManager where uploading a
file with an uppercase extension (e.g. .PDF) would be incorrectly rejected
even when the lowercase extension (pdf) was in AllowedUploadedFileExtensions.

Closes #22096

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(media): lowercase SVG extension check in media links info app

Fixes case-sensitive .svg check so that media files with uppercase
extensions (e.g. .SVG) correctly use the SVG viewer link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(core): also lowercase config extension lists before comparison

The server may return extensions in any case (config is stored as-is).
Lowercase both sides to ensure the comparison is truly case-insensitive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Ensure server-side checks for file extensions are case insensitive.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-12 10:30:07 +00:00
6444a2d2d7 E2E: Updated the acceptance tests to match the recent changes (#22088)
* Updated multiURLPickerSettings as there is a new setting for Culture-specific document links

* Updated tests for verify the default configuration of multi url picker data type

* Increased time for waiting the loader icon disappears to avoid the flaky tests

* Updated tests for reset manual URL using remove button due to locator changes

* Added ui helper for card collection view in content

* Updated tests to reflect that grid view is now the default instead of list view.

* Updated ui helper for public access saving button due to UI changes

* Removed unused code

* Fixed comments

* Removed unused test folder

* Updated auth to clear storage

---------

Co-authored-by: Andreas Zerbst <andr317c@live.dk>
2026-03-12 09:44:15 +00:00
6447e63170 Add JsonSchema support to the Management API for datatypes and contenttypes (#21771)
* Basic implementaion

* Tests and schema validation

* Attemp refactor

* Fix json single parent bug

* Surface doctype schema validation to management api

* Improve block schema and make validation errors less verbose

* fix validation error cleanup

* Improved GUID handling | added schema for all propertyEditors

* Add ContentTypeInputSchema

* move contenttype schemas to be actual jsonschemas

* Fix block limit on blocklist and grid

* add datatype schema batch

* Refactoring blocks json schema generation and add to richtext

* Package version update and more tests!

* ConvertToJsonNode optimization

* async refactor

* Add editorUiAlias to x-umbraco-properties and make DataType ref route dynamic

* Removed JsonSchema.net due to possible license issues

* Use void editor in the noop schema test

* Cleanup leftovers from Schema validation removal

* Move batch logic into batchcontroller

* Update src/Umbraco.Infrastructure/PropertyEditors/BlockJsonSchemaHelper.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Services/ContentTypeJsonSchemaService.cs

Improve lookup on building propertymetadata

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Fixed build error.

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-12 09:32:38 +01:00
37994a87db Management API: Return descriptive 400 for property variance mismatch (closes #22076) (#22100)
* Provide more descriptive management API responses for invariant with variant composition.

* Improved messaging and fixed integration tests.

* Fixed ordering of new ContentEditingOperationStatus values so existing values retain their integer equivalent.

* Suppress breaking changes in integration tests.

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-03-12 08:57:53 +01:00
5aa3ccd88c E2E: QA Added acceptance tests for DisableDeleteWhenReferenced setting (#22017)
* Changed appsetting.json

* Added tests for disableDeleteWhenReferenced setting

* Added constant variable for descendingReferenceHeadline

* Moved doesModalHaveText to uiBaseLocator

* Updated warning message for bulk trash due to the recent changes

* Updated warningMessageForBulk

* Fixed comments

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-12 14:46:03 +07:00
Andy Butland 9271da4a73 Fixed mocks in media-type.db.ts. 2026-03-11 21:50:49 +01:00
Andy Butland 36e672ce8d Fixed mocks in media-type.db.ts. 2026-03-11 21:49:06 +01:00
Andy Butland b14dfcf92c Bumped version to 17.4.0-rc. 2026-03-11 20:56:43 +01:00
Andy ButlandandGitHub 43167710fa Content Picker: Fix item reference link navigation (closes #22085) (#22103)
Remove culture from document item ref href to fix content picker navigation.
2026-03-11 18:29:02 +00:00
be25c4b0a0 Referenced Items: Prevent move to recycle bin when referenced and DisableDeleteWhenReferenced is enabled (closes #21986) (#21999)
* Prevent move to recycle bin for documents and media when disable delete when referenced is configured.

* Addressed code review feedback and fixed failing client-side test.

* Add suppression for renamed integration test.

* Simplified solution by moving disableDeleteWhenReferenced setting to modal.

* Fix flicker.

* Apply disable on delete handling to bulk trash dialog.

* Add additional translations.

* Move disableDeleteWhenReferenced resolution to document and media action classes, so the value is passed as modal data rather than being resolved in the modal itself.

* Update OpenApi.json.

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-11 16:57:48 +01:00
b634e6a2d1 Media: Allow File media type as fallback when no specific extension match is available (closes #21733) (#22054)
* Allow "File" media type as fallback when no specific extension match is available at the upload location

* Added regression test.

* Addressed test feedback.

* Fix after merge.

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-11 16:53:26 +01:00
651cb0a419 Auth: Fix re-entrant /token call after OAuth code exchange (#22097)
Move #inSessionUpdateCallback guard into #setSessionLocally() so all
callers are protected, not just makeRefreshTokenRequest()'s lock callback.

Previously, completeAuthorizationRequest() called #setSessionLocally()
directly without setting the flag. With keepUserLoggedIn=true and a short
TimeOut, session$ observers fired synchronously inside #setSessionLocally,
triggering #onSessionExpiring → validateToken() → makeRefreshTokenRequest()
before #inSessionUpdateCallback was ever set — causing a second /token call
immediately after the initial code exchange 200.

The no-Web-Locks fallback path in makeRefreshTokenRequest() had the same
gap. Moving the flag into #setSessionLocally() covers all call sites.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:10:29 +01:00
Andy ButlandandGitHub 89de02dd5d Relations: Fix relation type detail navigation from collection list (closes #22092) (#22095)
* Fix display of relation type detail view.

* Add export to index file.
2026-03-11 14:48:41 +01:00
Andy ButlandandGitHub 694364960e Fix the CSP in our local project to support iframing the marketplace (#22093)
* Fix the CSP in our local project to support iframing the marketplace.

* Update to use constant and HTTP scheme.

* Use constant for news dashboard to
2026-03-11 14:24:43 +01:00
14a047b090 Auth: Skip /token refresh when access token is still valid (#22087)
* Auth: Skip /token refresh when access token is still valid

Guard the per-request validateToken() call sites with #isAccessTokenValid()
in configureClient() and getLatestToken(). Previously, every API request
triggered a /token call even when the access token had not expired, causing
unnecessary token churn and OpenIddict ID2019 errors for in-flight requests.

Proactive refresh via UmbAuthSessionTimeoutController and startup validation
in app-auth.controller.ts are unaffected — those call validateToken() directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auth: Remove redundant first-check validateToken() on app startup

setInitialState() already handles server verification before the router
evaluates guards — either via a direct /token call (makeRefreshTokenRequest)
or via peer session adoption (BroadcastChannel). The #isFirstCheck guard in
UmbAppAuthController was a leftover from the AppAuth/localStorage era, where
token state was restored from storage and needed a server round-trip to confirm
validity. That assumption no longer holds: if getIsAuthorized() is true after
setInitialState(), the session came directly from the server or from a peer
whose timing is still valid. Stale/revoked peer sessions are handled lazily
by the 401 interceptor, which triggers re-auth as needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auth: Wait for ongoing cross-tab refresh before sending requests

Restores the cross-tab lock serialization that was implicitly provided by
the old unconditional validateToken() call. When another tab holds the
umb:token-refresh lock (keepUserLoggedIn proactive refresh), API requests
in this tab now wait for it to complete before proceeding. This prevents
sending requests with an access token that is about to be revoked, which
caused OpenIddict ID2019 errors on in-flight requests.

The fast path (token valid, no refresh in progress) remains: navigator.locks.query()
is a cheap browser-internal call, and the lock.request() no-op is only
incurred when a cross-tab refresh is actually happening.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auth: Extract #ensureTokenReady(), improve naming and JSDoc

- Extract duplicate guard logic from configureClient() and getLatestToken()
  into a single #ensureTokenReady() private method
- Rename from #ensureValidToken() → #ensureTokenReady() to distinguish from
  the validate/valid naming cluster (validateToken, isAccessTokenValid)
- Add JSDoc to #isAccessTokenValid() clarifying it is a local timestamp check
  with no network call
- Improve JSDoc on validateToken() to make clear it forces a network refresh
  (unconditional /token call), distinct from the per-request #ensureTokenReady()
  gate which skips the call when the access token is still live

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(auth): prevent re-entrant /token call when session$ fires synchronously inside lock

With keepUserLoggedIn=true and a short access token lifetime (e.g. expiresIn ≤ buffer),
#updateSession() triggers session$ synchronously inside the lock callback. The observer
fires #scheduleCheck → #onSessionExpiring → validateToken() before the lock is released.
This re-entrant call captures sessionBefore = newSession (already updated), so the
reference guard cannot detect it, resulting in a duplicate /token request.

Fix by tracking #inSessionUpdateCallback around the #updateSession() call. Re-entrant
callers return true immediately; concurrent non-re-entrant callers are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 11:23:39 +00:00
6d9fdec8b1 Auth: Fix preview window stuck loading after Save and Preview (closes #22083) (#22089)
The window.opener guard in #setAuthStatus() was too broad — it skipped
setInitialState() for ANY window opened via window.open(), including the
preview window. This left isAuthorized stuck at false in the preview window,
causing the loading spinner to never resolve.

The guard is only needed for the OAuth code exchange popup (oauth_complete),
where calling setInitialState() could silently refresh the session, set
isAuthorized=true, and cause the popup to redirect to the backoffice instead
of completing the code exchange.

Fix: narrow the guard to window.opener + pathname === '/oauth_complete'.
The preview window (at path /preview) now correctly calls setInitialState(),
which restores the session from a peer tab via BroadcastChannel.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 10:13:56 +00:00
93b8560035 External Login Providers: Set SignOutRedirectUrl on backoffice sign-out to support external OIDC provider logout (closes #21854) (#21952)
* Fix issue signout oidc external login provider

* Update src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-11 09:13:39 +00:00
Jacob OvergaardandClaude Sonnet 4.6 0263245b36 Docs: Add backoffice CLAUDE.md reference and frontend auth pitfalls to root CLAUDE.md
- Add src/Umbraco.Web.UI.Client/CLAUDE.md to Project-Specific Documentation
  (was notably absent alongside Core and Api.Common)
- Expand Authentication section with frontend pitfalls: validateToken() per-request
  danger, window.opener scope issue, BroadcastChannel sender exclusion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 09:30:29 +01:00
Jacob OvergaardandClaude Sonnet 4.6 4bc2cb4bdc Docs: Document auth architecture and cross-tab coordination edge cases
security.md:
- Expand auth section with v17 httpOnly cookie model, [redacted] pattern,
  configureClient() usage, and explicit warning against calling validateToken()
  per request (causes token churn and ID2019 errors)

edge-cases.md:
- window.opener is set for any window.open() target, not just OAuth popups —
  must check pathname too (root cause of #22083 preview regression)
- BroadcastChannel does not deliver to the sender — use local-only setters
  inside handlers to avoid N² broadcast storms
- sessionRequest must guard with isSessionValid() before responding
- Web Lock umb:token-refresh pattern for cross-tab refresh deduplication

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 09:27:43 +01:00
a113ceae41 Backoffice: Update vite from 7.1.11 to 7.3.1 (#22065)
* Backoffice: Update vite, vite-plugin-static-copy, vite-tsconfig-paths

- vite: ^7.1.11 → ^7.3.1
- vite-plugin-static-copy: ^3.1.3 → ^3.2.0
- vite-tsconfig-paths: ^5.1.4 → ^6.1.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* build(deps): bumps vite in umbracoextension template

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-11 09:13:19 +01:00
Andy ButlandandGitHub 959f7d57d2 Public Access: Align state and initial display of toggles and buttons on modal (#22086)
Align state and initial display of toggles and buttons on public access modal.
2026-03-11 08:55:53 +01:00
38f68f007e E2E: QA Updated the UI helper to verify that the image cropper is rendered (#22046)
* Updated ui helper to verify the image cropper is rendered

* Added .skip for the failing tests due to the actual issue

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-11 03:47:17 +00:00
fb5cad6e86 E2E: QA: Updated locator to find rollback button on the document workspace (#22030)
Updated locator to find rollback button on the document workspace

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-11 10:12:21 +07:00
6acb0ba002 Management API: Add batch read endpoints for Document Types, Media Types, Member Types, and Data Types (#21565)
* Add bulk fetch endpoints for retrieving full details for multiple entities by provided IDs, for data, document, media and member types.

* Switch to GET endpoints.

* generate new managment api types + sdk

* Update to use "batch" over "fetch".

* Update OpenApi.json and client-side types/sdk.

* Add endpoint summaries and descriptions.

* Align controller method signatures with use of HashSet<Guid> over Guid[].

* Backoffice Performance: Client-side bulk fetch of Element Types for Blocks, Content Type Compositions, and Data Types to reduce API requests (#21610)

* Add readMany for document type details

* Add batch read methods to detail interfaces

* Pre-register content-type structures and bulk load

* Add readMany support to detail request managers

* Simplify loadType and delegate to setType

* add js docs to detail data request manager

* add unit tests for detail data request manager

* Add byUniques support to detail store/repository

* implement readMany for data types

* fix typescript errors

* Preload and pass data type details to properties

* Update content-type-structure-manager.class.ts

* Replace per-property UmbDataTypeDetailRepository requests with the structure manager's bulk-loaded data type details

* Deduplicate inflight detail read/readMany requests

* Use 'read:' inflight cache key prefix

* Add bulk detail requests & status helpers

* Add management API request/cache for media/member types + requestByUniques support

* use observe controller instead of rxjs

* adjust to new apis

* rename prop to make it easier to read

* throw on error

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>

* remove unused import

* Fixes to failing E2E tests.

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-10 22:04:43 +01:00
d8e1318290 Notifications: Correct the deep link URL in notification emails (closes #22047) (#22050)
* Fixed link in notification to editable document.

* Update translations using legacy mail format.

* Delete inadvertently added file

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-10 22:03:02 +01:00
23bc9ed702 Public Access: Preserve ancestor settings in dialog when setting up protection (closes #21740) (#21742)
* Allowed for easier public access management.

* Revert the update controller as that is being handled by the frontend.

* Cleaning up pull request

* Preserving obsolete function, updating controller to pass optional parameter.

* pass in the includeAncestors parameter

* Added in an alert message for when the permissions are being inhereited.

* Complete resolution of breaking changes on IPublicAccessPresentationFactory.

* Update call to controller from integration tests.

* Fixed variable name typo and whitespace.

* Added clarifying comment to client-side behaviour.

* Supressed the breaking change on the controller with the additional parameter.

* Added unit tests for PublicAccessPresentationFactory.

* Added localisation for ancestor label.

* Typo and whitespace.

* Updating the model to allow for switching between methods while still preserving ancestor selections.

* update locatlizations

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-10 19:32:58 +00:00
fbb5d871be Link Picker, RTE: Support linking to a specific culture (#21466)
* add language selection for link picker

* update model for link and rte when have culture

* resolve illegal imports

* update ApiLink

* Update ApiLink create content

* Update LocalLinkTag

* remove culture from picker modal

* remove culture from picker input

* update unit test, process culture from modalValue

* Use compile time regular expressions.

* wip custom document picker for multi url picker

* Set document link picker modal size to small

* render variant aware picked document item

* utiliza variant context in link picker modal

* Update link-picker-modal.element.ts

* remove unused

* remove unused

* remove unused

* remove unused

* Update types.ts

* Update tree-picker-modal.element.ts

* Update document-picker-modal.token.ts

* Update document-item-ref.element.ts

* Update document-item-data-resolver.ts

* Update document-item-data-resolver.ts

* Update tree-picker-modal.element.ts

* Update tree-picker-modal.element.ts

* Update tree-picker-modal.element.ts

* remove unused

* fix lint errors

* Update document-link-picker-modal.element.ts

* Skip language selector when <=1 language

* Fix typo in variant context comment

* Use strict equality for document type check

* Localize document picker headline

* remove unused default language

* Don't fallback culture when updating link

* Update document-link-picker-modal.element.ts

* use uui-combobox for a11y benefits

* remove unused code

* Cache repositories and reuse data resolvers

* clean up

* Update input-multi-url.element.ts

* Add multi-url-picker constants exports

* Update index.ts

* Tidied up code comments and attributes following merge. Addressed code review comments.

* Initialize link picker in async firstUpdated

* Await pickerSelect in onPickerSelection

* Await variant context & picker select calls

* Await setCulture in language handler

* Include culture in document edit href

* Add data type config for culture specific document links

* Localize culture-specific document link UI

* change of wording for configuration

* use Auto (visitor's language) for default option

* localization updates

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-10 18:00:21 +00:00
Niels Lyngsø dd89a147d3 Merge branch 'v17/improvement/refactor-21186-with-one-js-cycle' 2026-03-10 17:36:12 +01:00
Niels Lyngsø 564ec5b490 fix decorator test 2026-03-10 16:14:07 +01:00
Niels LyngsøandGitHub 4723713a31 Core: Minimize await to a single JS cycle (refactor #21186) (#22074)
* refactor to use Abort Controller instead of requestAnimationFrame

* dismantle currentScope immediately when disconnected

* improve life cycle
2026-03-10 15:08:42 +00:00
Niels LyngsøandGitHub d957c99442 Merge branch 'main' into v17/improvement/refactor-21186-with-one-js-cycle 2026-03-10 15:26:45 +01:00
Niels Lyngsø f2269aaa4c use undefined for no currentScope 2026-03-10 15:26:06 +01:00
Johannes LantzandGitHub 1e8ef4e644 Localization: Added missing Japanese translations (#22056)
* Added missing Japanese translations

* Formatted japanese localization file
2026-03-10 15:13:19 +01:00
Niels Lyngsø 6076a582ee extension-slot tests 2026-03-10 15:12:40 +01:00
Niels Lyngsø b09e7781ba apply extreme life cycle tests 2026-03-10 15:02:23 +01:00
Niels Lyngsø f976df164f use queueMicrotask 2026-03-10 14:54:25 +01:00
aa993af648 Auth: Fix popup flow showing backoffice after session timeout re-auth (#22071)
* Auth: Fix popup flow showing backoffice after session timeout re-auth

When a session times out client-side, the parent tab's #session was still
non-null (the timeout signal fires without clearing the session). When the
re-auth popup opened and called setInitialState(), it sent a sessionRequest
via BroadcastChannel. The parent responded with the expired session because
the handler only checked `if (session)` — not if the session was still valid.

The popup's auth context then thought it was already authorized, causing the
oauth_complete handler to hit the early-return `redirectToStoredPath` instead
of completing the authorization code exchange. The popup navigated to the
backoffice instead of exchanging the code and closing.

Fix: only share the session in response to sessionRequest if isSessionValid()
returns true (i.e. session.expiresAt > now).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auth: Fix re-auth popup not opening on session timeout

Two issues:

1. When the countdown modal timer reached 0, it called onLogout() -> signOut()
   which performed a full page redirect to /logout before timeoutSignal could
   fire. The re-auth popup (makeAuthorizationRequest('timedOut') in
   UmbAppAuthController) was never triggered. Fix: reject the modal on timer
   expiry instead of calling onLogout(). The catch block in #openTimeoutModal
   then calls #tryValidateToken(); if the refresh token is still valid the
   session is silently renewed, otherwise timeOut() fires -> timeoutSignal ->
   re-auth popup opens.

2. Only the Web Lock leader tab was showing the timeout countdown modal.
   All tabs should show the warning so the user can respond from any active
   tab. Remove the lock-leader election logic — show the modal on every tab.
   When any tab successfully refreshes (Continue button or silent refresh), the
   session$ observer fires in all tabs, closing the modal everywhere.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auth: Show re-auth popup when timeout countdown expires

When the countdown reaches zero the user was away and the session has
effectively expired — silently refreshing is the wrong behaviour. Instead:

- Add onExpired callback to UmbModalAuthTimeoutConfig, called (instead of
  onLogout) when the countdown hits 0.
- The controller sets onExpired -> timeOut(), which clears the session and
  fires timeoutSignal. UmbAppAuthController picks this up and calls
  makeAuthorizationRequest('timedOut'), opening the re-auth popup so the
  user can sign back in without losing their work.
- The modal uses submit() (not reject()) on expiry so the catch block's
  tryValidateToken() is not triggered.
- The Logout button still calls signOut() as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auth: Close re-auth modal on other tabs when session is restored

When all tabs showed the re-auth modal and the user signed in on one tab,
the authorized BroadcastChannel message updated every other tab's auth
context but nothing triggered the modal to close on those tabs.

Fix: observe isAuthorized in UmbAppAuthModalElement. When it becomes true
(either from local sign-in or from another tab's BroadcastChannel message),
call #onSuccess() to submit and close the modal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: adds null guard

* docs: updates CLAUDE.md to let it know that there is a circular check call

* fix: fixes issue where the popup window could redirect to and show the full backoffice inside

* fix: ensures that the timeout modal is not shown until the buffer window is reached and extend the buffer window in case of short timeouts, and use the full expiresAt value for timeout but only the accessTokenExpiresAt for refresh of token

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 13:48:10 +00:00
Niels Lyngsø 5898a6b36b tests for disconnection life cycle 2026-03-10 14:36:10 +01:00
Niels LyngsøandGitHub 56f269e3ab Merge branch 'main' into v17/improvement/refactor-21186-with-one-js-cycle 2026-03-10 14:32:04 +01:00
Niels Lyngsø 2bd69fe329 improve life cycle 2026-03-10 14:30:03 +01:00
Andy ButlandandGitHub 3aa41920ce Dependencies: Update MailKit to 4.15.1 (#22028)
Update MailKit to 4.15.1.
2026-03-10 14:13:55 +01:00
e1ffb63aff Routing: Safely ensure AliasUrlProvider URLs have a leading slash (#22068)
* Append leading / to AliasUrlProvider URLs only if it doesn't already have one

* Add unit tests for AliasUrlProvider.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-10 12:09:39 +00:00
d6892ec06c Management API: Defensively handle path integrity issues when resolving ancestors (closes #21822) (#22036)
* Defensively handle node path integrity issues when resolving ancestors.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 13:06:16 +01:00
f324f4cd0d Member Service: Fix skip/take pagination in GetAll (closes #22006) (#22010)
* Fixed issue with GetAll on MemberService where skip/take weren't translated to pageIndex/pageSize.

* fix(core): fix paging in MemberService.GetAll skip/take overload

The skip/take overload was passing skip and take directly as pageIndex
and pageSize to the repository, causing incorrect pagination for any
non-zero skip value. Use PaginationHelper.ConvertSkipTakeToPaging to
correctly convert skip/take to page index/size, matching the pattern
used by all other services.

Also update ContentTypeIndexingNotificationHandler to call the
pageIndex/pageSize overload directly, avoiding the redundant conversion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Treat empty or whitespace filter as no filter in MemberService.GetAll

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:50:00 +01:00
Andy ButlandandGitHub 93a38eb707 Relations: Exclude relateParentDocumentOnDelete from EmptyRecycleBin reference check (closes #21926) (#21954)
Fixes ability to empty the recycle bin when DisableDeleteWhenReferenced is set to true.
2026-03-10 12:43:47 +01:00
b3f5ba3652 Auth: Addresses regression where you could not configure separate auth cookie names (closes #22049) (#22057)
* feat: adds new SiteName setting to cookie options to use as a postfix for oauth cookies

* fix: adds configured postfix to oauth cookies to make them work on multiple sites on same domain (fixes regression)

* fix: addresses an issue where the AuthCookieName option was not respected for the _EXPOSED auth cookie

* Update src/Umbraco.Core/Configuration/Models/BackOfficeTokenCookieSettings.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Moved the "exposed" cookie config to IConfigureNamedOptions

* Add missing constants

* Add unit tests to prove the site postfix

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 12:28:26 +01:00
5e31ac2410 Backoffice: Fix circular dependencies introduced by #21830 and #21846 (#22064)
* Backoffice: Fix circular dependencies introduced by PRs #21830 and #21846

Two circular dependency chains were created by the combination of recent
auth rewrites and the auth modal split:

1. `resources ↔ auth`: api-interceptor.controller imported UMB_AUTH_CONTEXT
   from auth, while auth.context imported UmbApiInterceptorController from
   resources.

2. `server → resources → auth → server`: umb-auth-view.element imported
   UMB_SERVER_CONTEXT from the server package, and was reachable from
   auth/index.ts via the components barrel added in #21846.

Fix for circular 1: Introduce UmbAuthSignalerContext in resources — a
lightweight bridge context with isAuthorized and requestTimeout(). The
interceptor creates it and owns it directly; auth context consumes it via
consumeContext to bridge its own authorization state and react to timeout
signals. Resources now has zero knowledge of the auth package.

Fix for circular 2: Remove umb-auth-view.element from auth/components/index.ts.
The modal already imports it directly within the package; app-auth.element
uses it as a custom element tag string with no class import needed.

Also updates MAX_CIRCULAR_DEPENDENCIES from 1 → 0 since both known cycles
are now resolved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Backoffice: Fix circular dependencies - part 2

- Remove auth dependency from server.context.ts: replace eager constructor
  side-effect (consumeContext + HTTP fetch) with lazy defer()-based observable
  using a backing field flag; fetch only happens on first subscription to
  isProductionMode
- Re-add umb-auth-view.element.ts to auth/components barrel (now safe since
  server no longer imports from auth)
- Ensure umb-auth-view is registered on the /logout route by adding a
  side-effect import in app-auth.element.ts
- Fix JSDoc in auth-signaler.context.ts and api-interceptor.controller.ts to
  correctly describe ownership and direction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 11:05:35 +00:00
d42e8114c5 Account login: Separate AllowConcurrentLogins settings for users and members (closes #21667) (#21940)
* Make cookie renewal conditional to fix AllowConcurrentLogins enforcement.

* Reduce SecurityStampValidatorOptions validation interval for users to zero.

* Apply member security stamp options.

* Addressed code review feedback.

* Add separate settings for AllowConcurrentLogins for members and users.

* Clarify comment.

* Further unit tests as suggested by code review.

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2026-03-10 10:46:12 +00:00
dbc0b430ce Templates: Add direct Swashbuckle dependency to extension template (closes #21864) (#21869)
Ensure Swashbuckle version is aligned with Umbraco in the extension template.

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2026-03-10 10:28:34 +00:00
f3a369a5c0 Migrations: Run unattended upgrades in background, add liveness/readiness health probes (closes #21987) (#22020)
* Move unattended migrations to a background service, allowing liveness checks to recognise the application as healthy but not yet ready to serve requests.

* Add maintenance protection to surface controllers.

* Add protection for delivery API in upgrading state.

* Add protection for management API in upgrading state.

* Skip dynamic route transformer during Upgrading state (ensures surface controllers with attribute routing are handled in the upgrading state).

* Fix regression in attended upgrade state.

* Addressed code review feedback.

* Tidied up comments.

* Scope readiness health check predicate to Umbraco's own check.

* Fixed failing integration test.

* Removed TestCase from test with only a single case.

* Fix localization for "backoffice"

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>

* Removed UpgradeFailed from OpenApi.json and client-side types.

---------

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-03-10 11:28:23 +01:00
Andy ButlandandGitHub ca2397a603 Account login: Enforce AllowConcurrentLogins for backoffice users and members (#21928)
* Make cookie renewal conditional to fix AllowConcurrentLogins enforcement.

* Reduce SecurityStampValidatorOptions validation interval for users to zero.

* Apply member security stamp options.

* Addressed code review feedback.
2026-03-10 11:05:51 +01:00
Niels Lyngsø 1478df4d4e dismantle currentScope immediately when disconnected 2026-03-10 10:46:17 +01:00
Niels Lyngsø ea5dbfa56f refactor to use Abort Controller instead of requestAnimationFrame 2026-03-10 10:11:01 +01:00
f2ecac6055 Dependencies: Updates @umbraco-ui/uui to 1.17.2 to fix multiple folder drag-and-drop failing (closes #21837) (#21886)
* fix(media): ensure sequential creation in media drag-and-drop

When multiple folders are dragged into the Media section, the creation
handlers (#handleFile/#handleFolder) were not awaited in the batch loop.
This caused child items to attempt server operations before their parent
folders were fully created, resulting in 404 errors for subsequent items.

Adding await ensures each item is fully created before the next is
processed, which is required because child items in the flat list
reference parent folder IDs that must exist on the server.

Closes #21837

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Task: Bump @umbraco-ui/uui to 1.17.2

Includes the fix for multi-folder drop DataTransfer staleness
(umbraco/Umbraco.UI#1339).

* qa(dropzone): add unit tests for UmbDropzoneManager folder flattening order

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:29:49 +01:00
Andy ButlandandGitHub 6cf80a0723 Migrations: Run AddSortableValueToPropertyData before MoveDocumentBlueprintsToFolders (#22063)
* Ensures all columns on property data exist before an earlier migration that requires them runs.

* Clarified comment.
2026-03-10 08:24:21 +00:00
Andy Butland 1dc35d59c1 Merge branch 'release/17.2.2' 2026-03-10 06:41:46 +01:00
11a412c0fd Merge commit from fork
* Add authorization checks for domain operations.

* Remove duplicate 403 ProducesResponseType attributes.

BackOfficeSecurityRequirementsOperationFilterBase already adds 403
responses for endpoints whose controllers inject IAuthorizationService.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-03-10 05:11:16 +01:00
Andy ButlandandGitHub 2624b25e38 Merge commit from fork 2026-03-10 05:10:31 +01:00
Andy ButlandandGitHub 5f389f8bb4 Merge commit from fork
* Protect endpoint that sets user groups for a user collection to prevent elevation of permissions for users.

* Update tests from code review feedback.
2026-03-10 05:07:41 +01:00
3220526151 Entity Data Picker: Add configurable Picker Views for Collection Data Sources (#21738)
* Add alias property to collection config interface

Introduced an 'alias' property to the UmbCollectionItemPickerModalCollectionConfig interface

* render collection element when modal is configured with an alias

* expose a picker modal route

* use collection in use picker

* adjust spacing

* add config option for selectOnly

* dynamic modal alias

* support selectable entity item ref

* wip entity data picker collection + ref and card views

* Add entity collection item card extension type + default elements

* implement user collection item card

* fix selection events

* map to prop

* add prop/attr for href

* add support for which detail properties to show

* update type import

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/item/entity-collection-item-card/entity-collection-item-card.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* import card in correct file

* Fix event listener binding for selection events

* implement disabled property for collection item cards

* init commit of collection item ref extension

* fix imports

* add element interface

* Implement UmbEntityCollectionItemElement interface in item cards

Added the UmbEntityCollectionItemElement interface to document and user collection item card elements for improved type safety and consistency. Updated type exports to include the new interface.

* Update collection item ref to use uui-ref-node

Replaces the placeholder div with a uui-ref-node component, passing relevant item properties and event handlers. Adds dynamic icon rendering using umb-icon.

* Refactor entity collection item elements to use shared base

Introduces a new abstract base class for entity collection item elements, consolidating shared logic for card and ref variants. Updates card and ref element implementations to extend the new base, and refactors extension manifest interfaces for consistency. This improves maintainability and reduces code duplication.

* use class instead of magic string

* Use entity collection item card in picker view

Replaces the placeholder card markup with the <umb-entity-collection-item-card> component, enabling selection and deselection functionality for items in the entity data picker card collection view.

* Update entity item ref to collection item ref

Replaces <umb-entity-item-ref> with <umb-entity-collection-item-ref> in the picker collection view. Adjusts event handlers and select-only logic to improve selection behavior and component consistency.

* utilise ref and card kind for picker views

* introduce ref and card collection view kinds

* Utilise card kind for user collection view

* Add item-specific href support to collection views

Introduces a requestItemHref method to collection contexts for retrieving item-specific hrefs. Updates card, ref, and user table collection views to use these hrefs, enabling dynamic linking for collection items. Refactors user table name column layout to accept href via value prop instead of constructing it internally.

* Update ManifestCollectionView import path

Changed the import of ManifestCollectionView from '../extensions/types.js' to '../view/types.js' to reflect its new location.

* remove unused

* use size medium for entity collection item picker

* use box

* render entity actions

* use edit path builder for user links

* rename method

* Revert "rename method"

This reverts commit 4df577688e.

* Update collection-default.context.ts

* make type lint ignore unused args with an underscore

* temp remove unused

* only make collection vie selectable if there are any registered bulk actions

* don't render name link if there is no href

* fix imports

* Render selection actions only if bulk actions exist

* use selectable state

* Update language-table-collection-view.element.ts

* Update language-table-collection-view.element.ts

* Update card-collection-view.element.ts

* clean up

* Refactor collection views to use shared base class

* refactor(collection): parallelize href fetching and make method private

* docs(examples): update collection example to use card and ref kinds

* docs(examples): add icon property to collection example data model

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/view/types.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update collection-bulk-action.manager.test.ts

* Removed duplicate and redundant '@typescript-eslint/no-unused-vars' rule definitions, consolidating the configuration to use only 'argsIgnorePattern'.

* Handle missing user href in name column layout

Replaces the user name link with a span when the href property is not provided, preventing broken links in the user table name column layout.

* Update user-table-name-column-layout.element.ts

* pass modal data and value to routable modal

* Update picker-input.context.ts

* support selectableFilter

* scaffolding of a collection text filter extension

* Refactor collection text filter to use API interface

* Fix incorrect tag

* Update types.ts

* Update collection-text-filter.extension.ts

* Add cancelation to debounced search on destroy

* clean up

* add js docs

* two way binding of filter value

* clean up

* Add collection text filter manifest example

Introduced a new filter manifest for the example collection and updated the main manifests file to include it. This enables a text filter extension for the example collection.

* Delete unused element and context

* Update src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.context-token.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update user-group-table-collection-view.element.ts

* support search for tree item and collection item pickers

* add spacing between collection ref items

* add margin between picker search result items

* remove spacing after last item

* remove padding in search results

* Update collection-item-picker-modal.element.ts

* move select only logic to collection selection manager

* add tests for collection selection manager

* change to filter label instead of search

* delete unused user grid collection view

* Select-only mode is now only disabled when all items are deselected, rather than on every deselection.

* prepare umb table for pickers

* utilize UmbCollectionViewElementBase in user table collection view

* remove console log

* handle select all and select item from same event

* bulk actions workaround

* add bulk action in collections feature toggle

* remove unused method

* make fields optional to avoid a breaking change

* remove unused import

* fix typescript errors

* adjust search styling

* hide with css

* fix ts errors

* Add modal data support to picker input context

Introduces methods to set and get modal data in UmbPickerInputContext, allowing base configuration for picker modals. Updates modal data handling to merge stored modal data with provided data for both direct picker opening and modal route setup.

* Fix bulk action manager test initialization

Added calls to setConfig in tests to properly initialize the observer before subscribing to hasBulkActions. Simplified the test logic for checking emissions when actions are present.

* Update tree-picker-modal.element.ts

* Update picker-search-result.element.ts

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/view/umb-collection-view-element-base.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Use ifDefined for modal route in user input button

* Use ifDefined for href binding in entity data picker

* Fix collection alias binding in item picker modal

* wire up user table collection view with selectableFilter

* clean up controller aliases

* Update collection-item-picker-modal.element.ts

* Update collection-item-picker-modal.element.ts

* Add support for collection items with thumbnails

Introduces thumbnail support for collection items by extending models and updating the default collection item card to render thumbnails when available. Adds a new example data source and manifest for items with thumbnails, and updates grid styling for card views.

* Improve card grid responsiveness and card sizing

Added a new CSS variable for large card min-width and updated the card grid to use container queries for responsive column sizing. Adjusted user card styles to ensure proper sizing and layout within the grid.

* add example image to thumbnail example

* introduce generic card component

* wip picker views configuration

* Update manifests.ts

* store value as alias

* Improve handling of missing collection view manifests

Refactors manifest storage to use a Map for faster lookup by alias and updates rendering logic to handle missing manifests gracefully. Now displays a 'not found' message with a remove button for missing collection view manifests.

* add sorting

* rename

* Add confirmation modal before removing picker view

* remove unused

* move collection selectOnly logic to context

* Update user-picker-modal.token.ts

* Add data-source package and integrate in input-entity-data

* Add optional description to collection items

* introduce extension picker data source

* fix problem with shallow copy because of js module in object

* nest manifest data

* Hide pagination when all items are shown

* Add a fallback page size

* merge extension insight code with extension code

* clean up

* Add optional description support to default item ref

* Revert "Add data-source package and integrate in input-entity-data"

This reverts commit e02881e8b6.

* fix post merge

* add input-extension utilizing input-entity-data

* proxy value and selection

* add todo

* temp hardcode config

* add typed config model

* Support multiple extension types in filters

* Use extensionTypes filter and deprecate type

Standardize extension collection filtering by introducing extensionTypes and phasing out the old type field.

* More explicit type name

* Expose allowedExtensionTypes as a @property on UmbInputExtensionElement

* Add text filter support for entity data picker

* remove reexport as this is not public available

* remove unused

* clean up

* clean up

* Add storage and getter for allowedExtensionTypes

* Inline collection view alias and remove constant

* Update vite.config.ts

* Update manifests.ts

* Update extension.picker-data-source.ts

* add tests for extension picker data source

* change to an observable feature config

* make feature object optional

* add unit tests

* Reference condition class directly in manifests

* Simplify collection view types and refactor setup

* remove todo

* implement input-extension on picker views configuration

* Add collection view aliases and defaults

* use correct type

* make name optional

* remove debugger

* delete - merge gone wrong. They are now called figure-cards

* map views to layouts

* Add viewsOverride to enforce collection layout order

* clean up observers if data source type changes

* remove unused

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-03-09 17:34:14 +00:00
Rick ButterfieldandGitHub e127bbc3ae Custom Views: Prevent re-rendering Block Views and Properties (#21186) 2026-03-09 17:03:15 +00:00
Andy ButlandandGitHub 301d3c98ba URL Picker: Fix title field only showing first character when typing URL (closes #22048) (#22053)
* Ensures title is set in full, and only updated when not already set, when entering a URL manually.

* Addressed code review feedback.
2026-03-09 16:55:59 +00:00
4ab34b1a2b Auth: Split auth modal into reusable view and introduce new non-dismissable modal type (closes #19628) (#21846)
* Auth: Split auth modal into reusable view and thin modal wrapper

Extract the full login screen UI from umb-app-auth-modal.element.ts into
a standalone umb-auth-view.element.ts that extends UmbLitElement. The
modal becomes a thin wrapper that delegates rendering to the view and
bridges onSuccess to _submitModal().

The view defaults userLoginState to 'loggedOut', so the /logout route
renders it directly as a component without needing to cast or configure
properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Fix imports and add readonly to styles in umb-auth-view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: import directly from main app itself to avoid dynamic imports

* Auth: Reopen timeout modal on dismiss and fix login layout height

Reopen the auth modal in a loop when the session has timed out, so
the user cannot dismiss it without re-authenticating. Fix login
layout height from calc(100vh - 64px) to 100vh with box-sizing.

Height fix credit: Lan Nguyen (PR #19843, closes #19628)

Co-Authored-By: Lan Nguyen <lan@umbraco.dk>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Fix timeout modal reopen by removing explicit modal key

The do/while loop to reopen the modal on dismiss was failing because
reusing the same key caused a race condition in the modal manager —
appendToFrozenArray replaced the old entry but the container's
_modalElementMap still held the stale key, preventing creation of
the new modal element. Letting each open() generate a unique key
via UmbId.new() avoids the collision entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Prevent auth modal from being dismissed via ESC

Add UmbPersistentModalDialogElement that extends UUIModalDialogElement
and intercepts ESC keydown to prevent the native dialog cancel behavior.
The auth modal now always uses this element via type: 'custom', ensuring
users must complete authentication rather than dismissing the modal.

Also simplifies #showLoginModal by using umbOpenModal() and removing
the do/while reopen loop which is no longer needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: renames file and adds appropriate exports

* Auth: Use AbortController for listener cleanup and add cancel handler

Use AbortController to manage event listeners, preventing accumulation
if _openModal is called multiple times. Add cancel event handler
alongside keydown as a fallback for the native dialog cancel behavior.
Clean up listeners on forceClose.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lan Nguyen <lan@umbraco.dk>
2026-03-09 13:16:20 +00:00
5f5ac459d3 Auth: Fix multi-tab auth failures by removing appauth dependency (closes #20873, #21598, #21704, #22022) (#21830)
* Auth: Add minimal PKCE client to replace appauth library (closes #20873)

Introduces UmbAuthClient — a focused OAuth PKCE client that replaces the
forked @openid/appauth library. Uses Web Crypto API for code_challenge
generation and fetch() with credentials:'include' for cookie-based auth.
Zero localStorage usage — PKCE state held in memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Rewrite auth context with BroadcastChannel and Web Locks

Merges UmbAuthFlow into UmbAuthContext (single consumer, no export).
Replaces localStorage token storage with in-memory session state.

- BroadcastChannel('umb:auth') for cross-tab auth event coordination
- Web Locks API prevents concurrent refresh token race conditions
- postMessage for popup PKCE code_verifier exchange
- sessionStorage for redirect-flow PKCE state (tab-scoped)
- Adds configureClient() for extension developer DX
- Deprecates authorizationSignal (scheduled for removal in Umbraco 19)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Update session timeout controller and SharedWorker

Session timeout controller simplified to take only UmbAuthContext (no
separate authFlow parameter). Observes session$ for timing updates.

SharedWorker now accepts expiresAt timestamp instead of full
TokenResponse. Removes TokenResponse import and TOKEN_EXPIRY_MULTIPLIER.
Sends current session state to new tab connections. Cleans up stale
ports via try/catch on postMessage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Simplify OAuth completion flow and API interceptor

app.element.ts: Remove authorizationSignal wait pattern —
completeAuthorizationRequest() now handles everything. Remove
umbHttpClient.setConfig() call (moved to auth context constructor).

api-interceptor.controller.ts: Replace deprecated authorizationSignal
observer with isAuthorized transition for retrying 401 requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Deprecate external/openid package and storage constant

Delete all 17 appauth implementation files. Replace index.ts with
deprecated type-only stubs for backwards compatibility — external
consumers can still reference types through v18.

Mark UMB_STORAGE_TOKEN_RESPONSE_NAME as deprecated (scheduled for
removal in Umbraco 19).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Update auth context tests for new implementation

Rewrite tests to cover the new auth context API surface including
configureClient(), getOpenApiConfiguration(), URL generation, lifecycle
management, and bypass auth mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Update extension template to use configureClient() API

Replace manual getOpenApiConfiguration() pattern with the new
configureClient() method on UmbAuthContext — single line to configure
any @hey-api/openapi-ts client for authenticated Management API calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Fix token refresh not firing and adaptive worker timing

Two bugs fixed:

1. makeRefreshTokenRequest() checked expiresAt > now which always
   returned true when the worker fired proactively (before session
   expiry). Changed to compare session reference before/after acquiring
   the Web Lock — only skips if another tab actually refreshed.

2. getLatestToken() checked the full session expiresAt (with 4x
   multiplier) instead of the access token expiry. Split UmbAuthSession
   into accessTokenExpiresAt and expiresAt so each check uses the
   correct threshold.

Also made the worker's buffer and check interval adaptive for short
sessions (< 2 minutes) — buffer is reduced to 25% of session lifetime
and check interval scales proportionally. Fixes the long-standing issue
where very low timeouts caused the buffer to exceed the session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Propagate sign-out to all tabs via BroadcastChannel

When a user signs out in one tab, broadcast a 'signedOut' message so
other tabs redirect to the logout page. Previously, other tabs only
cleared their in-memory session but continued showing stale data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Route setInitialState through Web Lock to prevent duplicate refreshes

setInitialState() was calling refreshToken() directly, bypassing the
Web Lock. Concurrent API calls (via getLatestToken) also triggered
refresh through the lock. This caused duplicate /token calls — one
outside the lock, one inside — leading to rolling refresh token
invalidation races.

Now setInitialState() goes through makeRefreshTokenRequest() so all
refresh calls are serialized by the same Web Lock.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Close timeout modal when another tab refreshes the session

When the session$ observable emits a new session (e.g. from a
BroadcastChannel update after another tab refreshed), close any open
timeout modal. Previously the modal stayed open with its own countdown,
eventually triggering a spurious logout even though the session was
already extended.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Replace SharedWorker with setTimeout and leader-elected modal

Four improvements from a fresh design review:

1. Remove SharedWorker — replaced with a simple setTimeout in the
   timeout controller. A 15-60s timer is negligible on the main thread,
   and the focused tab's timer is never throttled by browsers.

2. Leader-elected timeout modal — uses Web Lock (ifAvailable) so only
   one tab shows the timeout modal. Non-leader tabs set a fallback
   timeout. When the leader tab resolves the modal, BroadcastChannel
   propagates the result and session$ observer closes stale modals.

3. Peer session request — new tabs ask existing tabs for their session
   via BroadcastChannel before attempting a server refresh. Avoids the
   400 error on fresh sessions and eliminates unnecessary /token calls
   for new tabs in an existing session.

4. Single expiry concept — no more refreshToken vs logout distinction
   from the worker. The controller checks remaining time and decides
   based on keepUserLoggedIn and whether time has fully expired.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Fix timeout modal not showing during buffer zone

The #onSessionExpiring guard used isSessionValid() which returns true
during the warning buffer (before full expiry), preventing the modal
from ever appearing. Replace with expiresAt comparison that only skips
if the session was actually refreshed since the check was scheduled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Set auth header at module level to eliminate timing gap

Move `auth: () => '[redacted]'` into the http-client module-level
config so it's available from first import. Previously, extensions
importing umbHttpClient before UmbAuthContext initialized would send
cookies but not the Authorization header needed by
HideBackOfficeTokensHandler, causing 401s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Bind default interceptors via configureClient()

configureClient() now creates an UmbApiInterceptorController and binds
the default response interceptors (401 retry, error handling,
notifications) alongside auth config. app.element.ts uses this for
umbHttpClient, giving extensions the same middleware pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Fix review findings — stale state, double-broadcast, PKCE cleanup

- clearTokenStorage: also set isAuthorized=false on originating tab
- signOut: inline state clearing to avoid double-broadcasting
  sessionCleared + signedOut; fix dead URL base arg; use
  window.location.origin consistently
- makeRefreshTokenRequest: compare accessTokenExpiresAt values instead
  of object identity for robustness
- completeAuthorizationRequest: only remove sessionStorage PKCE entry
  when state matches (preserve valid entry on mismatch)
- umb-auth-client: warn when expires_in is missing or zero
- configureClient: guard against duplicate calls with WeakSet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(auth): resolve lint errors and Copilot review issues

- Add eslint-disable blocks around OAuth wire-format URLSearchParams keys
  (client_id, redirect_uri, grant_type, etc.) — these must use snake_case
  per RFC 6749/7636 and cannot be renamed
- Fix optional chaining gap in #openTimeoutModal: store modal ref before
  awaiting so modal?.onSubmit() is safe when modalManager is undefined
- Fix popup Promise never settling: poll for authWindowProxy.closed and
  resolve (cleanup) when the user closes or cancels the login popup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auth: Deprecate getLatestToken() — always returns '[redacted]' with cookie auth

With cookie-based auth, getLatestToken() always returns '[redacted]'.
The proactive token refresh it performed is no longer needed since:
- The session timeout controller refreshes proactively via setTimeout
- The API interceptor retries 401s automatically

Internal callers (linkLogin, unlinkLogin, server-event, tryXhrRequest)
now use '[redacted]' directly. getOpenApiConfiguration() is kept as the
recommended API for manual fetch calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: adds links to deprecations

* docs: adds deprecation notices

* Auth: Deprecate getLatestToken(), clarify openid stub behavior

- Mark getLatestToken() as deprecated (always returns '[redacted]' with
  cookie auth). Points to configureClient() and getOpenApiConfiguration().
- Inline '[redacted]' in internal callers (linkLogin, unlinkLogin,
  server-event, tryXhrRequest) instead of going through getLatestToken().
- Update getOpenApiConfiguration().token to return '[redacted]' directly.
- Clarify external/openid deprecation header: data classes remain
  functional, handler classes reject because the operations are no
  longer possible with cookie-based auth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: overrides options after applying defaults

* Auth: Supply keepUserLoggedIn from backend via HTML attribute

Instead of fetching keepUserLoggedIn asynchronously from the Management
API after authorization, the server now renders it as a boolean attribute
on <umb-app> from SecuritySettings. This eliminates the timing gap where
the access token could expire before the async preference was fetched,
causing 401s on API calls.

Chain: Index.cshtml → <umb-app keep-user-logged-in> → UmbAuthContext →
UmbAuthSessionTimeoutController. When true, the timeout controller
schedules based on accessTokenExpiresAt (proactive refresh) instead of
expiresAt (full session expiry).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Fix review findings — message storm, PKCE state, spread order

- Fix BroadcastChannel message storm: completeAuthorizationRequest
  was calling #updateSession (which broadcasts sessionUpdate) AND
  separately broadcasting 'authorized'. Other tabs receiving 'authorized'
  called #updateSession again, cascading N² messages. Split into
  #setSessionLocally (no broadcast) and #updateSession (broadcasts).

- Increase PKCE state from 10 to 32 characters for stronger CSRF nonce
  (was ~59 bits, now ~190 bits of entropy).

- Fix tryXhrRequest spread order: ...options was last, allowing callers
  to accidentally override baseUrl/token. Now baseUrl/token come last.

- Remove unused endSessionEndpoint from UmbAuthClientEndpoints interface
  (signOut URL is constructed directly in auth.context.ts).

- Export UmbAuthSession interface for extension developers observing
  session$.

- Add clarifying comments for: anonymous UmbApiInterceptorController in
  configureClient, refresh_token server contract, Web Lock deduplication
  edge case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Fix review findings — redirect loop, popup leak, navigator.locks fallback

- Fix redirect loop after code exchange by using force=true navigation
  so setInitialState() runs with fresh httpOnly cookies
- Clean up pending popup flows before starting new ones (prevents
  pkceHandler/closedPoll leaks)
- Add navigator.locks fallback for environments without Web Locks
- Clear session on timeOut() to prevent stale in-memory state
- Make AuthorizationError constructor params optional (compat fix)
- Remove dead #previousAuthUrl field
- Add clarifying comments on configureClient and peer session timeout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Wait for both auth and server contexts before initializing SignalR hub

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Use ifAvailable lock to prevent redundant token refresh across tabs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Add default Authorization header to umbHttpClient

The hey-api `auth` callback is only invoked when requests include
`security` metadata (which generated SDK functions do automatically).
Direct `.get()`/`.post()` calls lack this metadata, so the
Authorization header was silently omitted. Adding it as a default
header ensures all requests through umbHttpClient trigger the
server-side HideBackOfficeTokensHandler cookie swap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auth: Use exclusive lock with freshness check for token refresh

Replaces ifAvailable lock with an exclusive lock that queues tabs.
After acquiring the lock, isSessionValid() checks whether another tab
already refreshed — preventing sequential /token calls when timers
fire slightly offset.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(web): configure umbHttpClient baseUrl before server connection

Move auth context creation and configureClient() before
UmbServerConnection.connect() so that the generated SDK calls
(ServerService.getServerStatus/getServerConfiguration) have a
valid baseUrl on umbHttpClient.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(auth): use object reference comparison in token refresh lock

The isSessionValid() check inside the Web Lock used expiresAt (full
session lifetime), which incorrectly skipped proactive refreshes when
keepUserLoggedIn=true. The timeout controller fires based on
accessTokenExpiresAt, but the full session was still valid at that
point, so the refresh was silently skipped — eventually causing 401s.

Fix: capture the session object reference before entering the lock
queue. Inside the lock, compare references to detect whether another
tab broadcast a sessionUpdate while we were waiting. This correctly
deduplicates multi-tab refreshes while allowing proactive refreshes
to proceed.

Also fixes prettier formatting in UmbAuthClient constructor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: do not assume that any endpoint is authenticated or accepts an Authorization header (this should come from the OpenAPI spec)

* E2E: QA: updated acceptance tests to match the authorization changes in  #21830 (#22021)

Updated tests to the updated auth

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2026-03-09 12:17:56 +00:00
c48c594e51 Redirect Tracking: Fix segment duplication when domain has a path segment (closes #21763) (#21772)
* Increase precision available to decimal data types.

* Fix redirect URL segment duplication when domain has a path segment.

* Revert accidental commit.

* Fix multiple enumeration

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2026-03-09 10:19:21 +00:00
Jacob Overgaard 74c76be952 internal: removes accidentally committed claude settings file 2026-03-09 11:06:22 +01:00
Andy ButlandandGitHub 10ba3519ec Management API: Add server-side validation preventing element types from varying by segment (closes #21643) (#21728)
Validate at management API to prevent create or update of an element type that varies by segment.
2026-03-09 10:47:48 +01:00
Niels LyngsøandGitHub 1958dfe3d2 Content: Only validate selected Cultures (#21361)
* correct comments

* poc

* refactor validation of variants

* refactor to enable parsing alternative validation methods

* only validate selected variants

* validateVariantsAndSubmit method

* refactor for better diff view

* refactor for better diff view

* remove empty comment

* minor refactor

* ensure segment-variants are included when validating

* turn into arrow method

* clean up

* adjust types
2026-03-09 10:23:43 +01:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>iOvergaard
d29b7d26b8 Docs: Reference CLAUDE.md from copilot-instructions instead of duplicating content (#22032)
* Initial plan

* Docs: Update .github/copilot-instructions.md to match CLAUDE.md content

Co-authored-by: iOvergaard <752371+iOvergaard@users.noreply.github.com>

* Docs: Reference CLAUDE.md from copilot-instructions instead of duplicating content

Co-authored-by: iOvergaard <752371+iOvergaard@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: iOvergaard <752371+iOvergaard@users.noreply.github.com>
2026-03-09 10:00:19 +01:00
c6c8254386 fix: combine external-supplied pickableFilter with internal filter in picker input contexts (closes #21859) (#21989)
* fix: compose user-supplied pickableFilter with internal filter in picker input contexts (#21859)

The openPicker method in UmbDocumentPickerInputContext, UmbMediaPickerInputContext,
and UmbMemberPickerInputContext unconditionally overwrites the user-supplied
pickableFilter with the internal implementation. This prevents package developers
from providing custom filtering logic (e.g., filtering out unpublished items).

The fix composes both filters using a logical AND: the internal filter runs first
(access checks, allowedContentTypes), and if it passes, the user-supplied filter
is also evaluated. This preserves the existing behavior while enabling extensibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract _composePickableFilters into base UmbPickerInputContext class

Move duplicated filter composition logic from document, media, and member
picker input contexts into a shared protected method on the parent class.
This reduces cyclomatic complexity in each openPicker override and
eliminates code duplication across the three picker contexts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Rename picker filter helper to _combinePickableFilters

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-03-09 09:55:13 +01:00
e8a521eaa0 Backoffice: Export block-single package for external consumers (closes #22044) (#22045)
Export block-single client-side package.

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-03-09 08:23:02 +00:00
Andy ButlandandGitHub 65ad90e248 URL Picker: Fix validation error persisting after link selection (closes #21903, #21454) (#22034)
Fix validation of the multi-URL picker.
2026-03-09 07:50:06 +00:00
Andy ButlandandGitHub 9ceb317003 Media Picker: Show friendly inline validation error when uploading a file with required media type properties (closes #20295) (#22025)
* Align validation failed on image upload messaging with that used for not allowed.

* Trigger build

* Use destructuring.
2026-03-09 08:30:07 +01:00
Niels LyngsøandGitHub 8f42dfe1ee Sorter: Detecting outside drops when browser does not get events (#21664)
Sorter detecting outside drops when browser does not get events from the outside
2026-03-09 08:29:17 +01:00
Johannes LantzandGitHub ba688d4e4c Localization: Add for language picker modal (#22043)
* Added localize.term for umb-language-picker-modal

* Added missing Japanese translation keys for umb-language-picker-modal
2026-03-09 06:41:02 +01:00
Johannes LantzandGitHub cb88588600 Localization: Export dictionary modal (#22038)
* Added localization for Export dictionary modal with Japanese translations

* Replaced unnecessary export key with actions_export
2026-03-07 08:39:01 +01:00
Johannes LantzandGitHub d2b8d02eae Add localize for restore entity action (#22040) 2026-03-06 19:04:03 +01:00
79ddf45e23 Data Types: Fix collection view references not showing in data type usages (closes #21649) (#21655)
* Fix FindListViewUsages to match ListView key instead of naming convention

* Align GuidUdi creation with FindUsages by adding .EnsureClosed()

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-06 11:52:12 +00:00
Nhu DinhandGitHub 32270e7548 E2E: QA Added acceptance tests for allowing folder selection in media entity picker (#21981)
* Added api helper for creating tiptap data type with media folder

* Added ui helper for remove image upload folder

* Updated ui helper for selecting media with name

* Added tests for selecting media link in multi url picker

* Added tests for media picker start node

* Added tests for image upload folder in tiptap data type

* Updated tests for user media start nodes

* Updated tests for user group media start nodes

* Make tests run in the pipeline

* Fixed comment

* Cleaned code

* Added tests for add multiple media start nodes to a user

* Reverted npm command
2026-03-06 10:35:20 +00:00
Jacob OvergaardandGitHub c9b4e1a141 build(deps): bumps @umbraco-ui/uui from 1.17.0 to 1.17.1 (#22029) 2026-03-06 10:08:06 +00:00
b1b5ce45c8 Backoffice: Add CSP nonce support for inline scripts (closes #21575) (#21581)
* Add CSP nonce support for inline scripts

* Add UseUmbracoCspNonceInjection middleware for NWebsec integration.

* Add unit tests for InjectNonceIntoDirective method.

* Add documented CSP rules to local website so any issues that conflict with these rules are surfaced in local development and testing.

* Test formatting.

* Addressed code review feedback.

* Use tag helper for nonce rendering.

* Apply suggestions from code review

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

* Move CspNonceInjectionOptions into it's own file.

* Reduce clutter in Program.cs in local web project, by moving use of documented CSP to an extension method.

* Trigger build

* Exclude CSP from template but keep in local project.

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2026-03-06 11:03:00 +01:00
Andy ButlandandGitHub b32c944299 Dependencies: Update server-side dependencies to latest patch or minor releases (#21860)
* Update server-side dependencies to latest patch or minor releases.

* Revert and comment upgrade to MailKit.

* Update Microsoft.NET.Test.Sdk to latest minor.
2026-03-06 15:55:00 +09:00
0ead90a7e1 Collection Views: Add sortable value column for custom property sorting (closes #21425) (#21479)
* Migration, model and repository data access for sorting via a sortable field.
Property editor sortable interface and implementation of JSON stored date fields.

* Add migration to populate sortable field for existing date property data.

* Added unit tests for GetSortableValue on datetime property editors.

* Fixed issues raised in code review.

* Re-use code in base from DocumentRepository to avoid additional call to SetEntitySortableValues.

* Move migration to 17.3.

* Fix merge issue.

* Move around migrations so they are in correct order

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Zeegaan <skrivdetud@gmail.com>
2026-03-06 07:40:27 +01:00
fd905e334e Content picker: Fix dynamic root not firing when inside block list (closes #22008) (#22011)
* Check whether picker is in a block. If so, act as with a new content node.

* re-use isNew flag to not increase complexity for the requestRoot function

* remove random whitespace added by visual studio

* remove ternary to reduce complexity

* move check to backend

* update fallback in SiteDynamicRootOriginFinder as well

* Revert "update fallback in SiteDynamicRootOriginFinder as well"

This reverts commit 0a14aa7393.

* Revert "move check to backend"

This reverts commit ca8b0c06da.

* get content workspace context - analogous to document-block-property-value-user-permission.workspace-context.ts. import interface for getIsNew().

* Use getContext.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-05 15:53:50 +00:00
dd12555e53 Configuration: Make MainDom acquisition timeout configurable (#22013)
* Make the hardcoded time for MainDom acquisition configurable.

* Fixed grammar in comment

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sven Geusens <sge@umbraco.dk>
2026-03-05 15:51:55 +01:00
96846799ba Audit Log: Abstracted History Info App into reusable auditLog kind (for documents & media) (#21898)
* feat(content): add shared types and repository interface for audit log kind

Introduces UmbAuditLogTagData types, ManifestWorkspaceInfoAppAuditLogKind manifest
interface, and UmbAuditLogHistoryRepository extending the core audit log repository
with getTagStyleAndText() method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(content): create shared audit log workspace info app element

Reusable element that receives manifest config with auditLogRepositoryAlias
and optional allowedActions. Uses UMB_ENTITY_WORKSPACE_CONTEXT for entity
unique resolution and createExtensionApiByAlias for repository lookup.
Includes reload event listener, pagination, and user avatar caching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(content): add auditLog kind definition and manifest registration

Registers the 'auditLog' kind for 'workspaceInfoApp' extension type,
mapping to the shared element. Includes info-app and audit-log manifest
aggregators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(documents,media): register audit log repos as extensions and add getTagStyleAndText

- Register UmbDocumentAuditLogRepository and UmbMediaAuditLogRepository as
  extension manifests with type 'repository' and dedicated alias constants
- Add getTagStyleAndText() method to both repositories implementing the
  UmbAuditLogHistoryRepository interface from content package
- Export audit-log types from @umbraco-cms/backoffice/content
- Deprecate getDocumentHistoryTagStyleAndText and getMediaHistoryTagStyleAndText
  utility functions (scheduled for removal in Umbraco 19)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(documents,media): switch audit log info apps to use shared auditLog kind

- Update document and media info-app manifests to use kind: 'auditLog'
  with meta configuration (auditLogRepositoryAlias, allowedActions)
- Include repository manifests in document and media audit-log aggregators
- Wire audit-log kind manifests into the content package
- Deprecate UmbDocumentHistoryWorkspaceInfoAppElement and
  UmbMediaHistoryWorkspaceInfoAppElement (scheduled for removal in Umbraco 19)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(documents,media): add `api` exports to audit log repositories

Required for the extension registry API loader pattern which expects
either a default or named 'api' export from the module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Linting

* refactor(content): extract renderHistoryItem to reduce cyclomatic complexity

Splits the repeat callback out of #renderHistory into a dedicated
#renderHistoryItem method, reducing the method's cyclomatic complexity
below the threshold of 9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(content): throw error when workspace entity unique is missing

Restores fail-fast behavior for missing entity unique in audit log
requests, matching the original document/media implementations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(audit-log): move getTagStyleAndText to UmbAuditLogRepository as optional method

Removes UmbAuditLogHistoryRepository interface and adds optional
getTagStyleAndText() to UmbAuditLogRepository in core. Moves tag
types to core/audit-log and adds a default type parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit-log): export repository alias constants from package entry points

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Linting and tidy-up

* Fixes canceled Rollback modal error

* Removed the `allowedActions` property

* feat(content): formalize `auditLogAction` extension type

Add proper TypeScript interfaces, default kind, and dedicated element
for the `auditLogAction` extension type, replacing the previous
untyped usage that relied on `ManifestEntityAction`.

- Define `ManifestAuditLogAction` and `MetaAuditLogAction` interfaces
- Create `umb-audit-log-action` element using `uui-button` (suited for
  the audit log info-app header, unlike `uui-menu-item`)
- Register default and contentRollback kind manifests
- Move contentRollback audit-log-action kind to the content module
- Separate document-specific audit-log-action manifest into its own file

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-05 12:44:40 +00:00
Andy ButlandandGitHub 59bbcaa129 Memory Management: Dispose IDisposable resources correctly in four internal classes (#22014)
* Dispose event listener created in InMemoryAssemblyLoadContextManager.

* Use using to dispose ICryptoTransform in MemberPasswordHasher.

* Dispose CancellationTokenSource in DatabaseServerMessenger.

* Dispose deserialized JsonDocument in CacheInstructionService.

* Use try/finally to ensure dispose.
2026-03-05 11:44:44 +01:00
Niels Lyngsø 2e75da2df9 Chore: decrease threshold to 15 bidirectional imports 2026-03-05 11:07:12 +01:00
Niels Lyngsø ee68fb393c fix unit test types 2026-03-05 11:04:17 +01:00
Niels LyngsøandGitHub 71ea6f4a93 Breadcrumb variant-name logic improvment (#21617)
* Adjust name logic to adapt to current-culture and not display invariant name as inherited

* refactor
2026-03-05 09:28:15 +00:00
337139f7b2 Data Access: Modifies entity repository sibling queries to support custom database p[oviders (closes #21852) (#21671)
* quote table, column and alias names with SqlSyntaxProvider methods in raw sql

* refactoring private methods into new file as internal methods,
refactor new extensions into another file

* refactor GetAlias method

* Double check the change

* improve code health

* change new static classes into public static partial class NPocoSqlExtensions

* resolve some Copilot review suggestions

* revert Copilot suggestion because it decreases code health

* revert test

* compare in with LOWER, change two methods from private to protected in UmbracoDatabaseFactory

* revert Query.cs in this PR

* Refactor for code health and fixing raw sql

* divers small issues fixed

* refactor two methods to respect the DRY pricipal

* update IQuery interface

* clean up

* revert refactoring for CodeScene

* delete obsolete Test

* rename method

* remove new methods and updates, which are not relevat for this PR

* prepare for additional states in the future

* don't mix string building methods

* fix SQL injection danger

* fix test for reverted methods

* another SqlSyntax issue

* fix update

* fix reverted changes

* restore change for this PR

* restore change for this PR

* fix merge bug

* update formating

* extend ISqlSytax for database independent autoIkrement feature

* fix DTOs, extend ISqlSyntax

* fix tests

* revert

* updates

* diverse SqlSyntax and NPoco related updates for custom databse providers

* fix names

* squash merge v173/20453-DTO-attributes-fixed into v173/20453-final-sql-syntax-fixes

* merge

* add default implementation to interface

* fix tests

* fix PrimaryKey for multi columns

* test fix

* Resolve the issues with SqlSyntaxProvider for SQLite. If executed correctly, a single test would reveal the problem.

* add another test

* revert changes which causes even more issues

* fix SQL syntax

* fix column const naming

* ensure column const names from v17.2

* add comment for change

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeTemplateDto.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* resolve review comments

* Make ReferenceMemberName consistent across all DTOs (use constants defined on the referenced DTO).

* Ensure [ExplicitColumns] attribute exists on all DTOs.

* Ensure we consistently use PrimaryKeyColumnName over PrimaryKeyName.

* Fix further inconsistency to use only TemplateNodeIdColumnName.

* Fixed trailing whitespace.

* Restored primary key constraint name on ContentVersionCleanupPolicyDto (it doesn't seem in scope of PR to remove this).

* Removed the confusing PrimaryKeyColumnName constants for multi-column primary key DTOs where the constant refers to only one of the key columns.

* ReferenceMemberName needs to be a C# property name, so it's safer to use nameof.

* Comment fix.

* Amended accessibility modifiers.

* revert unnecessary changes

* revert unnecessary changes

* revert unnecessary changes

* fix SQLite escape variants

* fix typo

* simple (typo) fixes of Copilot review comments

* solve another Copilot review comment

* improve comments and minimise changes

* add an detailed change comment

* resolve review and revert all integration test. Tests changes will be done in the PostgreSqlProvider-npocp branch like some unit tests.

* remove InsertWithSpecialAutoIncrement()

* update WhereIn() for case sensitive databases

* fix special char in test comment

* throw exception for invalid values

* remove values type check

* add extra check

* resolve review comments

* revert more changes with question

* refine method SiblingsSql of EntityRepository, add another AndSelect() method overload to NPocoSqlExtensions.

* resolve review

* fix replacement

* trigger new pipeline build

* trigger new pipeline build

* Added comment explaining why withAlias: false is needed.

* Add additional tests around sibling retrieval.

* Add tests for the AndSelect overloads.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-05 09:21:40 +01:00
Niels LyngsøandGitHub 684d08b631 Content Rollback: error handling (#22018) 2026-03-05 07:49:52 +00:00
Jacob Overgaard e1cc6926ab build: optimises azure static builds in order not to consume too many environments 2026-03-05 08:31:30 +01:00
f7bab4521b Content Rollback: Abstracted rollback into reusable contentRollback entity action and modal kinds (#21939)
* Content Rollback: Abstract document rollback into reusable entity action and modal kinds

Create shared `rollback` entity action kind and modal kind in the content package,
enabling reuse for upcoming entity types (e.g., Elements in v18). The document
rollback now uses these kinds via manifest meta, while old APIs are preserved
with @deprecated annotations for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Content Rollback: Address PR review feedback

- Add validation for manifest meta in rollback modal element, throwing
  descriptive errors if rollbackRepositoryAlias or detailRepositoryAlias
  are not configured
- Remove non-null assertions in favor of validated manifest access
- Fix deprecated requestVersionByDocumentId to delegate through the
  generic requestVersionById interface method
- Remove unused requestVersionByDocumentId deprecated method (original
  method was requestVersionById, not requestVersionByDocumentId)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Renamed "rollback" to "contentRollback"

for class names and manifest kind.

* Content Rollback: Move repo aliases to entity action meta; remove modal kind

Move rollbackRepositoryAlias and detailRepositoryAlias from the modal
kind manifest meta to the entity action meta, passing them as modal
data. Remove the contentRollback modal kind entirely and register the
modal element directly. Introduce UMB_CONTENT_ROLLBACK_MODAL token so
the entity action no longer needs a configurable rollbackModalAlias.
Deprecate document-level modal constants in favor of content-level ones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fixed linting errors

* eslint missed an export! 🤦

* refactor(backoffice): rename UMB_ENTITY_ACTION_ROLLBACK_KIND_MANIFEST to UMB_ENTITY_ACTION_CONTENT_ROLLBACK_KIND_MANIFEST

Address PR review feedback to include "Content" in the manifest constant name for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:09:56 +01:00
Andy ButlandandGitHub 31aa6e5847 Decimal Property Editor: Align step precision with database storage (closes #22003) (#22004)
Align decimal property editor precision with storage.
2026-03-04 13:33:35 +01:00
bd52e95a10 Management API: Add item ancestors endpoints returning item response models (#21874)
* Create item endpoints that return ancestor IDs for a given collection of entity IDs.

* Return item models instead of just IDs.

* Use async methods.

* Use NamedItemResponseModel for container ancestor endpoints.

* Simplify the usage of ItemAncestorService - use less assumptions about structure and use generic mapping for basic response models.

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-03-04 12:04:46 +01:00
8b9bfb3a65 URL and Alias Caches: Optimize for invariant documents (#21558)
* Optimize (memory usage, database storage, and processing time) document URL and alias cache for invariant documents.
Store invariant content with NULL languageId instead of duplicating records for each language.

* Additional integration tests verifying aspects of changed functionality.

* Implement and test that URLs and aliases are updated when a content type changes from variant to invariant or vice versa.

* Tidied up migration.

* Corrected file name.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Further updates from code review, resolved warnings.

* Use rebuild key defined in constant in migration.

* Handle possibility of custom URL providers generating different URL segments per culture.

* Resolve breaking change.

* Handling breaking change in DocumentUrlDto.

* Tidied up code comments

* Fix issue where URL aliases on variant content with a shared property were not being recorded.

* Tidy up comment.

* Fix breaking change in nullability.

* Fix breaking change in nullability (2).

* Revert "Fix breaking change in nullability (2)."

This reverts commit c77a37c855.

* Fix failing integration tests.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 11:55:09 +01:00
Nhu DinhandGitHub e89cc4961b E2E: QA Updated failing acceptance tests to match the UI changes (#22002)
* Updated ui helper for verify the file uploads

* Updated tests due to test helper changes

* Updated tests for block due to UI changes

* Added comment for the failing tests
2026-03-04 17:34:51 +07:00
Andreas ZerbstandGitHub 7f9f58f559 E2E: QA: added acceptance tests for preview (#21967)
* Added preview helper

* Added preview helpers

* Added preview tests

* Updates based on comments

* reinitialize the preview locators for the pop up preview page

* Cleaned up based on comments

* Update smokeTest command in package.json
2026-03-04 10:27:13 +00:00
Andreas Lykke BorgandGitHub c313e6f112 List view: Added labels entity bulk action buttons (#21964)
* Added labels to checkboxes and buttons and fixed checkbox alignments

* Reverted u200B as label in checkboxes
2026-03-04 08:47:42 +00:00
24a01df870 Content Picker: Pass preview flag to published content cache lookups (closes #21972) (#21975)
* Ensure content picker correctly handles preview state.

* Return null for unresolvable content picker values, preserve routing properties.

* Apply suggestions from code review

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2026-03-04 09:31:12 +01:00
8ae768d4eb Update badge icon (#21911)
* Change the badge icon svg content

* Updates "badge" icon with "id-card.svg"

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-03-04 04:15:46 +00:00
59fd3938a2 Content Types: Fix API response for cancelled delete operation (#21758)
* fix(core,api,web): fix backoffice UI errors on notification cancellation

- Fix ContentTypeServiceBase.DeleteAsync() to use PublishCancelableAsync
  directly instead of delegating to the sync Delete() method, which
  silently swallowed cancellation and always returned Success.
- Extract shared deletion logic into private PerformDelete() method
  to keep both Delete() and DeleteAsync() DRY.
- Mark ProblemDetails with notificationsDeliveredViaHeader extension
  when Umb-Notifications header carries event messages, preventing
  the frontend from showing duplicate error toasts.
- Add notificationsDeliveredViaHeader to UmbProblemDetails type and
  skip redundant ProblemDetails notification in try-execute controller.
- Add integration test for DeleteAsync cancellation detection.

* fix(core,api,web): fix backoffice UI errors on notification cancellation

- Fix ContentTypeServiceBase.DeleteAsync() to use PublishCancelableAsync
  directly instead of delegating to the sync Delete() method, which
  silently swallowed cancellation and always returned Success.
- Extract shared deletion logic into private PerformDelete() method
  to keep both Delete() and DeleteAsync() DRY.
- Mark ProblemDetails with notificationsDeliveredViaHeader extension
  when Umb-Notifications header carries event messages, preventing
  the frontend from showing duplicate error toasts.
- Add notificationsDeliveredViaHeader to UmbProblemDetails type and
  skip redundant ProblemDetails notification in try-execute controller.
- Add integration test for DeleteAsync cancellation detection.

fix: #12636

* fix(core): reduce PerformDelete arguments and trim LOC

Address CodeScene quality gate failures:
- Reduce PerformDelete from 5 to 4 parameters by resolving
  EventMessages internally via EventMessagesFactory.Get()
- Trim lines of code to stay within the 1000 LOC threshold

* refactor(core): extract obsolete container methods into partial class

Split ContentTypeServiceBase into two partial class files to address
CodeScene's "Lines of Code in a Single File" quality gate (1007 > 1000).

The #region Containers block was chosen for extraction because all its
methods are already marked [Obsolete] and scheduled for removal in
Umbraco 18, replaced by IContentTypeContainerService and
IMediaTypeContainerService. The region is fully self-contained with no
inbound calls from the rest of the class.

This is a compile-time only change — partial classes produce identical
IL output. No public API, behavior, or binary compatibility impact.

* Revert "refactor(core): extract obsolete container methods into partial class"

This reverts commit 6fd8fd23f6.

* Pass eventMessages from the caller into PerformDelete instead of being re-obtaining from the factory.

* Use try/finally in test to ensure clean-up.

* Restore removed comments.

* Revert client-side updates.

* Revert client-side updates (2).

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-03 17:41:05 +00:00
Andy ButlandandGitHub c8564f33e7 Health Checks: Add check for imaging HMAC secret key (#21991)
* Added healthcheck for Imaging:HMACSecretKey configuration setting.

* Added healthcheck for Imaging:HMACSecretKey configuration setting.

* Address code review feedback.

* Removes unnecessary test.

* Use service in healthcheck.
2026-03-03 17:17:15 +00:00
Mads RasmussenandGitHub d7d3e4a613 Backoffice Toast Notifications: prevent double toast notifications on cancelled server operation statuses (#21993)
Ignore server-notified operation statuses
2026-03-03 17:46:57 +01:00
ad6813ca4a Search field: Added aria-label and name to search input for accessibility (closes #21938) (#21962)
* Add aria-label and name to search input for accessibility

- Add aria-label attribute to search input using localized placeholder text
- Add name attribute ("search-input") to provide form field identification
- Fixes Google Console warning about missing id/name on form field
- Improves WCAG 3.3.2 compliance (Labels or Instructions)
- Improves WCAG 2.5.3 compliance (Form input identifiable names)

Closes #2193

* Reused localized label

* Tidy-up/linting

---------

Co-authored-by: Emma L Garland <1649855+emmagarland@users.noreply.github.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-03-03 16:46:12 +00:00
Andy ButlandandGitHub 317319cd9f Imaging Configuration: Auto-generate HMAC secret key for new installs (#21976)
* Auto-generate HMAC secret key for imaging on new installs.

* Address code review feedback.

* Log results of configuration operations.

* Refactored to use the Attempt pattern.
2026-03-03 16:21:54 +00:00
Andy ButlandandGitHub f58894eb8a Media Picker: Allow custom folder types when creating inline folders (closes #21850) (#21959)
* Allow custom folder types when creating from media picker.

* Adjust selector padding.

* Corrected call to await.

* Addressed feedback from code review.
2026-03-03 15:41:42 +00:00
Jacob Overgaard 57c4339dd1 build(login): syncs lockfile 2026-03-03 16:18:50 +01:00
Jacob Overgaard ad6606e048 build(login): syncs package files 2026-03-03 16:16:40 +01:00
059c25fb3e Media Workspace: Fix collection view showing root items after creating new folder (closes #21700) (#21753)
* Set entity unique before scaffold processing to fix collection view for new media folders.

* Instead of moving setUnique() earlier in the provider, fix the consumers to observe the unique observable rather than reading synchronously.

* Addressed code review point on context observation.

* implement satisfies type check

* minor refactor

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-03 13:47:39 +00:00
f954a3fe74 Templating: Prevent editing of templates and partial views in production runtime mode (closes #21564) (#21600)
* Prevent save of partial view file when using runtime production mode, and verify for partial views and templates with integration tests.

* Display warning when templates and partial views are not editable in the backoffice.

* Share styles.

* Validate at the partial view API whether updates are allowed based on production runtime mode.

* Add similar checks for templates, handling case where metadata updates are allowed.

* Add integration tests for verifying behaviour in production mode.

* Fix the breaking changes on the constructor of the service classes.

* Use IOptions (we don't need live updates for this setting).

* Addressed code review feedback.

* Add IsProductionMode private property on both updated services.

* Move create template check to validate method.

* Remove entity actions create/delete/rename for templates and partial views whilst running in production mode.

* Addressed code review feedback.

* include server in condition name

* move tag to bottom right corner of workspace

* introduce info modal

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-03-03 13:24:41 +00:00
Andy ButlandandGitHub 0340d8c37d Block editors: Make block editors read-only when document is trashed (closes #21973) (#21982)
* Make block editors read-only when document is trashed.

* Hide update button on block non-line workspace if document is read-only.
2026-03-03 13:06:15 +00:00
Andy ButlandandGitHub d224251098 Backoffice Search: Default global search to current section (closes #21621) (#21636)
* Default the global search to the current section.

* Refactor to use meta element to target a section alias.
2026-03-03 13:45:11 +01:00
Andreas ZerbstandGitHub 27d03c3632 E2E: QA: Added acceptance tests for dynamic roots (#21966)
* Added tests

* Updated tests

* Updated smokeTest script, will be reverted

* Fixes based on comments

* Fixes

* Reverted command
2026-03-03 12:40:38 +00:00
Andy ButlandandGitHub 49eba63172 Hybrid Cache: Resolve IsPublished() returning false in preview mode (closes #21983) (#21985)
Resolve IsPublished() returning false in preview mode for published content.
2026-03-03 12:58:43 +01:00
88d07d5c3f Content Type: Introduce Entity Content Type Entity Context (#21817)
* introduce entity content type entity context

* introduce for document, media, member workspaces and trees

* provide for Document card

* add conditions and reorganize

* Stabilize entity content-type condition tests

* Set media content-type context in item card

* rename example

* Remove entity-type condition and refs

* add example

* Refactor entity-action components to consume UMB_ENTITY_CONTEXT instead of requiring entityType/unique props.

* update stories

* Add UmbEntityContext to collection item elements

* add tests

* Add context boundary to entity collection items

* Add test for entity context boundary

* Update umb-entity-collection-item-element-base.element.test.ts

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update umb-entity-collection-item-element-base.element.ts

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update umb-entity-collection-item-element-base.element.test.ts

* Update umb-entity-collection-item-element-base.element.ts

* Remove entity props from umb-entity-actions-bundle

* Revert "Update umb-entity-collection-item-element-base.element.ts"

This reverts commit 9e4ef79150.

* Provide entity context on host and update tests

* Revert "Provide entity context on host and update tests"

This reverts commit f618a8216e.

* fix lint errors

* Test entity context boundary for collection items

* Update entity-content-type-unique.condition.test.ts

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 11:30:11 +00:00
8e662db487 Tiptap RTE: Table node-view refactor to fix popover menus (closes #20614) (#21696)
* Tiptap Table: fix popover positioning for row and column grips

Refactored the table extension to use a proper container structure
and separate popovers for row and column context menus.

Key changes:
- Added UmbTableView with block container, inner table container,
  widgets container, and overlay container structure
- Created TableHandlePlugin to manage grips and popovers centrally
- Changed from single shared popover to separate row and column
  popovers, fixing the issue where column menu always appeared
  at the first column position
- Updated CSS styles to support the new container structure
- Added proper cleanup when tables are removed from the editor

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Deprecates `UmbBubbleMenu` extension

No longer used internally.
There were issues with the popover and Tiptap editor state.

* Tiptap Table node-view refactor

* Exports `UmbTableView`

* Handles `mouseleave` event

* Adds readonly guard and dynamic grip offset for table handles

Prevents grips/popovers from appearing and dispatching transactions
when the editor is in readonly mode. Replaces hardcoded 16px container
offset with dynamic bounding rect computation to stay in sync with CSS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Uses TableMap for cell indices instead of DOM child indexes

Resolves cell row/column via ProseMirror position resolution and
TableMap.findCell, which correctly handles merged cells (colspan/rowspan)
instead of relying on DOM child indexes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-03 11:13:48 +00:00
6535a9e753 Tiptap RTE: Adds actionButton kind for toolbar extensions (closes #21682) (#21703)
* Improvement: Use `when` callback parameter in tiptap toolbar disabled button

Use the callback parameter from Lit's `when` directive instead of a
non-null assertion to access the icon value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Feature: Add `actionButton` kind for tiptap toolbar extensions

Create a new `actionButton` kind that uses the disabled button element,
replacing manual `element` overrides in the Unlink, Undo, and Redo
toolbar manifests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Improvement: Add dedicated element for `actionButton` tiptap toolbar kind

Addresses review feedback by creating a proper `umb-tiptap-toolbar-button-action`
element for the `actionButton` kind instead of reusing the `-disabled` element.

- Uses `api.isDisabled()` for the disabled state (not `!isActive`)
- Types manifest correctly via generic on base class
- Makes base `UmbTiptapToolbarButtonElement` generic so subclasses can
  specify their manifest kind
- Deprecates `umb-tiptap-toolbar-button-disabled` (scheduled for removal in v19)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Improvement: Add base API class for `actionButton` toolbar extensions

Introduces UmbTiptapToolbarActionButtonApiBase with a default isDisabled
implementation that returns !isActive(editor), so third-party extensions
get meaningful disabled state without needing to override isDisabled.

Updates undo, redo, and unlink APIs to use the new base class, removing
their redundant isDisabled overrides. Adds a comment explaining the
implicit re-render dependency in the action button element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:00:27 +00:00
005241ee4a Upload Field: Show filename after file upload (closes #21587) (#21887)
* fix(media): display filename in upload field preview

Add visible filename text to the file and image upload field preview
components. Previously, the file preview only showed an icon and the
image preview only used the filename as invisible alt text.

The filename is extracted from the File object when available (blob
URLs during upload), falling back to the last path segment for
persisted server paths.

Closes #21587

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(media): display filename in audio, video, and SVG upload previews

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(media): move filename display to parent upload field with file-info bar

Move filename rendering from 5 individual preview components into the
parent input-upload-field element. The filename and remove action now
share a bordered bar below the preview. Filename is plain text during
upload (blob URL) and a clickable link to the file when saved.

Reverts preview components to their original state (preview only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style updates

* link style adjustment

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-03 10:19:02 +00:00
Andy ButlandandGitHub fcbedf2d6f Service registration: Allow running Umbraco with different combinations of backoffice, website and delivery API (closes #21622) (#21630)
* Add support for running website without backoffice.

* Add support for running delivery API without website or backoffice.

* Reverted unncessary idempotent checks on individual builder extensions.

* Integration tests for service registrations.

* Integration HTTP tests for service registrations.

* Tidy up and code review feedback.

* Remove unnecessary null check.

* Ensure models builder references are added to attempt to resolve the deliver API setup.
2026-03-03 09:49:21 +01:00
43f91ef47e Account logout: Handle revocation request for cookie-stored back-office tokens (closes #21918) (#21944)
* Handle revocation request for cookie-stored back-office tokens

* Addressed feedback from code review.

* Use OpenIddict constant instead of hardcoded string

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-03-03 06:37:07 +01:00
d2a6bd0a40 Media Picker: Allow folder selection in media entity picker (closes #21885) (#21895)
* Allow folder selection in media entity picker

* Also handle user and user group media root node picker.

* Allow file selection for users and user groups, fixing failing E2E test.

* Changed media start nodes for user/user group to select folders only

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-03-03 05:12:23 +00:00
Andy ButlandandGitHub 0e6a04c4fc Public Access: Handle inherited protection gracefully in modal dialog (closes #21965) (#21971)
Handle inherited public access gracefully in modal dialog.
2026-03-02 21:08:52 +00:00
Andy ButlandandGitHub 6a00d61337 Media Dropzone: Clarify error messages when file upload is not allowed (closes #21506) (#21708)
* Improve messaging on failed file uploads.

* Introduced utility for getting the extension from a file and addressed other code review comments.
2026-03-02 18:35:51 +00:00
1d3216e08c Members: Enable sorting on member table and order member groups by name (closes #21960) (#21963)
* Add support for member sorting by member type.

* Make the backoffice member table sortable by the supported fields.

* Sort member groups by name.

* Fixed linting issue.

* Use UmbDirection for sort direction

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-03-01 21:59:39 +01:00
eaed6cb0c9 Accessibility: Added title attribute for icon in content types (#21956)
* Fix missing <title> attribute for Icons in document types in backoffice

* Move the title attribute to uui-button from umb-icon.

* Removed color option from lable and title. Added prefix "Change icon:" in the title. Prefix managed from the localization.

* Improve icon tooltip accessibility and i18n in content type header

  - Add defensive check in #iconTitle to avoid "undefined" text when icon is unset
  - Move colon separator from translation strings to component template
  - Use consistent label for both title and aria-label on icon button
  - Add Spanish and Italian translations for changeIcon key

* Fix failing test by using an exact match for a label.

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-27 18:51:27 +00:00
bbab9f8006 Move/Duplicate: Filter tree picker based on allowed parent rules. (#21646)
* Document Types returns a list of allowed parent keys

* Media types included

* Add selectable filter for duplicate action based on allowed parents.

* Add optional selectable filter provider support to move action.

* Add selectable filter provider for move action in documents.

* Add selectable filter provider for move action on media.

* Optimize filter providers by using allowedAsRoot property directly.

* Refactor document move action to use repository for selectable filter.

* Move media filter logic from provider to repository .

* Centralize allowed-parent logic in data sources.

* Remove document item lookup from duplicate action.

* Simplify custom filter assignment in move action.

* Remove unused import.

* Rename getSelectableFilter method in document duplicate action..

* Refactor move to action for documents.

* Refactor of media move to action.

* Refactor duplicate action and remove unused imports.

* Clean up.

* Use .js extension for media tree type import

* Export move action and fix imports.

* add interfaces for type safety

* local implementations

* make linter happy

* make linter happy

* Filter out current node in MoveTo action

* Return error instead of throwing on fetch

* Use typed getters for structure data sources

* remove unused

* Add UmbTreeItemModel typing to move-to actions

* align paramater naming

* Defer move repository lookup until after modal

* Fetch type data concurrently with Promise.all

---------

Co-authored-by: NillasKA <kramernicklas@gmail.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-02-27 15:36:09 +00:00
Sven GeusensandGitHub 8340ff015e Integration Tests: Fix null reference errors (#21953)
* Dont blow up GetTestOptions when inside testfixtures

* Dont blow up Reference resolving when working with proxies

* Improve GetAssemblyFolders nullability

* More verbosity
2026-02-27 16:27:08 +01:00
82f805abca Management API: Add document and data types tree search endpoints (#21628)
* Messy implementation of documenttypes and datatypes

* Formatting and move service injection to constructor

* cleanup and bubble up new constructor

* Allow folder or item only searches

* feedback pr & subsequent refactoring

* Apply review suggestions

* Update openapi file

* Used constant, resolved minor layout warnings.

* Fix parent key lookup in tree search to check both folders and items.

* Remove TreeItemKind.None from flags enum.

* Add TODOs for removing the default implementation on the interfaces.

* Add permission integration tests.

* Update OpenApi.json.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-27 14:44:23 +00:00
Andy ButlandandGitHub 53615e3d86 Repeatable Textstring: Skip empty strings in validation and persistence (closes #21912) (#21915)
* Skip empty strings in repeatable textstring validation and persistence.

* Override RequiredValidator for repeatable textstring to treat all-empty arrays as no value.
2026-02-27 15:18:23 +01:00
Nathaniel NunesandGitHub db1cf01342 Accessibility: Add tooltips to block grid entry actions (#21958)
#20422 - Add tooltips to block grid entry actions for improved accessibility
2026-02-27 14:21:37 +01:00
Niels LyngsøandGitHub a7164d56f2 Slider Preset: make it easier to read the code (#21955)
refactor to make easier to read
2026-02-27 13:06:18 +01:00
Nhu DinhandGitHub 32d7f528f1 E2E: QA Updated AllowEditInvariantFromNonDefaultIsTrue tests to match the UI changes (#21950)
* Updated ui helper for add block list button

* Make AllowEditInvariantFromNonDefaultIsTrue tests run in the pipeline

* Removed .skip since the issue is resolved

* Fixed tests for submit an empty URL in RTE property

* Make TiptapToolbar tests run in the pipeline

* Reverted npm command
2026-02-27 08:44:24 +00:00
Mads RasmussenandGitHub 6f2cd9ec8e Backoffice Performance: Add inflight request deduplication to item data request managers (#21767)
* add inflight request cache to all item request managers

* Use inflight request cache for item data

* Add tests for Item Data Request Manager
2026-02-27 08:38:50 +01:00
f3adc14a72 Performance: Optimize handling of content type updates (#21910)
* Claude's suggestions

* Rewrite for tags based hybrid cache eviction and optimize the converted, in-memory cache eviction

* Replicate cache invalidation/flushing optimizations for the media cache service

* Do not perform Examine re-indexing for "other" changes on content types

* Clean up TODOs

* Use configured batch size for indexing, and use cached structure for checking publish status

* Default implementations of new interface methods to prevent breaking changes

* Clean out more TODOs

* Refactor logic to extension methods

* Add missing notification handlers to cache tests

* Add additional test coverage.

* Remove OnChange from settings for transient notification handler.

* Adds a migration to clear the hybrid cache to ensure all items are tagged by content type.

* Clear all converted content on type change in auto models builder mode.

* Apply the same fix for data type updates.

* Apply the same fix for data type updates (2).

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-27 08:29:51 +01:00
Lars-Erik AabechandGitHub b0756cb626 Integration tests: Re-virtualized CustomMvcSetup (#21947)
Re-virtualized CustomMvcSetup

Someone finalized my beautiful "last-hook-in-setup-for-mvc-things". 🥹
2026-02-27 08:26:27 +01:00
5958198f0d Fix: Increase size of modal listing property editors (#21825)
* Changed the modal size to medium data type picker modals.

* Updated the icon and label alignment so that icon always align vertically top and label in each starts from same position.

* Linting

* Adds `justify-items: center` for "Create new" button icon

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-27 05:08:21 +00:00
39170fbf66 Entity Signs: Enable entity signs for media items (closes #21786) (#21832)
* Add support for entity signs on media items.

* Code linting tweaks

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-26 22:51:48 +00:00
1719e5d07f Member Group Picker: Add server-side paging to public access modal (closes #21790) (#21834)
* Add pagination to the member group picker.

* Linting

...and use of `when` directive ;-)

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-26 21:42:39 +00:00
7a07c1160f User History: Improve recent history display with better labels and de-duplication (#21656)
* Improve recent history display with better labels and de-duplication.

* Addressed code review comments.

* Handle race condition where wrong item would get updated.

* Uses Lit `repeat` directive

* Reverted breaking-changes

added deprecation comments (for removal in v19)

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-26 19:58:36 +00:00
Andreas ZerbstandGitHub 35fb0a74d7 E2E: Move test helpers and builders into the acceptance test project and publish as @umbraco/acceptance-test-helpers (#21773)
* Move testhelpers and builder into the acceptance test project

* Updated imports in tests

* Updated readme

* Updated postinstall to exclude setting up config

* added a cleanup when npm packing

* update tsconfig path mapping to @umbraco/acceptance-test-helpers

* Added dist to git ignore

* Adds separate README files for npm and GitHub
 README.md: contributor-focused (test docs)
 README.npm.md: consumer-focused (package docs)
 cleanse-pkg.js swaps them during npm pack

* Updated to swap READMEs on npm pack. So the consumer README is the one being released

* Add npm publish pipeline for @umbraco/acceptance-test-helpers

* Configure package.json for npm publishing as @umbraco/acceptance-test-helpers

* Updated missing imports

* Cherrypicked helper changes

* Updated tests

* Updated name of builder

* added tslib

* Fixed test

* Renamed

* Add nbgv version step for test helpers npm package

* Fixes based on comments

* More fixes

* Removed unnecessary imports

* Fix naming of storage_state_path

* Added recommend for storage state

* Create console file if not present
2026-02-26 17:58:42 +01:00
950b5861c7 Block List: consistent spacing between blocks (#21750)
* simpler and more consistent css for block list and block single

* adjust spacing only for default views

* adjust inline and support for Block Grid

* simplify gap css for Grid Entries

---------

Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-26 16:48:15 +00:00
1d9d44a470 Create new folder on enter in media picker (#20648)
* Create new folder on enter in media picker

* Move CSS properties and change value for placeholder.

* Add localization key for labels and placeholder.

---------

Co-authored-by: Emma L Garland <emmagarland77@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: engjlr <enl@umbraco.dk>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-26 14:19:11 +00:00
0780c22002 Templates: Add optional Central Package Management support to UmbracoProject and UmbracoExtension templates (#21641)
* Adding CPM into Umbraco Project

* Adding CPM for UmbracoExtension

* remove CPM from umbraco templates

* remove change from readme

* update readme for umbracoproject

* Adding CPM options to Umbraco Project and Umbraco Templates

* update name param

* make central to default option

* Update templates/UmbracoExtension/Umbraco.Extension.csproj

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update templates/UmbracoExtension/.template.config/template.json

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update templates/UmbracoProject/.template.config/template.json

Co-authored-by: Andy Butland <abutland73@gmail.com>

* add PackageManagement into Visual studio display

* Apply suggestions from code review

* Fix ascii art and typo.

* Remove trailing commas in template.json files.

* Aligned casing and grammar between package management choices.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-26 13:33:39 +00:00
Niels LyngsøandGitHub 7c031f8ae4 is-routable-context-condition (#21428) 2026-02-26 13:29:58 +00:00
e9a6d52814 Collection: Provide UmbEntityContext for entity collection item elements (#21847)
* Add UmbEntityContext to collection item elements

* add tests

* Add context boundary to entity collection items

* Add test for entity context boundary

* Update umb-entity-collection-item-element-base.element.test.ts

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update umb-entity-collection-item-element-base.element.ts

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update umb-entity-collection-item-element-base.element.test.ts

* Update umb-entity-collection-item-element-base.element.ts

* Revert "Update umb-entity-collection-item-element-base.element.ts"

This reverts commit 9e4ef79150.

* Provide entity context on host and update tests

* Revert "Provide entity context on host and update tests"

This reverts commit f618a8216e.

* Test entity context boundary for collection items

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-26 14:25:18 +01:00
Niels LyngsøandGitHub 27618a3621 Block Editors: Align create label (#21731)
Use 'add content' to align with the create modal and other Block Editors
2026-02-26 13:31:19 +01:00
e0bb8f294e E2E: QA Added acceptance tests for for scheduled publishing (#19794)
* Cleaned up

* Make ScheduledPublishing tests run in the pipeline

* Updated npm command

* Increased timeout

* Updated npm command

* Addec console log to test in the pipeline

* Make tests run in the pipeline

* Removed step to verify that the document is published since it doesn't work in the pipeline - only works locally

* Update npm command

* Fixed tests

* Fixed comments

* Removed unnecessary comments

* Revert npm command

---------

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2026-02-26 10:47:50 +00:00
Engiber LozadaandGitHub 1cb8b2c8d2 Media Picker Modal: Fix missing tooltip on media items in picker modal. (#21913)
* Set title attribute on media card in picker.

* Add title attribute to uui-card-media in media inputs.
2026-02-26 09:55:50 +01:00
Andy Butland 2f381fe700 Fix after merge. 2026-02-26 07:05:51 +01:00
Andy Butland c3fc3c949e Merge branch 'release/17.2.1' 2026-02-26 06:59:27 +01:00
Andy ButlandandGitHub 0a40fe7364 Data Types: Use configured ValueType when creating Label data types (closes #21853) (#21914)
Use configured ValueType when creating Label data types.
2026-02-26 12:44:07 +09:00
a8526429ba Cache: Ensure local cache instructions count towards last synced ID (#21907)
* Ensure local cache instructions count towards last synced ID

* Add obsoletion message to the interface.

* Fixed failing integration tests, then refactored them so they call and test the non-obsolete method.

* Rework the solution to retain existing functionality

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-25 15:03:26 +00:00
Andy Butland 040f27c673 Bumped version to 17.2.2. 2026-02-25 15:03:55 +01:00
Andy ButlandandLaura Neto c9c16d2605 URL Info: Fix invariant content URLs missing under non-default language domains (closes #21866) (#21883)
* Show correct URLs for invariant content under non-default language domains.

* Use configured domain hosts instead of request host for fallback URL filtering.

* Addressed feedback from code review.

* Fixed code warnings.

* Update file references in integration test csproj.

* Simplify invariant URL culture filtering by determining cultures upfront

Instead of querying all cultures and post-processing to remove irrelevant
URLs, determine the relevant cultures before the loop by checking which
domains are assigned to the content's ancestor path.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-02-25 11:28:39 +01:00
df952c92a2 URL Info: Fix invariant content URLs missing under non-default language domains (closes #21866) (#21883)
* Show correct URLs for invariant content under non-default language domains.

* Use configured domain hosts instead of request host for fallback URL filtering.

* Addressed feedback from code review.

* Fixed code warnings.

* Update file references in integration test csproj.

* Simplify invariant URL culture filtering by determining cultures upfront

Instead of querying all cultures and post-processing to remove irrelevant
URLs, determine the relevant cultures before the loop by checking which
domains are assigned to the content's ancestor path.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-02-25 11:27:26 +01:00
Andy Butland 62eb91675d Database Cache: Fix full database cache rebuild dropping variant and composed property values (closes #21863, #21882) (#21890)
* Resolve full database cache rebuild dropping variant and composed property values

* Addressed feedback from code review.
2026-02-25 08:01:25 +01:00
Andy ButlandandGitHub 09206a62ac Database Cache: Fix full database cache rebuild dropping variant and composed property values (closes #21863, #21882) (#21890)
* Resolve full database cache rebuild dropping variant and composed property values

* Addressed feedback from code review.
2026-02-25 07:42:23 +01:00
Andy ButlandandGitHub d3b3661efc Dotnet Templates: Update default UmbracoVersion template value using MSBuild target (closes #21889) (#21893)
Apply version replacement to extensions template.
2026-02-25 01:31:53 +00:00
b1ca081613 Image cropper and file upload: Implemented automatic naming of uploaded file (closes #21764) (#21775)
* Added a feature to have automatic naming of the uploaded media files as per requested by #21764

* Replaced UMB_PROPERTY_DATASET_CONTEXT by UMB_NAMEABLE_PROPERTY_DATASET_CONTEXT to ensure proper use of isNameablePropertyDatasetContext

* style: fix import ordering to match eslint rules

Co-Authored-By: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>

* style: order furthest relative imports first

Co-Authored-By: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-02-24 13:11:26 +00:00
Andy ButlandandGitHub 15a75b1c5d Document Repository: Batch document IDs in GetContentSchedulesByIds to avoid SQL parameter limit (closes #21865) (#21868)
* Update GetContentSchedulesByIds to retrieve data in groups to avoid overrunning the SQL parameter count.

* Protect against duplicate retrieval if duplicate IDs are provided.
2026-02-24 11:45:57 +01:00
Andy ButlandandGitHub 5df7ff184f Backoffice Search: Discard stale search results when switching providers (closes #21784) (#21849)
* Discard stale search results when switching providers.

* Applied change from code review.
2026-02-24 10:44:17 +00:00
Andy Butland 5642c624d8 Remove legacy Windows path length checks and related tests (#21884)
Removed explicit 260-character path length checks from PhysicalFileSystem.GetFullPath and deleted associated unit tests. Updated tests to focus on path normalization and validity, and improved path assertions for clarity and cross-platform compatibility. No longer enforce or test for legacy Windows path length restrictions.
2026-02-24 11:21:18 +01:00
Andy ButlandandGitHub 7c0e332001 Content Picker: Fix dynamic root resolution for new unsaved documents (closes #21870) (#21880)
Correct resolution of dynamic root for new unsaved documents.
2026-02-24 09:46:45 +00:00
8b018c8178 Entity Data Picker: Add text filter feature toggle for Collection Data Sources (#21732)
* Add data-source package and integrate in input-entity-data

* Add optional description to collection items

* introduce extension picker data source

* fix problem with shallow copy because of js module in object

* nest manifest data

* Hide pagination when all items are shown

* Add a fallback page size

* merge extension insight code with extension code

* clean up

* Add optional description support to default item ref

* Revert "Add data-source package and integrate in input-entity-data"

This reverts commit e02881e8b6.

* fix post merge

* add input-extension utilizing input-entity-data

* proxy value and selection

* add todo

* temp hardcode config

* add typed config model

* Support multiple extension types in filters

* Use extensionTypes filter and deprecate type

Standardize extension collection filtering by introducing extensionTypes and phasing out the old type field.

* More explicit type name

* Expose allowedExtensionTypes as a @property on UmbInputExtensionElement

* Add text filter support for entity data picker

* remove reexport as this is not public available

* remove unused

* clean up

* clean up

* Add storage and getter for allowedExtensionTypes

* Inline collection view alias and remove constant

* Update vite.config.ts

* Update manifests.ts

* Update extension.picker-data-source.ts

* add tests for extension picker data source

* change to an observable feature config

* make feature object optional

* add unit tests

* Reference condition class directly in manifests

* clean up observers if data source type changes

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-24 09:30:51 +00:00
0f8b38c1b4 Razor Template Debugging: Allow Umbraco projects to work with the Razor cohosting editor (#21861)
* Allow Umbraco projects to work with the Razor cohosting editor

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-24 08:59:54 +01:00
Andy ButlandandGitHub 24540e11e0 Content Type: Fix null property description displaying as "null" string (closes #21873) (#21879)
Ensure an empty string is rendered for a null property description rather than a "null" string.
2026-02-24 07:44:36 +00:00
a2e3b929bd UmbracoExtension template: Use runtimeConfigPath for automatic auth (#21838)
* UmbracoExtension template: Use runtimeConfigPath for automatic auth

Use hey-api's runtimeConfigPath to pre-configure the generated client
by copying umbHttpClient's config (baseUrl, credentials, auth) at
initialization time. This eliminates the need for entrypoint auth setup
via consumeContext/getOpenApiConfiguration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: updates extension with newly generated SDK files

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:33:31 +00:00
9976b9f523 Examine: Keep track of rebuilding in memory on startup and move use of LongRunningOperationService to user triggered rebuilds (#21821)
* Keep track of rebuilding in memory

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove comment

* Revert back to original with lock

* Apply suggestion from @Zeegaan

* Remove unused

* Adress review comments

* Improve in-memory rebuild tracking for index rebuilder.

* Add cross-server rebuild status tracking via ILongRunningOperationService.

* Ensure index is used in operations, to allow rebuild of different indexes concurrently.

* Use Task.Delay.

* Resolve breaking change in constructor.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-24 06:56:03 +01:00
Mads RasmussenandGitHub f934c88911 Extension: Introduce extension core module and umb-input-extension element (#21705)
* Add data-source package and integrate in input-entity-data

* Add optional description to collection items

* introduce extension picker data source

* fix problem with shallow copy because of js module in object

* nest manifest data

* Hide pagination when all items are shown

* Add a fallback page size

* merge extension insight code with extension code

* clean up

* Add optional description support to default item ref

* Revert "Add data-source package and integrate in input-entity-data"

This reverts commit e02881e8b6.

* fix post merge

* add input-extension utilizing input-entity-data

* proxy value and selection

* add todo

* temp hardcode config

* add typed config model

* Support multiple extension types in filters

* Use extensionTypes filter and deprecate type

Standardize extension collection filtering by introducing extensionTypes and phasing out the old type field.

* More explicit type name

* Expose allowedExtensionTypes as a @property on UmbInputExtensionElement

* remove reexport as this is not public available

* remove unused

* clean up

* clean up

* Add storage and getter for allowedExtensionTypes

* Inline collection view alias and remove constant

* Update vite.config.ts

* Update manifests.ts

* Update extension.picker-data-source.ts

* add tests for extension picker data source
2026-02-23 16:54:26 +00:00
Andy Butland 79b3058a96 Bump version to 17.2.1. 2026-02-23 16:39:49 +01:00
23062762aa Media: Mark touchstart handler as non-passive using @eventOptions decorator (#21845)
The touchstart handler on the image cropper focus setter needs to call
preventDefault() to prevent scrolling during focal point drag. Use Lit's
@eventOptions({ passive: false }) decorator to explicitly declare this,
resolving the browser warning about non-passive event listeners.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-23 11:00:26 +01:00
e5ec2a9f11 Examine: For indexing in the RTE, replace all HTML tags with spaces to make sure word boundaries are preserved (#21797)
* For indexing in the RTE, replace all HTML tags with spaces to make sure wqord boundaries are preserved. Closes #21778

* Trim the returned string and adjust test cases to match new expected output #21778

* Address review comments:
- Updated XML docs
- Removed trimming in unit tests
- Moved HTML strip implementation to an extensions method

* Removed redundant regexes

* Address comment formatting

* Address failed tests by not replacing multiple characters if the replacement is String.Empty to preserve existing behavior

* Remove unnecessary partial and using.

* Add tests for introduced overload of StripHtml, fix found issues with replacement regex, then optimised by removing second regex and replaced with string operations.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 10:54:20 +01:00
9279a951c1 Routing: Fix umbracoUrlName being ignored in DefaultUrlSegmentProvider on culture-variant content when property is invariant (closes #16791) (#21735)
* Fix umbracoUrlName not working on multi sites

* update documentUrlServiceTests

* Use "is false" for false comparison

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 09:30:20 +00:00
dc1821e86f Cache Refreshers: Fix change tracking for content types (#21856)
* Fix change tracking for content types

* Update src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Updated comments for ContentTypeChangeTypes

* Clean up comments

* Revert "Clean up comments"

This reverts commit e17904c202.

* Actually clean up comments

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 07:56:32 +00:00
30aade8e3a Unit Testing: Add comprehensive coverage for BlockEditorVarianceHandler (#21706)
* test: add comprehensive test coverage for BlockEditorVarianceHandler

- Add tests for AlignPropertyVarianceAsync method (collection alignment)
- Add tests for AlignedExposeVarianceAsync method
- Add edge case tests for segment variations
- Add tests for multiple items and deduplication scenarios
- Remove TODO comment

Fixes #21706

* refactor: reduce code duplication in BlockEditorVarianceHandler tests

- Add CreateBlockListValue helper method to eliminate repeated setup code
- Remove redundant test cases to reduce duplication
- Consolidate similar tests while maintaining essential coverage

Fixes code duplication issues reported in PR #21706

* fix: correct assertion in AlignPropertyVarianceAsync_Removes_NonDefault_Culture_Values test

When culture variance is disabled (ContentVariation.Nothing), the culture
should be set to null, not preserved. This matches the behavior tested in
Removes_Default_Culture_When_Culture_Variance_Is_Disabled test.

* fix: always deduplicate expose entries in AlignExposeVariance

Deduplication should always occur at the end of AlignExposeVariance,
even when no alignment is needed. This ensures duplicate expose entries
are removed regardless of whether variance alignment occurred.

* fix: remove expose entries when ContentData is missing

Expose entries that don't have matching ContentData should be removed
from the expose list. This ensures data consistency and prevents orphaned
expose entries.

* test: add 8 additional test cases for BlockEditorVarianceHandler

Adds comprehensive test coverage for:
- Culture assignment scenarios
- Segment variation handling
- Multiple ContentData items
- Edge cases (missing element types, no matching expose)
- Variation matching scenarios

* refactor: eliminate code duplication in BlockEditorVarianceHandler tests

Extract common test patterns into helper methods:
- CreatePropertyValues: Creates property values from configuration tuples
- CreateBlockPropertyValues: Creates block property values with alias/culture/segment
- CreateBlockItemVariations: Creates block item variations from tuples
- ExecuteAlignPropertyVarianceAsync: Executes AlignPropertyVarianceAsync with common setup
- ExecuteAlignedExposeVarianceAsync: Executes AlignedExposeVarianceAsync with common setup
- ExecuteAlignExposeVariance: Executes AlignExposeVariance with common setup
- SetupAlignedExposeTest: Sets up test data for AlignedExposeVarianceAsync tests

This eliminates copy-pasted code patterns across multiple test methods.

* refactor: eliminate duplication in AlignedPropertyVarianceAsync tests

Extract common test setup into ExecuteAlignedPropertyVarianceAsync helper method.
This eliminates duplication in:
- Assigns_Default_Culture_When_Culture_Variance_Is_Enabled
- Removes_Default_Culture_When_Culture_Variance_Is_Disabled
- Ignores_NonDefault_Culture_When_Culture_Variance_Is_Disabled
- AlignedPropertyVarianceAsync_Returns_As_Is_When_Variation_Matches

* fix: add missing using statements for Task, IList, IEnumerable, Func

* fix: correct Assert.ThrowsAsync usage - await the task when accessing exception

* fix: await Assert.ThrowsAsync directly to get exception

* remove: AlignPropertyVarianceAsync_Throws_When_PropertyType_Is_Null test

* fix: mock should return null for unknown content types in AlignExposeVariance test

* Revert production code changes - keep only test additions

* Remove bug-fix verification tests - moved to PR #21801

* refactor: consistently use CreateBlockListValue helper in all tests

* test: restore AlignExpose_Can_Handle_Variant_Element_Type_With_All_Invariant_Block_Values test

* docs: clarify why mock returns null for unknown content types

* refactor: use configuration class to reduce argument count in CreateBlockPropertyValues

* fix: add missing closing brace for Assert.Multiple block

* fix: remove leftover merge conflict marker

* fix: remove duplicate method definitions

* Remove unused code and usings. Encapulate BlockPropertyValueConfig. Fix code warnings.

* Standardise test naming, order of methods and use of Assert.Multiple.

* Complete test coverage with additional tests for AlignedExposeVarianceAsync.

---------

Co-authored-by: root <root@dragon.second>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 07:22:28 +00:00
Niels LyngsøandGitHub 13a095934e CurrentUserModal: use getContext instead of consumeContext (#21843)
use getContext instead of consumeContext
2026-02-20 15:11:10 +01:00
Nathaniel NunesandGitHub f01ea69919 Accessibility: Add title attributes to buttons in block list entry and property editor UI (#21842)
#21841 - Add title attributes to buttons in block list entry and property editor UI for better accessibility
2026-02-20 13:03:38 +00:00
4cb81b2e0e Document Workspace: Update document status on publish and unpublish (closes #21650) (#21668)
* Ensure document status shown in the Infor workspace view is up to date after unpublish and save/publish operations.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Further feedback from code review.

* Fix false-positive pending changes after save and publish by ensuring the property value preset builder reconstructs objects with the same property key order.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-02-20 13:42:03 +01:00
3ac5986f19 Fix: BlockEditorVarianceHandler deduplication and orphaned expose entries (#21801)
* Fix: BlockEditorVarianceHandler deduplication and orphaned expose entries

- Fix deduplication not running when no alignment needed
- Fix orphaned expose entries not removed when ContentData missing

Fixes #21799
Fixes #21800

* Update src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Fix indentation and add bug-fix verification tests

* Code tidy, use helpers in tests.

---------

Co-authored-by: root <root@dragon.second>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-20 12:01:32 +00:00
Yari MariënandGitHub 8b8b1c607a Localization: Added missing translation values for field label on create member form (#21835)
fix(member-info-screen): added translation for confirmPassword to fix visible translation key on member view/info
2026-02-20 12:52:05 +01:00
Andy ButlandandGitHub e9ba715e62 Server Events: Invalidate client-side cache for composing types on composition deletion (#21831)
* Invalidate client-side cache after removal of composed content type.

* Address feedback from code review.
2026-02-20 15:12:57 +09:00
0ce31e0793 Media Querying: Fix MediaAtRoot() to use IMediaNavigationQueryService root keys (#21807)
* Add media navigation support to PublishedContentQuery

Introduced IMediaNavigationQueryService as a dependency and updated constructors to resolve it. Refactored ItemsAtRoot to accept a navigation query service, enabling MediaAtRoot to retrieve root media items via navigation queries. ContentAtRoot and MediaAtRoot now use the appropriate navigation query service for root item retrieval.

* Add IMediaNavigationQueryService support to content query

Extended PublishedContentQuery and ContentFinderByConfigured404 to accept and use IMediaNavigationQueryService alongside IDocumentNavigationQueryService. Updated constructors and service registrations to ensure both navigation services are available for enhanced content and media navigation scenarios.

* Add obsolete constuctors and expand PublishedContentQuery tests

Introduce [Obsolete] constructor overloads for PublishedContentQuery and ContentFinderByConfigured404 to support legacy usage, scheduled for removal in Umbraco 19. Refactor ItemsAtRoot for clarity. Significantly expand PublishedContentQueryTests with comprehensive unit tests covering constructor validation, Content/Media overloads, root item retrieval, and search functionality, including paging, ordering, and culture context. Add test helpers and mocks to improve test coverage and reliability.

* Fixes to constructor overloads.

* Re-organise tests into unit and integration (so the former, that don't need integration setup, will run more quickly).

* Remove low value integration tests.

---------

Co-authored-by: Fabian Beier <Fabian.Beier@aa-g.de>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-19 11:57:03 +00:00
bb567524d4 Media Collection: Introduce Entity Actions for cards (#21816)
* refactor to use the collection item extension point

* Add actions slot to media collection item card

* Set actions slot button background in media card

* Update src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-19 10:34:14 +01:00
a1627b82d3 RTE Link Picker: Fix media selection to allow items not permitted at root. (#21678)
* Exclude media folders and remove media-type filtering.

* Remove unused import.

* general clean up

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-02-19 09:30:43 +00:00
Andy Butland 588e93ae75 Re-ordered methods in class. 2026-02-19 09:40:29 +01:00
Andy ButlandandGitHub 9ea0520a46 Repository Caches: Fix GUID read repository cache key collision causing GetAll failures (closes #21756) (#21762)
* Fix GUID read repository cache key collision with int-keyed repositories.

* Remove GUID read repository for templates.

* Ensure GUID read repository cache keys are invalidated.

* Further optimisation of by GUD GetAll reads.

* Move default repository cache timespan to a centralised constant.

* Further use of centralised constant.

* Add GetGuidKey<T>(Guid id) and update callers to use it.
2026-02-19 07:58:07 +00:00
Andy Butland 4ca0d041f2 Merge branch 'release/17.2' 2026-02-19 07:46:06 +01:00
Andy Butland 51e91c88ae Bump version to 17.2.0. 2026-02-19 06:49:24 +01:00
Niels Lyngsø b2af37a149 Merge branch 'release/17.2'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
2026-02-18 15:39:11 +01:00
Jacob OvergaardandGitHub 725a0322ed build(deps): bumps @umbraco-ui/uui to 1.17.0 (#21765) 2026-02-18 14:59:45 +01:00
Mads RasmussenandGitHub 7f1e30a22e Document Collection: Enable Entity Actions on cards (#21802)
* Use card view kind for Document Collection

* Remove unused UmbUserDetailModel import

* Update document-collection-item-card.element.ts

* combine elements

* render actions
2026-02-18 13:29:26 +00:00
Mads RasmussenandGitHub f7661a310f Document Collection: Reuse card view kind (#21791)
* Use card view kind for Document Collection

* Remove unused UmbUserDetailModel import

* Update document-collection-item-card.element.ts
2026-02-18 12:48:21 +01:00
Andy ButlandandGitHub 48a431eeec Packaging: Fix package migration plans re-running all steps when a new step is added (closes #21730) (#21734)
* Ensure package migration steps only run once by moving the override of IgnoreCurrentState to true to the derived AutomaticPackageMigrationPlan, where it's needed.

* Add integration test to verify the fix.

* Fix failing integration test (the test migration plans were leaking outside of the new test, and being picked up by the DI container for other tests.
2026-02-17 10:17:50 +01:00
Laura NetoandGitHub d0acfa46bc Audit: Fix container update operations incorrectly logged as new (#21774)
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.
2026-02-17 08:43:10 +01:00
Andy ButlandandGitHub dbdafbdc19 Migrations: Re-trust untrusted foreign key and check constraints on SQL Server and fix bulk inserts to prevent recurrence (#21744)
* Be explicit about creating foreign key constrains with check (already the default).

* Add a migration to attempt to ensure that all constrains a trusted.

* Updated name of migration class.

* Ensure long timeout for migration.

* Update BulkInsertRecordsSqlServer to use SqlBulkCopyOptions.CheckConstraints and verify that no untrusted constraints remain afterward.

* Ensure NPoco InsertBulk uses SqlBulkCopyOptions.CheckConstraints by introducing UmbracoSqlServerDatabaseType (subclass of SqlServer2012DatabaseType) that overrides InsertBulk to pass SqlBulkCopyOptions.CheckConstraints.

* Also handle InsertBulkAsync.
2026-02-17 07:11:24 +01:00
Mads RasmussenandGitHub 3ef02bd3fa Tree: Provide UmbEntityContext from the tree item context base (#21770)
* Provide UmbEntityContext from tree item

* add tests to ensure entity context is provided
2026-02-16 19:37:04 +01:00
Andy ButlandandGitHub ba0d865ac9 Decimal Property Editor: Increase step size precision for configuration fields (closes #21759) (#21769)
Increase precision available to decimal data types.
2026-02-16 15:30:15 +01:00
dependabot[bot]andJacob Overgaard 541958b8c3 Bump qs
Bumps the npm_and_yarn group with 1 update in the /src/Umbraco.Web.UI.Client directory: [qs](https://github.com/ljharb/qs).


Updates `qs` from 6.14.1 to 6.14.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 10:31:35 +01:00
9c1a810fc7 Permissions: Fix GetPermissionsAsync to resolve permissions from nearest ancestor (#21741)
* 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>
2026-02-16 09:57:34 +01:00
695807b32e Delivery API: Make the Delivery API "access" attributes public (closes #21677) (#21760)
* Make the Delivery API "access" attributes public

* Update src/Umbraco.Cms.Api.Delivery/Filters/DeliveryApiAccessAttribute.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Delivery/Filters/DeliveryApiMediaAccessAttribute.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Also make the VersionedDeliveryApiRouteAttribute public

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-16 08:20:09 +01:00
dependabot[bot]andJacob Overgaard 30c6350643 Bump the npm_and_yarn group across 2 directories with 2 updates
Bumps the npm_and_yarn group with 1 update in the /src/Umbraco.Web.UI.Client directory: [markdown-it](https://github.com/markdown-it/markdown-it).
Bumps the npm_and_yarn group with 2 updates in the /src/Umbraco.Web.UI.Login directory: [lodash](https://github.com/lodash/lodash) and [markdown-it](https://github.com/markdown-it/markdown-it).


Updates `markdown-it` from 14.1.0 to 14.1.1
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/14.1.0...14.1.1)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `markdown-it` from 14.1.0 to 14.1.1
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/14.1.0...14.1.1)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-version: 14.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: markdown-it
  dependency-version: 14.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 21:53:21 +01:00
785168cacd Persistence Models: DTO attributes fixes (#21670)
* quote table, column and alias names with SqlSyntaxProvider methods in raw sql

* refactoring private methods into new file as internal methods,
refactor new extensions into another file

* refactor GetAlias method

* Double check the change

* improve code health

* change new static classes into public static partial class NPocoSqlExtensions

* resolve some Copilot review suggestions

* revert Copilot suggestion because it decreases code health

* revert test

* compare in with LOWER, change two methods from private to protected in UmbracoDatabaseFactory

* revert Query.cs in this PR

* Refactor for code health and fixing raw sql

* divers small issues fixed

* refactor two methods to respect the DRY pricipal

* update IQuery interface

* clean up

* revert refactoring for CodeScene

* delete obsolete Test

* rename method

* remove new methods and updates, which are not relevat for this PR

* prepare for additional states in the future

* don't mix string building methods

* fix SQL injection danger

* fix test for reverted methods

* another SqlSyntax issue

* fix update

* fix reverted changes

* restore change for this PR

* restore change for this PR

* fix merge bug

* update formating

* extend ISqlSytax for database independent autoIkrement feature

* fix DTOs, extend ISqlSyntax

* fix tests

* revert

* updates

* diverse SqlSyntax and NPoco related updates for custom databse providers

* fix names

* fix PrimaryKey for multi columns

* Resolve the issues with SqlSyntaxProvider for SQLite. If executed correctly, a single test would reveal the problem.

* add another test

* revert changes which causes even more issues

* fix column const naming

* ensure column const names from v17.2

* add comment for change

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeTemplateDto.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* resolve review comments

* Make ReferenceMemberName consistent across all DTOs (use constants defined on the referenced DTO).

* Ensure [ExplicitColumns] attribute exists on all DTOs.

* Ensure we consistently use PrimaryKeyColumnName over PrimaryKeyName.

* Fix further inconsistency to use only TemplateNodeIdColumnName.

* Fixed trailing whitespace.

* Restored primary key constraint name on ContentVersionCleanupPolicyDto (it doesn't seem in scope of PR to remove this).

* Removed the confusing PrimaryKeyColumnName constants for multi-column primary key DTOs where the constant refers to only one of the key columns.

* ReferenceMemberName needs to be a C# property name, so it's safer to use nameof.

* Comment fix.

* Amended accessibility modifiers.

* Fixed/tidied comments.

* Fixed references from UserGroupDto.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-13 11:42:36 +00:00
MoleandGitHub 8ace1e0e94 Persistence models: Fix incorrect webhook DTO (#21736)
Fix incorrect webhook dto
2026-02-13 11:48:48 +01:00
Jacob OvergaardandGitHub 4ce56e4bc9 Entity Actions: Adds a descriptive title to the first action so you know what it does (#21739)
* fix: adds a title to the first entity action in the entity actions bundle, otherwise you do not know what it does, unless the icon is very descriptive

* calculate the label once

* concatenate data-mark string better
2026-02-13 08:51:24 +01:00
Nhu DinhandGitHub 0b6507a02e E2E: QA Updated acceptance tests for adding block element to match the UI changes (#21679) 2026-02-12 23:29:11 +07:00
a374042e89 Textbox/area: Add character countdown message (closes #19505) (#21722)
* Show character count and instant exceed validation.

* Show character count for textarea editor.

* Add character-count utility and use in editors.

* Rename char count state, add tests, fix imports.

* Update textbox character messages in locales.

* Apply suggestions from code review

* Align textarea and textbox in use of #getMaxLengthMessage private helper function.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-12 13:27:27 +00:00
bc0110079c Package Manifest: Enable cache buster token replacement for extensions (closes #16893) (#21709)
* fix(manifest): replace %CACHE_BUSTER% token in extension paths served by manifest API

Move cache buster replacement to the presentation layer (manifest controllers)
instead of the infrastructure service. The importmap replacement stays in
HtmlHelperBackOfficeExtensions where it was already handled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Ordered usings.

* Add unit test for cache buster token replacement.

* Apply suggestions from code review

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Fix ambiguous controller constructors.

* Make ReplaceCacheBusterTokens void since it mutates in-place.

* Defensively code against special characters in the cache buster hash.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-12 11:35:58 +00:00
394de9e2d0 Imaging: Intelligent format detection for thumbnail generation (#21570)
* Imaging: Add format parameter to thumbnail component with webp default

Adds a format parameter to the imaging resize API endpoint and the
umb-imaging-thumbnail component. The component defaults to 'webp' format
for optimal browser support and smaller file sizes.

This ensures that non-image file types (like PDFs) that have custom image
providers can render thumbnails correctly by explicitly requesting an
output format instead of relying on the original file extension.

Changes:
- Add format query parameter to ResizeImagingController
- Pass format through IReziseImageUrlFactory to ImageUrlGenerationOptions
- Add format property to UmbImagingResizeModel TypeScript type
- Add format property to umb-imaging-thumbnail element (default: 'webp')

https://claude.ai/code/session_01GP7N2iTashrG1cBdYVSW97

* Imaging: Include format in cache key generation

Fix cache key to include the format parameter so that different format
requests with identical dimensions are cached separately.

https://claude.ai/code/session_01GP7N2iTashrG1cBdYVSW97

* Imaging: Refactor to use ImageResizeOptions record

Introduces ImageResizeOptions record to encapsulate resize parameters,
addressing CodeScene's "Excess Number of Function Arguments" warning.

Changes:
- Add ImageResizeOptions record with Height, Width, Mode, Format properties
- Add new CreateUrlSets overload accepting ImageResizeOptions
- Mark old CreateUrlSets overload as obsolete (removal in v19)
- Update controller to use new options pattern

https://claude.ai/code/session_01GP7N2iTashrG1cBdYVSW97

* Imaging: Add explicit obsolete method to satisfy API compatibility

The API compatibility checker requires the method to exist explicitly
in the implementation, not just via default interface method.

Co-Authored-By: Claude <noreply@anthropic.com>

* Imaging: Add unit tests for imaging store format parameter

Tests verify that:
- Different formats are cached separately (webp vs png)
- Crops with and without format are cached separately
- Cache operations work correctly with format parameter

Co-Authored-By: Claude <noreply@anthropic.com>

* Add API compatibility suppression for resize imaging endpoint

Suppress CP0002 for adding optional 'format' parameter to the resize
imaging controller endpoint. The HTTP API remains backward compatible
as existing clients simply won't send the new parameter.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix API compatibility for IReziseImageUrlFactory

Restructure interface to maintain binary compatibility:
- Keep original 4-parameter method as required (marked obsolete)
- Add new ImageResizeOptions overload with default implementation
- Factory overrides new method to properly handle format parameter

This allows existing implementations to continue working while
new code uses the ImageResizeOptions overload with format support.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore(api): regenerate API compatibility suppression file

Regenerated the CompatibilitySuppressions.xml file with proper metadata
to suppress the breaking change detection for the optional format parameter
added to ResizeImagingController.Urls method. This change is backward
compatible at the HTTP API level.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat(imaging): automatic format conversion for non-image files

Move format conversion logic from frontend to backend IImageUrlGenerator
implementations to handle format defaults intelligently based on source
file types.

Why Backend Should Handle This:
1. **Source-aware decisions**: Backend has access to source file extension
   and can determine if it's a true image (jpg, png) or processable
   non-image (pdf with plugin)

2. **Consistent behavior**: All consumers (backoffice, APIs, custom code)
   get consistent format handling without duplicating logic

3. **Plugin compatibility**: When ImageSharp plugins add support for new
   file types (e.g., PDF thumbnails), the system automatically converts
   them to web-compatible image formats

4. **User override preserved**: Explicit format parameter still works as
   an override, giving users control when needed

Changes:
- Add Format property to ImageUrlGenerationOptions for explicit format requests
- ImageSharp implementations auto-detect non-image files and default to WebP
- ReziseImageUrlFactory passes format directly instead of via FurtherOptions
- Frontend imaging-thumbnail component removes hardcoded format='webp' default
- Backend now handles: format override > auto-detect non-images > keep original

Example Scenarios:
- JPEG → No format added (keeps JPEG)
- PNG → No format added (keeps PNG)
- PDF (with plugin) → Auto-adds format=webp
- Any file + explicit format param → Uses specified format

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(imaging): improve robustness and code quality

Address code review feedback with three improvements:

1. Add URI parsing error handling to prevent UriFormatException crashes
   when malformed URLs are passed to RequiresFormatConversion()

2. Extract magic string array to class-level constant (TrueImageFormats)
   to eliminate duplication and provide single source of truth

3. Remove inconsistent default interface implementation that didn't pass
   format parameter, forcing concrete implementations to handle it properly

All changes maintain backward compatibility and improve code safety.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor(imaging): move format determination to factory layer

Refactors format conversion logic from ImageSharp implementations to the factory layer for better separation of concerns and maintainability.

Changes:
- Add ContentImagingSettings.TrueImageFormats configuration (native image formats)
- Move format determination logic to ReziseImageUrlFactory.DetermineOutputFormat()
- Simplify ImageSharp v1 & v2 generators (remove duplicate RequiresFormatConversion())
- Add backward-compatible obsolete constructor to ReziseImageUrlFactory
- Add 50 comprehensive unit tests for format determination and configuration

Benefits:
- Single Responsibility: ImageSharp generators only generate URLs, don't make business decisions
- DRY: Eliminated 70+ lines of duplicated code between ImageSharp packages
- Configurable: TrueImageFormats setting allows customization
- Testable: Format logic tested independently of ImageSharp

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor(imaging): repurpose ImageFileTypes for native format determination

Repurposes the existing unused ContentImagingSettings.ImageFileTypes setting instead of adding a new TrueImageFormats property. This provides better configuration control and eliminates the need for a new setting.

Changes:
- Repurpose ContentImagingSettings.ImageFileTypes (was unused, now active)
- Update ReziseImageUrlFactory to use ImageFileTypes for format determination
- Update TemporaryFileConfigurationPresentationFactory to use config instead of IImageUrlGenerator
- Add comprehensive XML documentation explaining usage in factory layer and backoffice UI
- Update all tests to reference ImageFileTypes

Benefits:
- No new configuration property needed (reuses existing setting)
- Frontend gets configurable format list instead of dynamic ImageSharp formats
- Better separation of concerns (config determines behavior, not infrastructure)
- Clearer documentation of where and how the setting is used

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test(core): remove duplicate test methods in ContentImagingSettingsTests

Removed duplicate test methods that were causing compilation errors:
- ImageFileTypes_DefaultValue_ContainsExpectedFormats (duplicate)
- ImageFileTypes_DefaultValue_MatchesStaticConstant (duplicate)
- ImageFileTypes_CanBeConfigured_WithCustomFormats (duplicate with incorrect test data)
- Contradicting assertion in StaticConstants_HaveExpectedValues

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(api): suppress CP0006 for IReziseImageUrlFactory.CreateUrlSets overload

Added API compatibility suppression for the new CreateUrlSets overload that
accepts ImageResizeOptions parameter. This change is backward compatible as the
concrete implementation already has both methods and the old method is marked
obsolete to guide users.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fixes merge conflict

* formatting

* fix(api): suppress CP0002 for TemporaryFileConfigurationPresentationFactory constructor change

Added suppression for constructor signature change where IImageUrlGenerator
parameter was replaced with IOptionsSnapshot<ContentImagingSettings> to get
ImageFileTypes directly from configuration instead of from the image URL
generator.

This change is part of the WebP thumbnail feature and aligns with getting
native format information from ContentImagingSettings configuration.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(api): maintain backward compatibility for TemporaryFileConfigurationPresentationFactory constructor

Instead of suppressing the CP0002 error, added back the old constructor marked
as [Obsolete] that chains to the new one. The old constructor:
- Accepts the original parameters (ContentSettings, RuntimeSettings, IImageUrlGenerator)
- Ignores the IImageUrlGenerator parameter (kept only for backward compatibility)
- Uses StaticServiceProvider to get ContentImagingSettings
- Chains to the new constructor

This maintains full backward compatibility while migrating to the new approach
where ImageFileTypes comes directly from ContentImagingSettings configuration.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test(core): update StaticConstants_HaveExpectedValues test to match actual constant value

The test was checking for format order 'jpg,jpeg,png,gif,webp,bmp,tif,tiff' but
the actual constant StaticImageFileTypes is 'jpeg,jpg,gif,bmp,png,tiff,tif,webp'.
Updated the test to match the actual constant value.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(api): resolve constructor ambiguity in TemporaryFileConfigurationPresentationFactory

Add [ActivatorUtilitiesConstructor] attribute to the new constructor to explicitly
indicate which constructor the DI container should use when both constructors have
the same number of parameters.

This fixes the "ambiguous constructors" error that was preventing the OpenAPI
contract test from running.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: adds parameter even though it is unused to help the DI system figure out which constructor to use

* test(api): update ReziseImageUrlFactory test to reflect corrected query string handling

The implementation was fixed to correctly handle URLs with query strings by
stripping the query string before extracting the file extension. Updated the
test expectations to verify that PDFs with query strings are now processed
correctly and converted to WebP format, rather than returning empty results.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* chore: adds double obsolete constructor to stay persistent and be able to use only new constructor with same amount of arguments

* Apply suggestions from code review

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Address remaining PR #21570 review comments

- Add GetFileExtension() to UriExtensions for reusable URI extension extraction
- Simplify ReziseImageUrlFactory to use GetFileExtension() instead of manual parsing
- Add default implementation to IReziseImageUrlFactory to avoid CP0006 breaking change
- Remove CP0006 suppression from CompatibilitySuppressions.xml
- Fix Obsolete message format and remove unnecessary [ActivatorUtilitiesConstructor]
- Add TODO for ReziseImageUrlFactory typo rename
- Remove stale ObsoleteOverload test and low-value ContentImagingSettingsTests
- Add unit tests for UriExtensions.GetFileExtension()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Avoid unnecessary second call to GetFileExtension().

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-12 11:21:06 +00:00
6a4b28b78a Management API: Optimize collection view performance by eliminating N+1 patterns (#21684)
* Added further integration test to verify list view permission checks.

* Replace per item GetPermissionsForPath calls with a single batch GetPermissions query across all unique path node ids.

* Added unit test verifying DocumentCollectionPresentationFactory and fixed constructors.

* Replace per-item IsProtected calls with a single batched GetAll query and in-memory path matching.
Compute shared ancestor path keys once for collection siblings instead of per item.

* Eliminate redundant GUID to int conversions in HasScheduleFlagProvider.

* Batch user profile resolution in collection view mapping.

* Addressed issues from code review.

* DRY up GetOwenerName and GetCreatorName in CommonMapper.

* Apply suggestions from code review

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Apply feedback from code review.

---------

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
2026-02-12 10:22:39 +00:00
3997 changed files with 145122 additions and 20426 deletions
-35
View File
@@ -1,35 +0,0 @@
{
"permissions": {
"allow": [
"Bash(dir:*)",
"Bash(do)",
"Bash(done)",
"Bash(echo:*)",
"Bash(find:*)",
"Bash(for:*)",
"Bash(gh pr diff:*)",
"Bash(gh pr view:*)",
"Bash(git log:*)",
"Bash(grep:*)",
"Bash(npm run build:*)",
"Bash(npm run check:*)",
"Bash(npm run compile:*)",
"Bash(npm run lint:*)",
"Bash(npm run:*)",
"Bash(npx eslint:*)",
"Bash(npx tsc:*)",
"Bash(tree:*)",
"Bash(gh issue view:*)",
"Bash(npm test:*)",
"mcp__umbraco__create*",
"mcp__umbraco__get*",
"mcp__playwright__browser_click",
"mcp__playwright__browser_type",
"mcp__playwright__browser_wait_for"
]
},
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [
"umbraco-cms"
]
}
+251
View File
@@ -0,0 +1,251 @@
---
name: umb-review
description: Automated PR code review for Umbraco CMS. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality. Non-interactive — outputs a full structured review. Use this skill whenever the user asks to review a branch, review a PR, check their changes for issues, analyze a diff, or validate breaking change patterns — even if they don't say "review" explicitly. Does NOT apply to writing new code, fixing bugs, refactoring, explaining architecture, writing tests, or reviewing documentation content.
argument-hint: <target-branch>
---
# PR Review - Umbraco CMS
Automated, non-interactive PR code review. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality.
**Do NOT use AskUserQuestion at any point. This skill runs fully autonomously.**
## Arguments
- `$ARGUMENTS` - Optional: target branch to diff against (auto-detected from PR, falls back to `origin/main`)
## Instructions
### 0. Verify GH CLI is Available
Run `gh auth status`. If it fails, read `references/gh-cli-setup.md` and present the setup instructions to the user. Do not proceed with the review.
### 1. Resolve Target Branch
Determine the target branch for comparison using this priority order:
1. **Explicit argument**: If `$ARGUMENTS` is provided and non-empty, use it as the target branch
2. **PR target branch**: If no argument, run `gh pr view --json baseRefName --jq '.baseRefName'` to detect the target branch of the current branch's open PR. If a PR exists, use `origin/{baseRefName}` as the target branch.
3. **Fallback**: If no argument and no PR found (command fails or returns empty), default to `origin/main`
Store the resolved target branch for use in subsequent steps. Log which resolution method was used (e.g., "Target branch: `origin/v18/dev` (from PR #1234)").
### 2. Load Review Standards
#### 2a. Load coding preferences
Read the coding preferences and code review scoring criteria from:
- `references/coding-preferences.md` (relative to this skill file)
Parse and internalize all rules, conventions, scoring categories, and severity definitions. These are your review criteria.
#### 2b. Load area-specific documentation
Once the changed file list is known (after step 3a), determine which areas of the codebase are touched and load the relevant documentation. Execute this sub-step between 3a and 3b. This documentation takes precedence over sibling comparison for architectural and pattern validation.
**Resolution order for each changed file:**
1. **Find the nearest `CLAUDE.md`** — walk up from the changed file's directory toward the repository root. The first `CLAUDE.md` found is the area guide for that file. Read it.
2. **Read referenced docs** — if the `CLAUDE.md` references documentation files (e.g., a `docs/` directory), use the descriptions in the `CLAUDE.md` to determine which docs are relevant to the type of code being changed, and read those. If unsure, read all referenced docs — the cost of reading is low, the cost of missing a convention is high.
3. **Follow cross-references in loaded docs** — if a loaded doc references another doc as covering a complementary or related concern, and the changed files touch that concern, read the referenced doc too. Repeat until no new relevant cross-references remain.
4. **Check for applicable skills** — review the available skills list. If a skill exists for the type of code being changed, read the skill file to understand the expected patterns, structure, and conventions it enforces. Do NOT invoke the skill — just use it as a reference for what the correct implementation should look like.
**Store all loaded documentation** for use in step 4. These docs define the authoritative patterns and conventions that the review evaluates against.
### 3. Gather Changed Files
#### 3a. Collect file list, stats, and diff
Run these git commands (where `{target}` is the resolved target branch):
```bash
git diff {target}...HEAD --name-only --diff-filter=d # changed files (excluding deleted)
git diff {target}...HEAD --stat # line counts per file
git log {target}...HEAD --oneline # commit history
git diff {target}...HEAD # full diff (primary review source)
```
**If no changes found**: Output "No changes found between current branch and `{target}`. Nothing to review." and stop.
#### 3b. Filter out noise files
From the changed file list, classify each file as **noise** or **reviewable**.
**Noise files** (skip entirely — do not read, do not review):
| Pattern | Reason |
| ---------------------------------------------------- | ------------------------------- |
| `*.gen.ts`, `*.gen.cs` | Auto-generated API client code |
| `*.generated.cs`, `*.Designer.cs` (in `Migrations/`) | Auto-generated models/snapshots |
| `*/assets/lang/*.ts` (except `en.ts`) | Non-English translation files |
| `*/mocks/data/*.ts` | Test fixture data |
| `*/dist-cms/*`, `*/storybook-static/*` | Build output |
| `*/TEMP/InMemoryAuto/*` | Runtime-generated models |
| `package-lock.json` | Dependency lock file |
| `appsettings-schema.*.json` | Generated JSON schema |
Log the skip list: "Skipped {N} noise files: {comma-separated list of filenames}"
#### 3c. Read reviewable changed files
Read the full file for every reviewable changed file.
#### 3d. Track file counts
Keep track of these numbers for the review output in step 7: total changed files, noise files skipped, and reviewable files read. Also record: distinct production layers touched, distinct project directories, and total lines changed — these feed step 3e.
#### 3e. Assess PR complexity
Follow the procedure in `references/complexity-assessment.md`. Store the triggered dimensions and suggestions for step 7.
#### 3f. Classify PR scope
Classify the PR to determine which review steps are relevant:
| Classification | Condition | Effect |
| --------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **Gen-only** | All reviewable files are `gen.ts` | Skip steps 5 and 6; step 4 reviews impact on other code only |
| **Docs-only** | All reviewable files are `.md` | Skip steps 5 and 6; step 4 reviews intent and readability only |
| **Test-only** | All reviewable files are in `tests/` | Skip steps 5 and 6; step 4 reviews intent, code quality, and test coverage only |
| **Config-only** | All reviewable files are `.csproj`, `.props`, `.json` config, or CI/build files | Skip step 5; step 6 checks dependency version changes only |
| **Standard** | Anything else | No skips — run all steps |
### 4. Raw Code Review
Review each changed file holistically. Think like a senior developer reading a colleague's PR. Note all findings without worrying about format or severity yet.
#### 4a. Read and reason about each file
For each changed file, reason about: What does this code do? Is it correct? What's missing — validation, error handling, notifications, cleanup, edge cases? Could this break anything for consumers?
#### 4b. Validate against documentation and patterns
Use a **docs-first** approach: classify the code by what it does, check it against documented conventions, and only fall back to sibling comparison when docs don't cover the pattern.
**Step 1 — Determine the correct approach from documentation, then check whether the PR matches**
A PR is a proposed solution, not the source of truth. This step has two parts that must happen in order — do not start part B until part A is complete.
**Part A — Before validating/judging the implementation**, determine what the correct approach is for each new class or file based on what it does. Use the documentation loaded in step 2b to identify the expected base classes, patterns, and conventions. Write down the expected approach. Classify based on what the code does, not based on what neighboring files look like.
**Part B — Now compare the PR's implementation** against the expected approach from Part A. If it deviates from the documented approach, flag it. If the documentation specifies reference examples, read those examples to verify the implementation matches.
**Pattern match is the leading finding.** If the documentation defines a pattern that fits what the code does, the first and most important finding is whether the code follows that pattern.
**Step 2 — Fall back to sibling comparison**
If the documentation does not cover the specific pattern, or for cross-cutting concerns not addressed in docs, fall back to sibling comparison:
1. **New method on existing class/interface**: Grep for the most similar existing method on the same class using `-A 80` to capture the full method body (e.g., `UpdateCurrentUserAsync` → grep for `UpdateAsync` in the same file with `-A 80`). Compare line by line for missing cross-cutting concerns: notifications/events, validation, scoping, authorization, error handling, audit logging.
2. **New TS class**: Grep for siblings by base class (`extends {BaseClass}`) or by interface (`implements {Interface}`) or by name suffix (e.g., `CurrentUserController` → grep for `UserController`). Compare for missing concerns.
3. **New CS class**: Grep for siblings by base class (`class {ClassName} : {BaseClass}`) or by interface (`class {ClassName} : {Interface}`) or by name suffix (e.g., `ManagementApiComposer` → grep for `ApiComposer`). Compare for missing concerns.
**Important:** Sibling comparison validates cross-cutting concerns, but it must not override documented conventions. If a sibling deviates from documented patterns, that sibling is wrong — do not copy its deviation.
Store your raw findings — they feed into step 7.
### 5. Impact Analysis
**Skip this step if PR scope is docs-only, test-only, or config-only.**
Follow the procedure in `references/impact-analysis.md`.
### 6. Breaking Changes Check
**Skip this step if PR scope is docs-only or test-only. If config-only, only check for dependency version changes that could break consumers.**
Follow the procedure in `references/breaking-changes.md`.
### 7. Consolidate and Output Review
Merge findings from step 4 (raw review), step 5 (impact analysis), and step 6 (breaking changes). For each finding, assign severity (Critical/Important/Suggestion) and verify it relates to changed code — not pre-existing issues. Before outputting, drop any finding about whitespace, blank lines, formatting, or comment wording. Then present the review in this exact format:
```markdown
## PR Review
**Target:** `{target_branch}` · **Based on commit:** `{head_sha}`
[If any skipped files, append: · **Skipped:** {skipped} files out of {total} total]
[If step 3f classification is not "Standard", append: · **Classified as:** {classification}]
[12 sentences: what this PR accomplishes , keep it as short as possible, only highlight the primary essence.]
- **Modified public API:** {changed existing interfaces/types/classes/methods}
[Omit bullet if none]
- **Affected implementations (outside this PR):** {interfaces/types/classes/methods using modified public API}
[Omit bullet if none]
- **Breaking changes:** {violations with specifics}
[Omit bullet if none]
- **Other changes:** {changes not listed above that an Umbraco user, plugin developer, or API consumer would notice — e.g., behavior changes, default value changes, error message changes, new configuration options, removed functionality. Exclude internal renames, formatting, and private implementation details.}
[Omit bullet if none]
[If step 3e triggered any dimensions, insert this block. Omit entirely if nothing triggered:]
> [!NOTE]
> **Complexity advisory** — This PR may benefit from splitting.
>
> - **{Dimension}:** {Explanation and concrete split suggestion from step 3e}
> [one bullet per triggered dimension]
>
> _This is an observation, not a blocker. The full review follows below._
---
### Critical
[Must fix before merge — security vulnerabilities, data loss, broken functionality, breaking changes without proper patterns]
- **`{file}:{line}`**: {problem} → {fix}
[Omit section if none]
### Important
[Should fix — performance issues, missing tests, architectural violations, pattern misuse]
- **`{file}:{line}`**: {observation} → {suggestion}
[Omit section if none]
### Suggestions
[Nice to have — readability, minor refactoring, alternative approaches]
- **`{file}:{line}`**: {detail}
[Omit section if none]
---
[One of:]
## Approved
This looks good to be merged as-is, but please do a manual sanity check and testing before merging.
## Approved with Suggestions for improvement
Good to go, but please carefully consider the importance of the suggestions.
## Request Changes
Critical and important issues must be addressed first.
## Needs re-work
This is in such a bad state that the feedback of this review is not sufficient to guide improvements, the PR cannot be approved.
```
**Guidelines for the review output:**
— When reporting information, be extremely concise and sacrifice grammar for sake of concision.
- Only review code that was changed in the diff — pre-existing issues are out of scope. Focus on what compilers and linters cannot catch: behavioral side-effects (e.g., a changed default alters runtime behavior for consumers), architectural violations (e.g., a new dependency breaks layering), breaking changes for external consumers of the public API, and security implications. Leave type errors, missing imports, and broken references to CI.
- Be specific — always reference file and line number
- Explain WHY something is an issue, not just WHAT, but avoid stating the obvious.
- For complex matters, provide concrete fix suggestions, including code snippets when helpful
- Keep it constructive — the goal is to help, not gatekeep
- Don't repeat the same finding for every occurrence — mention it once and note "same pattern in {other files}"
- Focus on substantive issues only. Do NOT flag purely cosmetic or stylistic concerns. Specifically, never flag: code formatting or whitespace, comment grammar or wording, redundant-but-harmless syntax (e.g., optional chaining after a truthiness check), code duplication that doesn't cause bugs, or HTML template cosmetics. The only exception is when a stylistic issue has a concrete impact on performance or rendering. Note: missing JSDoc/documentation on public or exported APIs is a substantive finding (per coding preferences), not a cosmetic one — flag it as a Suggestion.
- For breaking changes, reference the specific pattern from the CLAUDE.md that should be applied
- Do not suggest changes that would themselves introduce breaking changes. If a suggestion would alter public API surface (e.g., changing return types, renaming public members), it is not appropriate for a PR targeting `main` within a major version. Only suggest non-breaking alternatives.
@@ -0,0 +1,97 @@
{
"skill_name": "umb-review",
"evals": [
{
"id": 0,
"name": "pr-22214-large-frontend-refactor",
"prompt": "Review the changes in PR #22214 (branch origin/pr/22214 targeting main). This is a large frontend refactor migrating create entity actions to use entityCreateOptionAction extensions, with deprecations.",
"expected_output": "A structured review that identifies frontend deprecation patterns, flags the large PR complexity, handles 75+ files correctly, checks for breaking changes in exported components, and produces the correct output format.",
"pr_number": 22214,
"pr_branch": "origin/pr/22214",
"base_branch": "origin/main",
"files": [],
"assertions": [
{"id": "deprecation-patterns-noted", "text": "Review identifies deprecation patterns (@deprecated, UmbDeprecation)"},
{"id": "frontend-breaking-change-awareness", "text": "Checks frontend-specific breaking changes (exports, custom elements) not just backend"},
{"id": "file-references-present", "text": "Findings reference specific files with line numbers"},
{"id": "no-false-critical-on-deprecations", "text": "Properly deprecated code is NOT flagged as Critical breaking change"},
{"id": "no-stylistic-nitpicks", "text": "Review does not flag purely cosmetic/stylistic issues (formatting, whitespace, naming conventions, comment grammar, code style preferences) unless they affect performance or rendering. Missing JSDoc on new public APIs is NOT a stylistic issue — it is a legitimate finding."},
{"id": "manifest-alias-rename-detected", "text": "Alias renames (CreateOptions → Create) flagged as Critical breaking change"},
{"id": "non-exported-deletions-dismissed", "text": "Deleted action classes NOT flagged as breaking (verified against package.json exports)"},
{"id": "noise-files-filtered", "text": "Does not review noise files (generated files, lock files, etc.)"},
{"id": "complexity-advisory-triggers", "text": "Review includes a complexity/split advisory for the large 75+ file scope"}
]
},
{
"id": 1,
"name": "pr-21672-small-frontend-bugfix",
"prompt": "Review the changes in PR #21672 (branch origin/pr/21672 targeting main). This is a small 4-file frontend bugfix implementing tab validation badges in the block editor.",
"expected_output": "A clean review that correctly identifies this as a small focused bugfix, avoids false positives, and either approves or approves with minor suggestions.",
"pr_number": 21672,
"pr_branch": "origin/pr/21672",
"base_branch": "origin/main",
"files": [],
"assertions": [
{"id": "complexity-advisory-absent", "text": "Review does NOT include a complexity/split advisory"},
{"id": "no-false-breaking-changes", "text": "Review does not flag breaking changes"},
{"id": "proportionate-verdict", "text": "Verdict is 'Request Changes'"},
{"id": "concise-review", "text": "Review output is under 200 lines"},
{"id": "no-stylistic-nitpicks", "text": "Review does not flag purely cosmetic/stylistic issues (formatting, whitespace, naming conventions, comment grammar, code style preferences) unless they affect performance or rendering. Missing JSDoc on new public APIs is NOT a stylistic issue — it is a legitimate finding."}
]
},
{
"id": 2,
"name": "pr-22217-small-backend-webhook",
"prompt": "Review the changes in PR #22217 (branch origin/pr/22217 targeting v18/dev). This is a tiny 3-file backend change to the default webhook payload type.",
"expected_output": "A concise review that correctly resolves v18/dev as target branch, handles the small change proportionately, and considers the behavioral impact of changing a default value.",
"pr_number": 22217,
"pr_branch": "origin/pr/22217",
"base_branch": "origin/v18/dev",
"files": [],
"assertions": [
{"id": "correct-target-branch", "text": "Review references 'v18/dev' as the target branch (not 'main')"},
{"id": "default-value-change-noted", "text": "Review discusses the behavioral impact of changing the default payload type"},
{"id": "proportionate-review", "text": "Review output is under 150 lines"},
{"id": "no-stylistic-nitpicks", "text": "Review does not flag purely cosmetic/stylistic issues (formatting, whitespace, naming conventions, comment grammar, code style preferences) unless they affect performance or rendering. Missing JSDoc on new public APIs is NOT a stylistic issue — it is a legitimate finding."},
{"id": "ignores-preexisting-issues", "text": "Does NOT flag the ~30 builder extension methods with Legacy defaults (pre-existing, not changed in the PR)"},
{"id": "side-effect-detection", "text": "Flags stale WebhookSettings.cs docs as a side-effect of the constant value change"},
{"id": "consumer-identification", "text": "Identifies affected consumers outside the PR (WebhookSettings, UmbracoBuilder, or WebhookEventCollectionBuilderExtensions)"}
]
},
{
"id": 3,
"name": "pr-22268-frontend-feature-workspace-modal",
"prompt": "Review the changes in PR #22268 (branch origin/pr/22268 targeting main). This is a 29-file frontend feature adding a current user workspace modal.",
"expected_output": "A review of a medium-sized new feature PR. Should assess the new code for architectural compliance, check for breaking changes (new exports, custom elements), and evaluate code quality without flagging pre-existing issues.",
"pr_number": 22268,
"pr_branch": "origin/pr/22268",
"base_branch": "origin/main",
"files": [],
"assertions": [
{"id": "complexity-advisory-triggers", "text": "Review includes a complexity/split advisory (3 layers: Core, API, Frontend across 27+ files)"},
{"id": "breaking-changes-on-interface-additions", "text": "Flags new interface methods without default implementations as breaking changes (Pattern 3)"},
{"id": "no-stylistic-nitpicks", "text": "Review does not flag purely cosmetic/stylistic issues unless they affect performance or rendering. Missing JSDoc on new public APIs is NOT a stylistic issue — it is a legitimate finding."},
{"id": "diff-scoped", "text": "All findings reference code that was changed in the diff, not pre-existing issues"},
{"id": "new-feature-assessed", "text": "Review assesses the new feature's architecture, patterns, or integration approach — not just absence of bugs"},
{"id": "no-false-notification-finding", "text": "Review does NOT flag UpdateCurrentUserAsync as missing UserSavingNotification/UserSavedNotification — the sibling UpdateAsync also does not publish these notifications, so flagging their absence would be a false positive"}
]
},
{
"id": 4,
"name": "pr-22215-frontend-architecture-violation",
"prompt": "Review the changes in PR #22215 (branch origin/pr/22215 targeting main). This is a 2-file frontend feature adding user management to the user group workspace.",
"expected_output": "A review that catches the architecture violation: the workspace context directly imports and calls UserService and UserGroupService (generated API clients) instead of going through a repository. In the Umbraco backoffice, workspace contexts access data via repositories, not by calling API services directly. The review should flag this as a significant architecture issue and request changes.",
"pr_number": 22215,
"pr_branch": "origin/pr/22215",
"base_branch": "origin/main",
"files": [],
"assertions": [
{"id": "service-bypass-detected", "text": "Review flags that the workspace context directly imports/calls UserService or UserGroupService instead of using a repository"},
{"id": "repository-pattern-recommended", "text": "Review recommends using the repository pattern (going through a repository/data-source layer) rather than calling API services directly from the workspace context"},
{"id": "verdict-request-changes", "text": "Verdict is 'Request Changes' (the architecture violation warrants requesting changes, not just approving with suggestions)"},
{"id": "no-stylistic-nitpicks", "text": "Review does not flag purely cosmetic/stylistic issues (formatting, whitespace, naming conventions, comment grammar, code style preferences) unless they affect performance or rendering. Missing JSDoc on new public APIs is NOT a stylistic issue — it is a legitimate finding."},
{"id": "no-false-breaking-changes", "text": "Review does not flag breaking changes (this PR only adds new code, no public API is removed or modified)"}
]
}
]
}
@@ -0,0 +1,249 @@
# Breaking Changes Reference
This document describes how to detect and validate breaking changes during PR review. It covers both backend (.NET) and frontend (TypeScript/Lit) patterns.
---
## Version Detection
**Always read `version.json`** at the repository root to determine the current major version. This drives the obsolete removal target calculation:
- Current major version: read from `version.json``version` field (e.g., `"17.4.0-rc"` → major version `17`)
- Obsolete removal target: `current + 2` (e.g., if current is 17, removal is scheduled for Umbraco 19)
- Format: `[Obsolete("... Scheduled for removal in Umbraco {current+2}.")]`
---
## Backend (.NET) Breaking Changes
### What Constitutes a Breaking Change
Any of these on a `public` or `protected` member:
- Removing or renaming a class, interface, struct, record, or enum
- Removing or renaming a method, property, or field
- Changing a method signature (parameters, return type)
- Adding required parameters to an existing method
- Adding methods to a public interface (without default implementation)
- Changing a constructor signature on a public class
- Removing or changing enum values
- Changing type hierarchy (base class, implemented interfaces)
### Pattern 1: Obsolete Constructor + StaticServiceProvider
When a public class needs new dependencies, the existing constructor must be preserved.
**Correct pattern:**
```csharp
[Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")]
public MyService(IDependencyA depA)
: this(
depA,
StaticServiceProvider.Instance.GetRequiredService<IDependencyB>())
{
}
public MyService(IDependencyA depA, IDependencyB depB)
{
_depA = depA;
_depB = depB;
}
```
**Validation checklist:**
- [ ] Old constructor has `[Obsolete]` attribute with correct removal version
- [ ] Old constructor calls new constructor via `: this(...)`
- [ ] `StaticServiceProvider.Instance.GetRequiredService<T>()` used for new params only
- [ ] DI registration uses the NEW constructor (old is for external consumers only)
- [ ] Removal version is `{current_major + 2}`
**Common mistakes to flag:**
- Removing the old constructor entirely (breaking change!)
- Old constructor NOT calling new constructor (code duplication)
- Wrong removal version in `[Obsolete]`
- Missing `StaticServiceProvider` resolution for new dependencies
- DI registration still using the old constructor
### Pattern 2: Obsolete Method + New Overload
When a method signature needs to change, add the new overload and obsolete the old.
**Correct pattern:**
```csharp
[Obsolete("Use the overload taking all parameters. Scheduled for removal in Umbraco 19.")]
public void DoThing(string name)
=> DoThing(name, extraParam: null);
public void DoThing(string name, string? extraParam)
{
// Real implementation here
}
```
**Validation checklist:**
- [ ] Old method has `[Obsolete]` attribute with correct removal version
- [ ] Old method calls new method, providing defaults for new parameters
- [ ] All internal callers updated to use the new method
- [ ] No internal code references the obsolete method (except the delegation)
### Pattern 3: Default Interface Implementation
When adding methods to a public interface, provide a default implementation.
**Correct pattern:**
```csharp
public interface IMyService
{
void ExistingMethod();
// New method with default implementation
void NewMethod(string param)
=> ExistingMethod(); // delegate to existing if possible
}
```
**Strategies for defaults (in order of preference):**
1. Use existing interface methods to satisfy the contract
2. Return a sensible default (empty collection, null, etc.)
3. Throw `NotImplementedException` if no reasonable default exists
**Validation checklist:**
- [ ] New interface method has a default implementation
- [ ] TODO comment present: `// TODO (V{next-major}): Remove the default implementation when {obsolete method} is removed.`
- [ ] Default implementation is functionally correct (even if not optimal)
- [ ] If `StaticServiceProvider` is used in default impl, noted as temporary
### Obsolete Attribute Validation
For any `[Obsolete]` attribute found in changed code:
1. **Format**: Must contain `"Scheduled for removal in Umbraco {version}."`
2. **Version**: Must be `current_major + 2` (read from `version.json`)
3. **Pragma**: Where obsolete members must call each other, `#pragma warning disable CS0618` / `#pragma warning restore CS0618` must be present
### Internal Caller Check
After finding obsolete patterns, verify:
- Search the codebase for usages of the obsolete member
- **No internal code** (inside `src/`) should reference obsolete members
- Only the obsolete member's own delegation (calling the new version) is acceptable
- External consumers (outside the repo) get the deprecation period to migrate
---
## Frontend (TypeScript/Lit) Breaking Changes
The backoffice is published as `@umbraco-cms/backoffice` with 140+ named exports. Plugin developers depend on this public API surface.
**Critical frontend rule (does not apply to backend .NET where `public`/`protected` visibility determines the API surface): only symbols reachable through the `package.json` `exports` field are public API.** Anything not exported — whether classes, functions, constants, types, or entire files — is an internal implementation detail, even if other internal code imports it. Removing or changing unexported frontend symbols is not a breaking change. Before flagging a frontend deletion or rename as breaking, verify the symbol is reachable via `package.json` exports. If it is not, do not flag it.
### Custom Elements (Web Components)
**Breaking changes:**
- Renaming or removing a registered custom element tag (`umb-*`)
- Removing elements from `HTMLElementTagNameMap`
- Removing or changing `@property()` decorated fields on exported components
- Removing event emissions (checked via `this.dispatchEvent`)
- Removing CSS custom properties (`@cssprop` in JSDoc)
- Removing CSS parts (`@csspart` in JSDoc)
**How to detect:**
- Check diff for removed `@customElement('umb-...')` decorators
- Check diff for removed `@property()` fields on exported components
- Check diff for removed entries in `HTMLElementTagNameMap` declarations
### Exported Types/Interfaces
**Breaking changes:**
- Removing exports from `package.json` `exports` field
- Changing the shape of exported interfaces (removing properties, changing types)
- Renaming exported types (consumers import by name)
- Removing union type members
- Changing generic type parameter constraints
**How to detect:**
- Check if `package.json` `exports` field is modified
- Check diff for removed `export` statements
- Check diff for changed interface/type shapes
### Manifest/Extension System
**Breaking changes:**
- Renaming a manifest `alias` value — plugin developers reference aliases by string in conditions, overwrites, and extension registry lookups. Alias renames are not caught by the compiler since they are string-based. A renamed alias silently breaks any plugin that references the old string.
- Removing support for a manifest `type` that plugins use
- Changing manifest `alias` resolution or validation
- Removing or renaming manifest `kind` types
- Changing extension bundle structure
**How to detect:**
- **Alias renames**: Compare `alias:` values in manifest files before and after. Changed alias strings are Critical — the old alias should be preserved as a deprecated entry.
- Search for changes to manifest type definitions
- Check for removed or renamed manifest kinds
### Context API
**Breaking changes:**
- Removing context tokens from exports
- Changing the shape of data provided by a context
- Removing context provider/consumer mechanisms
**How to detect:**
- Check for removed context token exports
- Check for changes to context provider classes
### Controllers/Lifecycle
**Breaking changes:**
- Changing controller base class inheritance requirements
- Removing controller lifecycle hooks
- Breaking cleanup mechanisms in `disconnectedCallback()`
### Observable/State
**Breaking changes:**
- Removing observable properties from the public API
- Changing observable emission patterns
### npm Publishing
**Breaking changes:**
- Changing version constraints that exclude previously-supported versions
- Adding incompatible peer dependency constraints
**How to detect:**
- Check if `package.json` `peerDependencies` or `dependencies` changed
- Verify version ranges are not narrowed
---
## Reporting Breaking Changes
When a breaking change is detected, report:
1. **What**: The specific change and which public symbol is affected
2. **Pattern**: Which mitigation pattern should be applied (Pattern 1, 2, or 3 for backend)
3. **Severity**: Critical (no mitigation present) or Important (mitigation present but incorrect)
4. **Fix**: Concrete code suggestion showing the correct pattern
If no breaking changes are detected, state: "No breaking changes detected."
@@ -0,0 +1,168 @@
# Coding Preferences & Review Criteria
These are the coding preferences and code review standards used by the review skill. They define what the review evaluates against.
---
## Testing
- **Always create blackbox tests** for new/changed code
- Choose the appropriate test level:
- **Unit tests** for isolated logic
- **Integration tests** for application services/use cases
- **E2E tests** for API endpoints
### Test Class Naming
- Test classes must be postfixed with `Tests` (e.g., `OrderServiceTests`)
- One test class per class under test
### Test Method Naming
**C# tests**: Use the `Can_`/`Cannot_` pattern with PascalCase underscore-separated words:
- `Can_Schedule_Publish_Invariant`
- `Cannot_Delete_Non_Existing`
- `Can_Schedule_Publish_Single_Culture`
Large test classes are split into partial files by method: `ContentServiceTests.Delete.cs`, `ContentServiceTests.Publish.cs`.
**TypeScript tests**: Use BDD-style `it()` with natural language descriptions:
- `it('should not allow the returned value to be lower than min')`
- `it('converts string to camelCase')`
### Unit Tests
- Optional, but must be blackbox tests so refactoring does not break tests
### Integration Tests
- Every use case / application service must have integration tests
- Tests run against real database (containerized or similar)
- Test the full flow from application layer through infrastructure
### E2E Tests
- Every API endpoint must have E2E tests
- Test realistic scenarios including error cases
---
## Trade-offs
When making decisions, prioritize:
- **Readability** over cleverness
- **Flexibility** over rigidity
- Explain trade-offs when deviating from these defaults
---
## Breaking Changes
- Communicate breaking changes at the **OpenAPI/openapi.json level**
- Clearly document what changed and the migration path
---
## Documentation
- **Document all public or exported types** (classes, interfaces, types, methods, properties)
- Keep documentation in sync with code changes
- Add **JS Docs** on all public frontend APIs (classes, methods, properties)
- Focus on "why" and usage, not restating the obvious
---
## Dependencies
- Use what's available in the codebase, unless there is no good choice
- **Flag new dependencies** for review — new packages should be justified
- Prefer well-maintained, widely-used packages
---
## Error Messages & Logging
- **User-facing errors**: Clear, friendly, actionable
- **Log messages**: Technical, detailed, with context
- Include correlation IDs and relevant data in logs
---
## Security
- **Always check for security issues** using OWASP Top 10 as baseline
- Flag potential vulnerabilities immediately
- Suggest secure alternatives when spotting risky patterns
- Apply principle of least privilege
---
## Immutability
- Prefer **immutability** by default
- Allow internal properties to be mutated, as long as they are not direct references coming from the outside
---
## Nullability
- **TypeScript / JavaScript**
- Prefer `undefined` for optional/omitted values (e.g., optional parameters, props, and fields)
- Use `null` only when the domain model explicitly encodes "no value" or "not set" (e.g., `string | null` from APIs/DB), and be consistent with existing types
- Avoid mixing `null` and `undefined` for the same concept within the same model or API surface
- **C#**
- use nullable types (e.g., `string?`, `int?`) where absence is valid
- Prefer domain modeling (value objects, options/results, empty collections) over `null` where appropriate, but respect existing conventions in the codebase
---
## C# Specific
- use Notification pattern (not C# events), Composer pattern (DI registration), Scoping with `Complete()`, Attempt pattern for operation results.
---
## Architecture
- Follow **Clean Architecture** principles
- **Fail-fast** principle: detect and report errors as early as possible
- Within the established layered architecture (Core/Infrastructure/Web/API), organize code by feature inside each layer where practical, while preserving dependency direction
- One class per file
- Avoid N+1 queries
- Profile before optimizing non-critical paths
### Type Hierarchy Consistency
When parallel model types have inconsistent relationships to a shared base type:
**TypeScript**: manipulations via `Omit`, `Pick`, intersection overrides, or workarounds like `as unknown as` / double-casts to bridge type mismatches.
**C#**: hiding base members with `new` to change types, explicit interface implementations to mask mismatches, or downcasting base return types in derived classes.
- **Do NOT suggest** the PR code should deviate from its base type to match a sibling that already deviates. Copying the deviation spreads the problem.
- **Do flag** the architectural inconsistency: parallel models should share a compatible base contract. The model that manipulates or deviates from the base type is the one that needs attention — not the one that extends it correctly.
- **Frame the suggestion** as: "These related models have inconsistent type hierarchies. `{deviating type}` manipulates the base contract of `{base type}`, which forces shared consumers like `{shared utility}` to require a shape that conforming subtypes can't satisfy."
---
## Code Style
- Follow standard naming conventions for the language (C# or JS/TS)
- Keep components small and focused on a single responsibility
- Prefer early returns
- Small functions
- No nested ternaries
---
## Severity Levels
| Severity | Meaning |
|----------|---------|
| **Critical** | Must fix before merge — security vulnerabilities, data loss risks, broken functionality |
| **Important** | Should fix — performance issues, missing tests, architectural violations |
| **Suggestion** | Nice to have — readability, minor refactoring, alternative approaches |
@@ -0,0 +1,33 @@
# PR Complexity Assessment
Evaluate whether the PR's scope suggests it should be split. This assessment is **informational only** — it never blocks or shortens the review.
## Always check: Formatting mixed with logic
This check applies to every PR regardless of size or scope.
Run both commands and compare per-file line counts:
```bash
git diff {target}...HEAD --stat
git diff {target}...HEAD --stat --ignore-all-space
```
For any file where the whitespace-ignored diff is less than **half** the full diff size (and the full diff is over 50 lines), that file has significant formatting changes mixed with logic. Flag it with a split suggestion: "File(s) {list} contain significant formatting changes mixed with logic. Consider a separate formatting-only commit or PR to keep the functional diff reviewable."
## Multi-project scope check
Skip this section entirely if ALL production files reside in a single project directory or if the PR is docs-only, test-only, dependency-bump-only, or rename-only.
Otherwise, flag any dimension that applies:
| Dimension | Condition | Suggestion |
|---|---|---|
| **Size** | 30+ files OR 1500+ lines, spanning 2+ projects | "If changes in {projectA} and {projectB} are independently functional, they could be separate PRs." |
| **Layer spread** | 3+ layers touched (Core/Infrastructure/Web/API/Frontend), 10+ files | "Consider splitting by layer — e.g., Core+Infrastructure first, then API/Frontend consumers." |
| **Mixed intent** | 2+ intent categories (new feature, bugfix, refactor, dependency update) with 15+ files or 3+ projects | "Consider extracting the {secondary intent} into a separate PR." |
Intent categories — detect from diff characteristics, not commit messages:
- **New feature**: new files or new `public`/`export` declarations
- **Bug fix**: small targeted edits, no new files (don't co-flag with new feature)
- **Refactor**: file renames, symbols moved but logic unchanged
- **Dependency update**: changes to `.csproj`, `Directory.Packages.props`, `package.json`
@@ -0,0 +1,23 @@
# GH CLI Setup Instructions
The GitHub CLI (`gh`) is required for this review skill to detect PR target branches.
## Installation
Install via Homebrew:
```
brew install gh
```
Or see https://cli.github.com/ for other installation methods.
## Authentication
After installing, authorize by running this in the terminal (use the `!` prefix in Claude Code):
```
! gh auth login
```
Follow the prompts to authenticate with your GitHub account.
@@ -0,0 +1,153 @@
# Impact Analysis Reference
This document describes how to perform impact analysis during PR review. The goal is to look beyond the diff to understand how changes affect consumers in other parts of the codebase.
---
## 1. Extract Changed Public Symbols
Scan the diff output for changes to public API surface:
### Backend (.NET)
Look for added, modified, or removed lines containing:
- `public class`, `public abstract class`, `public sealed class`
- `public interface`
- `public record`, `public struct`, `public enum`
- `public` or `protected` methods, properties, fields
- `public static` members
- Constructor signatures on public types
### Frontend (TypeScript/Lit)
Look for changes to:
- `export class`, `export interface`, `export type`, `export enum`
- `export function`, `export const`
- `@property()` decorated fields on exported components
- `@customElement()` registrations
- Entries in `package.json` `exports` field
Collect a list of all changed public symbol names (type names, method names, property names).
---
## 2. Search for Consumers
For each changed public symbol, search the `src/` directory for usages **outside the changed file itself**.
### Grep Strategy
Use the Grep tool with these settings:
```
pattern: {symbol name}
path: src/
output_mode: files_with_matches
head_limit: 20
```
Use `head_limit: 20` to avoid overwhelming results — if there are more than 20 consumers, note "20+ consumers found" and list the first 20.
### What to Search For
For each changed type/method, search for:
- **Type references**: class name, interface name (e.g., `IContentService`)
- **Method calls**: method name in context (e.g., `\.GetById\(` for a method rename)
- **Constructor usage**: `new TypeName(`
- **DI registrations**: `.AddSingleton<IType, Type>`, `.AddScoped<`, `.AddTransient<`
- **Notification handlers**: if a notification type changed, search for `INotificationHandler<NotificationTypeName>` and `INotificationAsyncHandler<NotificationTypeName>`
- **Interface implementations**: if an interface changed, search for `: IInterfaceName` or `IInterfaceName,`
### Excluding the Changed File
When reporting consumers, exclude files that are part of the PR's changes (they're already being reviewed). The interesting consumers are those **outside** the PR that may be affected.
---
## 3. Check Dependency Flow Direction
The Umbraco architecture enforces strict unidirectional dependencies:
```
Api.Management / Api.Delivery (depend on Api.Common)
Api.Common (depends on Web.Common)
Web.Common (depends on Infrastructure)
Infrastructure (depends on Core)
Core (no dependencies)
```
### Layer Mapping
Map each changed file to its architectural layer:
| Path prefix | Layer |
|---|---|
| `src/Umbraco.Core/` | Core |
| `src/Umbraco.Infrastructure/` | Infrastructure |
| `src/Umbraco.PublishedCache.*` | Infrastructure |
| `src/Umbraco.Examine.Lucene/` | Infrastructure |
| `src/Umbraco.Cms.Persistence.*` | Infrastructure |
| `src/Umbraco.Web.Common/` | Web |
| `src/Umbraco.Web.UI/` | Web (Application) |
| `src/Umbraco.Web.Website/` | Web |
| `src/Umbraco.Cms.Api.Common/` | API |
| `src/Umbraco.Cms.Api.Management/` | API |
| `src/Umbraco.Cms.Api.Delivery/` | API |
| `src/Umbraco.Web.UI.Client/` | Frontend |
| `tests/` | Test |
### Violation Detection
Flag if a change introduces:
- **Core depending on Infrastructure**: Core file importing/referencing Infrastructure types
- **Core depending on Web/API**: Core file importing/referencing Web or API types
- **Infrastructure depending on Web/API**: Infrastructure file importing Web or API types
- **Cross-API dependencies**: Management API depending on Delivery API or vice versa
### How to Check
1. For each changed file, identify its layer
2. Read the file's `using` statements (C#) or `import` statements (TS)
3. Check if any imports reference a higher layer
4. Also check if new parameters or return types come from higher layers
---
## 4. Flag Cross-Project Risks
### High-Risk Patterns
These changes have high ripple potential:
- **Interface changes in Core** — all implementations in Infrastructure must be updated
- **Notification type changes** — all handlers across the codebase are affected
- **Base class changes** — all derived classes are affected
- **Composer changes** — can affect DI container and runtime behavior globally
- **Shared model/DTO changes** — can affect serialization, API contracts, and consumers
### What to Report
For each cross-project risk found, report:
1. **What changed**: The specific symbol and how it changed
2. **Who is affected**: List of consuming files/projects found via Grep
3. **Risk level**: Whether the consumers will break (compile error), behave differently (runtime), or are unaffected
4. **Recommendation**: Whether the PR should include updates to affected consumers
---
## 5. Performance Notes
- Use `head_limit: 20` on all Grep searches to cap results
- Only search for symbols that actually changed (not every symbol in the file)
- For very common type names (e.g., `IScope`, `ILogger`), consider adding more context to the search pattern to reduce false positives
- Skip impact analysis for test files — they don't have external consumers
- Skip impact analysis for private/internal members — they can't have external consumers
+1 -218
View File
@@ -1,218 +1 @@
# Umbraco CMS Development Guide
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
## Working Effectively
Bootstrap, build, and test the repository:
- Install .NET SDK (version specified in global.json):
- `curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version $(jq -r '.sdk.version' global.json)`
- `export PATH="/home/runner/.dotnet:$PATH"`
- Install Node.js (version specified in src/Umbraco.Web.UI.Client/.nvmrc):
- `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash`
- `export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`
- `nvm install $(cat src/Umbraco.Web.UI.Client/.nvmrc) && nvm use $(cat src/Umbraco.Web.UI.Client/.nvmrc)`
- Fix shallow clone issue (required for GitVersioning):
- `git fetch --unshallow`
- Restore packages:
- `dotnet restore` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
- Build the solution:
- `dotnet build` -- takes 4.5 minutes. NEVER CANCEL. Set timeout to 10+ minutes.
- Install and build frontend:
- `cd src/Umbraco.Web.UI.Client`
- `npm ci --no-fund --no-audit --prefer-offline` -- takes 11 seconds.
- `npm run build:for:cms` -- takes 1.25 minutes. NEVER CANCEL. Set timeout to 5+ minutes.
- Install and build Login
- `cd src/Umbraco.Web.UI.Login`
- `npm ci --no-fund --no-audit --prefer-offline`
- `npm run build`
- Run the application:
- `cd src/Umbraco.Web.UI`
- `dotnet run --no-build` -- Application runs on https://localhost:44339 and http://localhost:11000
Check out [BUILD.md](./BUILD.md) for more detailed instructions.
## Validation
- ALWAYS run through at least one complete end-to-end scenario after making changes.
- Build and unit tests must pass before committing changes.
- Frontend build produces output in src/Umbraco.Web.UI.Client/dist-cms/ which gets copied to src/Umbraco.Web.UI/wwwroot/umbraco/backoffice/
- Always run `dotnet build` and `npm run build:for:cms` before running the application to see your changes.
- For login-only changes, you can run `npm run build` from src/Umbraco.Web.UI.Login and then `dotnet run --no-build` from src/Umbraco.Web.UI.
- For frontend-only changes, you can run `npm run dev:server` from src/Umbraco.Web.UI.Client for hot reloading.
- Frontend changes should be linted using `npm run lint:fix` which uses Eslint.
## Testing
### Unit Tests (.NET)
- Location: tests/Umbraco.Tests.UnitTests/
- Run: `dotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj --configuration Release --verbosity minimal`
- Duration: ~1 minute with 3,343 tests
- NEVER CANCEL: Set timeout to 5+ minutes
### Integration Tests (.NET)
- Location: tests/Umbraco.Tests.Integration/
- Run: `dotnet test tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj --configuration Release --verbosity minimal`
- NEVER CANCEL: Set timeout to 10+ minutes
### Frontend Tests
- Location: src/Umbraco.Web.UI.Client/
- Run: `npm test` (requires `npx playwright install` first)
- Frontend tests use Web Test Runner with Playwright
### Acceptance Tests (E2E)
- Location: tests/Umbraco.Tests.AcceptanceTest/
- Requires running Umbraco application and configuration
- See tests/Umbraco.Tests.AcceptanceTest/README.md for detailed setup (requires `npx playwright install` first)
## Project Structure
The solution contains 30 C# projects organized as follows:
### Main Application Projects
- **Umbraco.Web.UI**: Main web application project (startup project)
- **Umbraco.Web.UI.Client**: TypeScript frontend (backoffice)
- **Umbraco.Web.UI.Login**: Separate login screen frontend
- **Umbraco.Core**: Core domain models and interfaces
- **Umbraco.Infrastructure**: Data access and infrastructure
- **Umbraco.Cms**: Main CMS package
### API Projects
- **Umbraco.Cms.Api.Management**: Management API
- **Umbraco.Cms.Api.Delivery**: Content Delivery API
- **Umbraco.Cms.Api.Common**: Shared API components
### Persistence Projects
- **Umbraco.Cms.Persistence.SqlServer**: SQL Server support
- **Umbraco.Cms.Persistence.Sqlite**: SQLite support
- **Umbraco.Cms.Persistence.EFCore**: Entity Framework Core abstractions
### Test Projects
- **Umbraco.Tests.UnitTests**: Unit tests
- **Umbraco.Tests.Integration**: Integration tests
- **Umbraco.Tests.AcceptanceTest**: End-to-end tests with Playwright
- **Umbraco.Tests.Common**: Shared test utilities
## Common Tasks
### Running Umbraco in Different Modes
**Production Mode (Standard Development)**
Use this for backend development, testing full builds, or when you don't need hot reloading:
1. Build frontend assets: `cd src/Umbraco.Web.UI.Client && npm run build:for:cms`
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
3. Access backoffice: `https://localhost:44339/umbraco`
4. Application uses compiled frontend from `wwwroot/umbraco/backoffice/`
**Vite Dev Server Mode (Frontend Development with Hot Reload)**
Use this for frontend-only development with hot module reloading:
1. Configure backend for frontend development - Add to `src/Umbraco.Web.UI/appsettings.json` under `Umbraco:CMS:Security`:
```json
"BackOfficeHost": "http://localhost:5173",
"AuthorizeCallbackPathName": "/oauth_complete",
"AuthorizeCallbackLogoutPathName": "/logout",
"AuthorizeCallbackErrorPathName": "/error",
"BackOfficeTokenCookie": {
"SameSite": "None"
}
```
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
3. Run frontend dev server: `cd src/Umbraco.Web.UI.Client && npm run dev:server`
4. Access backoffice: `http://localhost:5173/` (no `/umbraco` prefix)
5. Changes to TypeScript/Lit files hot reload automatically
**Important:** Remove the `BackOfficeHost` configuration before committing or switching back to production mode.
### Backend-Only Development
For backend-only changes, disable frontend builds:
- Comment out the target named "BuildStaticAssetsPreconditions" in src/Umbraco.Cms.StaticAssets.csproj:
```
<!--<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
[...]
</Target>-->
```
- Remember to uncomment before committing
### Building NuGet Packages
To build custom NuGet packages for testing:
```bash
dotnet pack -c Release -o Build.Out
dotnet nuget add source [Path to Build.Out folder] -n MyLocalFeed
```
### Regenerating Frontend API Types
When changing Management API:
```bash
cd src/Umbraco.Web.UI.Client
npm run generate:server-api-dev
```
Also update OpenApi.json from /umbraco/swagger/management/swagger.json
## Database Setup
Default configuration supports SQLite for development. For production-like testing:
- Use SQL Server/LocalDb for better performance
- Configure connection string in src/Umbraco.Web.UI/appsettings.json
## Clean Up / Reset
To reset development environment:
```bash
# Remove configuration and database
rm src/Umbraco.Web.UI/appsettings.json
rm -rf src/Umbraco.Web.UI/umbraco/Data
# Full clean (removes all untracked files)
git clean -xdf .
```
## Version Information
- Target Framework: .NET (version specified in global.json)
- Current Version: (specified in version.json)
- Node.js Requirement: (specified in src/Umbraco.Web.UI.Client/.nvmrc)
- npm Requirement: Latest compatible version
## Known Issues
- Build requires full git history (not shallow clone) due to GitVersioning
- Some NuGet package security warnings are expected (SixLabors.ImageSharp vulnerabilities)
- Frontend tests require Playwright browser installation: `npx playwright install`
- Older Node.js versions may show engine compatibility warnings (check .nvmrc for current requirement)
## Timing Expectations
**NEVER CANCEL** these operations - they are expected to take time:
| Operation | Expected Time | Timeout Setting |
| ----------------------- | ------------- | --------------- |
| `dotnet restore` | 50 seconds | 90+ seconds |
| `dotnet build` | 4.5 minutes | 10+ minutes |
| `npm ci` | 11 seconds | 30+ seconds |
| `npm run build:for:cms` | 1.25 minutes | 5+ minutes |
| `npm test` | 2 minutes | 5+ minutes |
| `npm run lint` | 1 minute | 5+ minutes |
| Unit tests | 1 minute | 5+ minutes |
| Integration tests | Variable | 10+ minutes |
Always wait for commands to complete rather than canceling and retrying.
The full development guide for this repository lives in [CLAUDE.md](../CLAUDE.md). Please read that file for complete instructions on architecture, build steps, testing, branching conventions, and coding patterns.
-4
View File
@@ -4,9 +4,7 @@ on:
push:
branches:
- main
- release/*
- v*/dev
- v*/main
paths:
- src/Umbraco.Web.UI.Client/package.json
- src/Umbraco.Web.UI.Client/package-lock.json
@@ -16,9 +14,7 @@ on:
types: [opened, synchronize, reopened, closed]
branches:
- main
- release/*
- v*/dev
- v*/main
workflow_dispatch:
jobs:
-2
View File
@@ -5,7 +5,6 @@ on:
branches:
- main
- v*/dev
- v*/main
paths:
- src/Umbraco.Web.UI.Client/package.json
- src/Umbraco.Web.UI.Client/package-lock.json
@@ -16,7 +15,6 @@ on:
branches:
- main
- v*/dev
- v*/main
workflow_dispatch:
env:
+64
View File
@@ -0,0 +1,64 @@
name: Claude PR Review
on:
pull_request_target:
types: [opened, ready_for_review]
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
jobs:
review:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_03 }}
base_branch: "main"
additional_permissions: "actions: read"
claude_args: "--model claude-sonnet-4-6 --allowedTools 'Bash(gh:*),Bash(git:*)'"
prompt: |
You are reviewing pull request #${{ github.event.pull_request.number }}
in the Umbraco CMS repository.
Read and execute the review procedure defined in `.claude/skills/umb-review/SKILL.md`.
For each finding that references a specific file and line:
- Post an individual inline PR comment on that line.
- Format: **[Severity]** explanation, then suggestion.
For the overall summary (header, impact, verdict):
- Post ONE top-level PR comment.
Do NOT use sticky/updating comments — post new individual comments.
After reviewing, apply labels to the PR based on changed files:
- `area/frontend` — if files under `src/Umbraco.Web.UI.Client/` are changed
- `area/backend` — if .cs files outside the frontend client are changed
- `area/test` — if only test files are changed
- `category/api` — if Management API or Delivery API files are changed
- `category/breaking` — if breaking changes were detected in the review
- `category/localization` — if localization/language files are changed
- `category/test-automation` — if only test files are changed
- `category/refactor` — if the PR is pure refactoring with no new features
- `category/performance` — if performance-related changes are detected
- `category/ux` — if user-facing changes are detected
- `category/ui` — if changes to the UI layer are detected
Only apply labels you are confident about. Never remove existing labels.
Be friendly and constructive. This project values community contributions.
Frame feedback as suggestions where possible.
Reserve firm language for genuine Critical issues only.
Run fully autonomously. Do NOT ask questions.
Only review changed files. Do not flag pre-existing issues.
Do not suggest changes that would themselves introduce breaking changes.
+91
View File
@@ -0,0 +1,91 @@
name: Claude
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned, labeled]
pull_request_review:
types: [submitted]
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_03 }}
assignee_trigger: "claude"
label_trigger: "claude"
base_branch: "main"
additional_permissions: "actions: read"
claude_args: "--model claude-sonnet-4-6 --max-turns 50 --allowedTools 'Bash(gh:*),Bash(git:*),Bash(npm:*),Bash(dotnet:*)'"
prompt: |
You are an AI assistant for the Umbraco CMS repository, an open-source
.NET CMS that welcomes community contributions.
You were triggered on issue/PR #${{ github.event.issue.number || github.event.pull_request.number }}.
Read the user's message and do what they ask. The trigger phrase
`@claude` is stripped before you see the message, so common requests
will look like:
- `review` — Review PR #${{ github.event.issue.number || github.event.pull_request.number }}.
Use `gh pr diff ${{ github.event.issue.number || github.event.pull_request.number }}`
and `gh pr view ${{ github.event.issue.number || github.event.pull_request.number }}`
to read the changes. Do NOT use git diff or the umb-review skill.
Focus on bugs, breaking changes, and architectural concerns.
Post inline comments for specific issues and a brief summary.
- `help` or a general question — Answer based on the codebase.
Read CLAUDE.md files for project structure and conventions.
- `fix ...` — Implement the requested fix on a new branch.
- `label` — Apply appropriate labels to the PR or issue.
If the message is empty or just whitespace, treat it as `review`
when on a PR, or `help` when on an issue.
If none of these match, read the user's message carefully and respond
to what they actually asked for.
## Labeling
When labeling PRs (based on changed files):
- `area/frontend`, `area/backend`, `area/test`
- `category/api`, `category/breaking`, `category/localization`
- `category/refactor`, `category/performance`, `category/ux`, `category/ui`
- `category/test-automation`
When labeling issues (based on content):
- `area/frontend`, `area/backend`, `area/test`
- `affected/v14` through `affected/v17`, `affected/backoffice`
- `category/api`, `category/localization`, `category/performance`
- `category/ux`, `category/ui`
Only apply labels you are confident about. Never remove existing labels.
## Tone
Be friendly and constructive. Frame feedback as suggestions.
Reserve firm language for genuine critical issues only.
## Constraints
- Run fully autonomously. Do NOT ask questions.
- Do not suggest changes that would introduce breaking changes.
+1
View File
@@ -103,6 +103,7 @@ tools/docfx/
playwright-report
trace.zip
/tests/Umbraco.Tests.AcceptanceTest/results
/tests/Umbraco.Tests.AcceptanceTest/dist
# Ignore auto-generated schema
/src/Umbraco.Cms.Targets/tasks/
+88 -55
View File
@@ -198,7 +198,7 @@ Use the format: `Area: Description (closes #IssueID)`
- Describe the change and its impact
- Be specific, not vague (describe "a golden retriever" not just "a dog")
**Issue Linking**: Add `(closes #IssueID)` to auto-close linked issues on merge.
**Issue Linking**: Add `(closes #IssueID)` to the title for readability, AND include a closing keyword on its own line in the PR body (e.g., `Fixes #IssueID`) so GitHub actually auto-links and auto-closes the issue on merge. GitHub only parses closing keywords (`closes`, `fixes`, `resolves`) from the PR body or commit messages — the title suffix is cosmetic and does **not** trigger auto-close on its own.
### Commit Messages
@@ -393,13 +393,18 @@ The repository contains BOTH (actively supported):
All APIs use **OpenIddict** (OAuth 2.0/OpenID Connect):
- Reference tokens (not JWT) for better security
- **Secure cookie-based token storage** (v17+) - tokens stored in HTTP-only cookies with `__Host-` prefix
- Tokens are redacted from client-side responses and passed via secure cookies only
- Tokens are redacted from client-side responses and passed via secure cookies only (`[redacted]` placeholder)
- ASP.NET Core Data Protection for token encryption
- Configured in `Umbraco.Cms.Api.Common`
- API requests must include credentials (`credentials: include` for fetch)
**Load Balancing Requirement**: All servers must share the same Data Protection key ring.
**Frontend auth pitfalls** — see `src/Umbraco.Web.UI.Client/docs/edge-cases.md` (Auth & Cross-tab section) and `docs/security.md`. Key points:
- Never call `validateToken()` per API request — it revokes the previous reference token (ID2019 errors)
- `window.opener` is set for ANY `window.open()` target, not only OAuth popups — scope guards to the pathname too
- BroadcastChannel does not deliver messages to the sender's own tab
### Content Caching Strategy
**HybridCache** (`Umbraco.PublishedCache.HybridCache`):
@@ -414,64 +419,19 @@ APIs use `Asp.Versioning.Mvc`:
- Delivery API: `/umbraco/delivery/api/v{version}/*`
- OpenAPI/Swagger docs per version
### Backoffice npm Package Structure
### Updating `OpenApi.json` (Management API)
The backoffice (`Umbraco.Web.UI.Client`) is published to npm as **`@umbraco-cms/backoffice`** with a plugin architecture:
When a PR changes Management API controllers or models, the `OpenApi.json` file in the Management API project must be updated:
#### Architecture Overview
1. Run the Umbraco instance locally
2. Open Swagger UI and navigate to the swagger.json link (e.g. `https://localhost:44339/umbraco/swagger/management/swagger.json`)
3. Copy the full JSON content and paste it into `src/Umbraco.Cms.Api.Management/OpenApi.json`
- **Multi-workspace structure**: Subprojects in `src/libs/*`, `src/packages/*`, `src/external/*`
- **Export model**: All exports defined in root `package.json` → `./exports` field
- **Importmap-driven runtime**: Dependencies provided at runtime via importmap (single source of truth)
- **Build-time types**: TypeScript types come from npm peerDependencies
- **Plugin model**: Developers create plugins that import from `@umbraco-cms/backoffice/*` exports
**Important**: Commit only the substantive changes — not IDE-applied formatting (whitespace, reordering, etc.). Extraneous formatting diffs make PRs harder to review and merge-ups more error-prone.
#### Dependency Hoisting Strategy
### Backoffice npm Package
When building for npm (`npm pack`), the `cleanse-pkg.js` script hoists subproject dependencies to root `peerDependencies` with intelligent version range conversion:
**Version Range Logic** (uses `semver` package):
1. **Pre-release (0.x.y)**: Convert to explicit range
- Input: `^0.85.0` or `0.85.0`
- Output: `>=0.85.0 <1.0.0`
- Rationale: Pre-release caret only allows patch updates, explicit range allows minor upgrades within 0.x.x
- Example: Plugin can use `@hey-api/openapi-ts@0.91.1` while backoffice uses `0.85.0`
2. **Stable with caret (^X.Y.Z where X ≥ 1)**: Keep as-is
- Input: `^3.3.1`
- Output: `^3.3.1` (unchanged)
- Rationale: Caret already implements correct semantics for stable versions
3. **Stable exact versions (X.Y.Z where X ≥ 1)**: Add caret
- Input: `3.16.0`
- Output: `^3.16.0`
- Rationale: Normalizes to conventional semver format
#### Key Dependencies
**Runtime via importmap** (types available from peerDependencies):
- `lit`, `rxjs`, `@umbraco-ui/uui` - Core framework
- `monaco-editor`, `@tiptap/*` - Feature-specific editors
- `@hey-api/openapi-ts` - HTTP client type generation
**Build-time only** (not hoisted):
- `vite`, `typescript`, `eslint` - Dev tooling
#### Plugin Development Implications
Plugin developers should:
- **Declare explicit dependencies** in their own `package.json` (avoid relying on transitive deps)
- **Understand the version ranges**: `>=0.85.0 <1.0.0` means they can use newer pre-release versions
- **Know that types match npm ranges**, but runtime comes from importmap (managed by backoffice)
- **When `@hey-api` hits 1.0.0**: Published constraint will automatically become `^1.0.0`
#### Implementation Details
- Script location: `src/Umbraco.Web.UI.Client/devops/publish/cleanse-pkg.js`
- Runs as `prepack` hook before npm pack
- Uses `semver.minVersion()` for robust version range parsing
- Generates single source of truth for importmap versions
The backoffice is published to npm as `@umbraco-cms/backoffice`. Runtime dependencies are provided via importmap; npm peerDependencies provide types only. For full details on dependency hoisting, version range logic, and plugin development, see `/src/Umbraco.Web.UI.Client/CLAUDE.md` → "npm Package Publishing".
### Known Limitations
@@ -481,6 +441,68 @@ Plugin developers should:
---
## 7. CI/CD — Claude AI Assistant
Two GitHub Actions workflows powered by `anthropics/claude-code-action@v1`. Advisory only — does not block merging.
### Workflows
| File | Trigger | Purpose |
|------|---------|---------|
| `claude-review.yml` | `pull_request: [opened, ready_for_review]` | Auto-review every non-draft PR using the `umb-review` skill |
| `claude.yml` | `@claude` comments, issue assign/label | Interactive assistant for PRs and issues |
### Auto-Review (`claude-review.yml`)
Runs the full `.claude/skills/umb-review/SKILL.md` procedure on every newly opened or un-drafted PR. Produces inline comments per finding and one summary comment with a verdict. Skips draft PRs. No turn limit.
### Interactive (`claude.yml`)
Responds to `@claude` mentions on PRs and issues. The trigger phrase is stripped before Claude sees the message, so:
- `@claude review` → light review using `gh pr diff` (not the umb-review skill)
- `@claude fix ...` → implements a fix on a new branch
- `@claude help` → answers questions about the codebase
- `@claude label` → applies labels
- `@claude` (empty) → defaults to `review` on PRs, `help` on issues
Also triggers on issue assignment to `claude` or adding the `claude` label. Gated: only runs when `@claude` appears in the comment/issue body. Max 25 turns.
**Allowed Bash tools**: `gh`, `git`, `npm`, `dotnet` (interactive only; auto-review allows `gh` and `git`).
### Labels
Both workflows apply labels based on content:
**On PRs** (based on changed files):
| Label | Condition |
|-------|-----------|
| `area/frontend` | Files under `src/Umbraco.Web.UI.Client/` |
| `area/backend` | `.cs` files outside the frontend client |
| `area/test` | Only test files changed |
| `category/api` | Management or Delivery API files |
| `category/breaking` | Breaking changes detected |
| `category/localization` | Localization/language files |
| `category/test-automation` | Only test files changed |
| `category/refactor` | Pure refactoring, no new features |
| `category/performance` | Performance-related changes |
| `category/ux` | User-facing changes |
| `category/ui` | UI layer changes |
**On Issues** (based on content): same `area/*` and `category/*` labels, plus `affected/v14` through `affected/v17` and `affected/backoffice`.
Labels are only added, never removed. Claude applies only labels it is confident about.
### Key Implementation Notes
- **Checkout required** — the action internally runs `git fetch origin main` for trusted file restoration. Without `actions/checkout`, it fails with `fatal: not a git repository`.
- **`id-token: write` permission** — required for OIDC token exchange with the Claude GitHub App.
- **Trigger phrase stripping** — the action strips `@claude` from comments before passing to Claude. Prompts must reference commands without the prefix (e.g., `review` not `@claude review`).
- **PR number injection** — the interactive workflow injects the PR/issue number into the prompt via `${{ github.event.issue.number }}` since Claude can't discover it from `gh pr view` when checked out on `main`.
---
## Quick Reference
### Essential Commands
@@ -502,6 +524,16 @@ dotnet format
dotnet pack -c Release
```
### Integration Test Database Configuration
Integration tests are configured in `tests/Umbraco.Tests.Integration/appsettings.Tests.json`.
The `Tests:Database:DatabaseType` setting controls which database is used:
- `"SQLite"` (default) - No external dependencies
- `"LocalDb"` - Uses SQL Server LocalDB, required for SQL Server-specific tests (e.g., page-level locking, `sys.dm_tran_locks`)
SQL Server-specific tests use `BaseTestDatabase.IsSqlite()` to skip when running on SQLite.
### Key Projects
| Project | Type | Description |
@@ -527,6 +559,7 @@ dotnet pack -c Release
For detailed information about individual projects, see their CLAUDE.md files:
- **Core Architecture**: `/src/Umbraco.Core/CLAUDE.md` - Service contracts, notification patterns
- **API Infrastructure**: `/src/Umbraco.Cms.Api.Common/CLAUDE.md` - OpenAPI, authentication, serialization
- **Backoffice Frontend**: `/src/Umbraco.Web.UI.Client/CLAUDE.md` - Lit web components, extension system, auth client
### Getting Help
+28 -27
View File
@@ -13,27 +13,27 @@
</ItemGroup>
<!-- Microsoft packages -->
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.2.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.4.0" />
<PackageVersion Include="System.Linq.Async" Version="7.0.0" />
</ItemGroup>
<!-- Umbraco packages -->
@@ -50,19 +50,19 @@
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="MailKit" Version="4.14.1" />
<PackageVersion Include="Markdig" Version="0.44.0" />
<PackageVersion Include="MailKit" Version="4.15.1" />
<PackageVersion Include="Markdig" Version="0.45.0" />
<PackageVersion Include="Markdown" Version="2.2.1" />
<PackageVersion Include="MessagePack" Version="3.1.4" />
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
<PackageVersion Include="MiniProfiler.Shared" Version="4.5.4" />
<PackageVersion Include="ncrontab" Version="3.4.0" />
<PackageVersion Include="NPoco" Version="6.1.0" />
<PackageVersion Include="NPoco.SqlServer" Version="6.1.0" />
<PackageVersion Include="NPoco" Version="6.2.0" />
<PackageVersion Include="NPoco.SqlServer" Version="6.2.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="7.2.0" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="7.2.0" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="7.2.0" />
<PackageVersion Include="Serilog" Version="4.3.0" />
<PackageVersion Include="Serilog" Version="4.3.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
@@ -76,7 +76,8 @@
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.0" />
<!-- When updating this version, also update templates/UmbracoExtension/Umbraco.Extension.csproj -->
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.4" />
</ItemGroup>
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
<ItemGroup>
@@ -88,4 +89,4 @@
<!-- TODO (V19): Remove these pinned dependencies when the Markdown dependency is removed. -->
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
</Project>
</Project>
+93 -2
View File
@@ -117,7 +117,7 @@ stages:
artifactName: csharp-docs-dlls
- powershell: |
dotnet tool install --global CycloneDX
dotnet-CycloneDX $(solution) --output $(Build.ArtifactStagingDirectory)/bom --filename bom-dotnet.xml
dotnet-CycloneDX $(solution) --spec-version 1.5 --output $(Build.ArtifactStagingDirectory)/bom --filename bom-dotnet.xml
displayName: 'Generate Backend BOM'
- powershell: |
npm install --global @cyclonedx/cyclonedx-npm
@@ -175,6 +175,41 @@ stages:
artifact: bom-frontend
displayName: 'Publish Frontend BOM'
- job: C
displayName: Build Test Helpers Package
pool:
vmImage: "ubuntu-latest"
steps:
- checkout: self
submodules: false
lfs: false
fetchDepth: 500
- template: templates/e2e-install.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
npm_config_cache: ${{ variables.npm_config_cache }}
- bash: |
echo "##[command]Install nbgv"
dotnet tool install --tool-path . nbgv
echo "##[command]Running nbgv get-version"
PACKAGE_VERSION=$(nbgv get-version -v NpmPackageVersion)
echo "##[command]Running npm version"
echo "##[debug]Version: $PACKAGE_VERSION"
cd tests/Umbraco.Tests.AcceptanceTest
npm version $PACKAGE_VERSION --allow-same-version --no-git-tag-version
displayName: Set NPM Version
- bash: |
echo "##[command]Running npm pack"
mkdir $(Build.ArtifactStagingDirectory)/npm-testhelpers
npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm-testhelpers
displayName: Run npm pack
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
- task: PublishPipelineArtifact@1
displayName: Publish Test Helpers npm artifact
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/npm-testhelpers
artifactName: npm-testhelpers
- stage: E2E_BOM
displayName: E2E Tests BOM Generation
dependsOn: []
@@ -579,7 +614,6 @@ stages:
UMBRACO__CMS__GLOBAL__VERSIONCHECKPERIOD: 0
UMBRACO__CMS__GLOBAL__USEHTTPS: true
UMBRACO__CMS__HEALTHCHECKS__NOTIFICATION__ENABLED: false
UMBRACO__CMS__KEEPALIVE__DISABLEKEEPALIVETASK: true
UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL: https://localhost:44331/
ASPNETCORE_URLS: https://localhost:44331
jobs:
@@ -832,6 +866,40 @@ stages:
npm publish "${files[0]}"
displayName: Push to npm (MyGet)
workingDirectory: $(Pipeline.Workspace)/npm
- job: PublishTestHelpersNpm
displayName: Push TestHelpers to pre-release feed (npm)
steps:
- checkout: none
- download: current
artifact: npm-testhelpers
- bash: |
# Check if we are on a nightly build
if [ $isNightly = "False" ]; then
echo "##[debug]Prerelease build detected"
registry="https://www.myget.org/F/umbracoprereleases/npm/"
else
echo "##[debug]Nightly build detected"
registry="https://www.myget.org/F/umbraconightly/npm/"
fi
echo "@umbraco-cms:registry=$registry" >> .npmrc
env:
isNightly: ${{parameters.isNightly}}
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
displayName: Add scoped registry to .npmrc
- task: npmAuthenticate@0
displayName: Authenticate with npm (MyGet)
inputs:
workingFile: "$(Pipeline.Workspace)/npm-testhelpers/.npmrc"
customEndpoint: "MyGet (npm) - Umbracoprereleases, MyGet (npm) - Umbraconightly"
- bash: |
# Setup temp npm project to load in defaults from the local .npmrc
npm init -y
# Find the first .tgz file in the current directory and publish it
files=( ./*.tgz )
npm publish "${files[0]}"
displayName: Push test helpers to npm (MyGet)
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
- stage: Deploy_NuGet
displayName: NuGet release
@@ -886,6 +954,29 @@ stages:
npm publish "${files[0]}"
displayName: Push to npm
workingDirectory: $(Pipeline.Workspace)/npm
- job: PublishTestHelpers
displayName: Push Test Helpers to NPM
steps:
- checkout: none
- download: current
artifact: npm-testhelpers
- bash: echo "@umbraco-cms:registry=https://registry.npmjs.org" >> .npmrc
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
displayName: Add scoped registry to .npmrc
- task: npmAuthenticate@0
displayName: Authenticate with npm
inputs:
workingFile: $(Pipeline.Workspace)/npm-testhelpers/.npmrc
customEndpoint: "NPM - Umbraco Backoffice"
- script: |
# Setup temp npm project to load in defaults from the local .npmrc
npm init -y
# Find the first .tgz file in the current directory and publish it
files=( ./*.tgz )
npm publish "${files[0]}"
displayName: Push test helpers to npm
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
- stage: Upload_API_Docs
pool:
+6 -7
View File
@@ -4,12 +4,10 @@ pr: none
trigger: none
schedules:
- cron: '0 0 * * *'
displayName: Daily midnight build
- cron: '0 3 * * *'
displayName: Daily 3AM build (main)
branches:
include:
- v15/dev
- v16/dev
- main
parameters:
@@ -321,7 +319,8 @@ stages:
- stage: DefaultConfigE2E
displayName: Default Config E2E Tests
dependsOn: Build
dependsOn: Integration
condition: always()
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
# Enable console logging in Release mode
@@ -340,7 +339,6 @@ stages:
UMBRACO__CMS__GLOBAL__VERSIONCHECKPERIOD: 0
UMBRACO__CMS__GLOBAL__USEHTTPS: true
UMBRACO__CMS__HEALTHCHECKS__NOTIFICATION__ENABLED: false
UMBRACO__CMS__KEEPALIVE__DISABLEKEEPALIVETASK: true
UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL: https://localhost:44331/
ASPNETCORE_URLS: https://localhost:44331
jobs:
@@ -502,7 +500,8 @@ stages:
- stage: AdditionalConfigE2E
displayName: Additional Config E2E Tests
dependsOn: Build
dependsOn: DefaultConfigE2E
condition: always()
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
ASPNETCORE_URLS: https://localhost:44331
+1
View File
@@ -10,6 +10,7 @@ schedules:
include:
- v13/dev
- v16/dev
- v18/dev
- main
steps:
+5 -1
View File
@@ -29,7 +29,7 @@ steps:
"UMBRACO_USER_LOGIN=${{ parameters.PlaywrightUserEmail }}
UMBRACO_USER_PASSWORD=${{ parameters.PlaywrightPassword }}
URL=${{ parameters.ASPNETCORE_URLS }}
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
STORAGE_STATE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
@@ -47,3 +47,7 @@ steps:
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
- script: npm run build
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Build test helpers
@@ -1,7 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using OpenIddict.Validation;
using Umbraco.Cms.Core;
@@ -25,6 +28,7 @@ internal sealed class HideBackOfficeTokensHandler
: IOpenIddictServerHandler<OpenIddictServerEvents.ApplyTokenResponseContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ApplyAuthorizationResponseContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ExtractTokenRequestContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ExtractRevocationRequestContext>,
IOpenIddictValidationHandler<OpenIddictValidationEvents.ProcessAuthenticationContext>,
INotificationHandler<UserLogoutSuccessNotification>
{
@@ -33,13 +37,16 @@ internal sealed class HideBackOfficeTokensHandler
// The __Host- prefix enforces secure cookies at browser level (requires Secure, Path=/, no Domain).
// For local development over HTTP, we use a simpler prefix to avoid browser rejection.
private const string SecureCookiePrefix = "__Host-";
private const string AccessTokenCookieName = "umbAccessToken";
private const string RefreshTokenCookieName = "umbRefreshToken";
private const string PkceCodeCookieName = "umbPkceCode";
private readonly string _accessTokenCookieName = "umbAccessToken";
private readonly string _refreshTokenCookieName = "umbRefreshToken";
private readonly string _pkceCodeCookieName = "umbPkceCode";
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly ILogger<HideBackOfficeTokensHandler> _logger;
#pragma warning disable CS0618 // Type or member is obsolete
private readonly BackOfficeTokenCookieSettings _backOfficeTokenCookieSettings;
#pragma warning restore CS0618 // Type or member is obsolete
private readonly GlobalSettings _globalSettings;
/// <summary>
@@ -47,18 +54,27 @@ internal sealed class HideBackOfficeTokensHandler
/// </summary>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
/// <param name="dataProtectionProvider">The data protection provider for encrypting cookie values.</param>
/// <param name="logger">The logger.</param>
/// <param name="backOfficeTokenCookieSettings">The back-office token cookie settings.</param>
/// <param name="globalSettings">The global settings.</param>
public HideBackOfficeTokensHandler(
IHttpContextAccessor httpContextAccessor,
IDataProtectionProvider dataProtectionProvider,
ILogger<HideBackOfficeTokensHandler> logger,
#pragma warning disable CS0618 // Type or member is obsolete
IOptions<BackOfficeTokenCookieSettings> backOfficeTokenCookieSettings,
#pragma warning restore CS0618 // Type or member is obsolete
IOptions<GlobalSettings> globalSettings)
{
_httpContextAccessor = httpContextAccessor;
_dataProtectionProvider = dataProtectionProvider;
_logger = logger;
_backOfficeTokenCookieSettings = backOfficeTokenCookieSettings.Value;
_globalSettings = globalSettings.Value;
_accessTokenCookieName += _backOfficeTokenCookieSettings.SiteName;
_refreshTokenCookieName += _backOfficeTokenCookieSettings.SiteName;
_pkceCodeCookieName += _backOfficeTokenCookieSettings.SiteName;
}
/// <summary>
@@ -78,13 +94,13 @@ internal sealed class HideBackOfficeTokensHandler
if (context.Response.AccessToken is not null)
{
SetCookie(httpContext, AccessTokenCookieName, context.Response.AccessToken);
SetCookie(httpContext, _accessTokenCookieName, context.Response.AccessToken);
context.Response.AccessToken = RedactedTokenValue;
}
if (context.Response.RefreshToken is not null)
{
SetCookie(httpContext, RefreshTokenCookieName, context.Response.RefreshToken);
SetCookie(httpContext, _refreshTokenCookieName, context.Response.RefreshToken);
context.Response.RefreshToken = RedactedTokenValue;
}
@@ -106,7 +122,7 @@ internal sealed class HideBackOfficeTokensHandler
if (context.Response.Code is not null)
{
SetCookie(GetHttpContext(), PkceCodeCookieName, context.Response.Code);
SetCookie(GetHttpContext(), _pkceCodeCookieName, context.Response.Code);
context.Response.Code = RedactedTokenValue;
}
@@ -128,12 +144,12 @@ internal sealed class HideBackOfficeTokensHandler
// Handle when the PKCE code is being exchanged for an access token.
if (context.Request.Code == RedactedTokenValue
&& TryGetCookie(httpContext, PkceCodeCookieName, out var code))
&& TryGetCookie(httpContext, _pkceCodeCookieName, out var code))
{
context.Request.Code = code;
// We won't need the PKCE cookie after this, let's remove it.
RemoveCookie(httpContext, PkceCodeCookieName);
RemoveCookie(httpContext, _pkceCodeCookieName);
}
else
{
@@ -144,7 +160,7 @@ internal sealed class HideBackOfficeTokensHandler
// Handle when a refresh token is being exchanged for a new access token.
if (context.Request.RefreshToken == RedactedTokenValue
&& TryGetCookie(httpContext, RefreshTokenCookieName, out var refreshToken))
&& TryGetCookie(httpContext, _refreshTokenCookieName, out var refreshToken))
{
context.Request.RefreshToken = refreshToken;
}
@@ -159,6 +175,40 @@ internal sealed class HideBackOfficeTokensHandler
return ValueTask.CompletedTask;
}
/// <summary>
/// This is invoked when a token revocation request is received.
/// </summary>
public ValueTask HandleAsync(OpenIddictServerEvents.ExtractRevocationRequestContext context)
{
if (context.Request?.ClientId != Constants.OAuthClientIds.BackOffice)
{
// Only ever handle the back-office client.
return ValueTask.CompletedTask;
}
HttpContext httpContext = GetHttpContext();
// Determine which cookie to read based on the token type hint.
var cookieName = context.Request.TokenTypeHint == OpenIddictConstants.TokenTypeHints.RefreshToken
? _refreshTokenCookieName
: _accessTokenCookieName;
if (context.Request.Token == RedactedTokenValue
&& TryGetCookie(httpContext, cookieName, out var token))
{
context.Request.Token = token;
}
else
{
// If we got here, either the token was not redacted, or nothing was found in the expected cookie.
// If OpenIddict found a token, it could be an old token that is potentially still valid. For security
// reasons, we cannot accept that; at this point, we expect the tokens to be explicitly redacted.
context.Request.Token = null;
}
return ValueTask.CompletedTask;
}
/// <summary>
/// This is invoked when extracting the auth context for a client request.
/// </summary>
@@ -170,7 +220,7 @@ internal sealed class HideBackOfficeTokensHandler
return ValueTask.CompletedTask;
}
if (TryGetCookie(GetHttpContext(), AccessTokenCookieName, out var accessToken))
if (TryGetCookie(GetHttpContext(), _accessTokenCookieName, out var accessToken))
{
context.AccessToken = accessToken;
}
@@ -190,8 +240,8 @@ internal sealed class HideBackOfficeTokensHandler
return;
}
RemoveCookie(httpContext, AccessTokenCookieName);
RemoveCookie(httpContext, RefreshTokenCookieName);
RemoveCookie(httpContext, _accessTokenCookieName);
RemoveCookie(httpContext, _refreshTokenCookieName);
}
private HttpContext GetHttpContext()
@@ -247,8 +297,19 @@ internal sealed class HideBackOfficeTokensHandler
var key = GetCookieKey(httpContext, cookieName);
if (httpContext.Request.Cookies.TryGetValue(key, out var cookieValue))
{
value = EncryptionHelper.Decrypt(cookieValue, _dataProtectionProvider);
return true;
try
{
value = EncryptionHelper.Decrypt(cookieValue, _dataProtectionProvider);
return true;
}
catch (CryptographicException ex)
{
// Decryption can fail if the data protection key ring has changed
// (e.g., after deployment, app pool recycle, or slot swap).
// Treat this as a missing cookie — the user will need to re-authenticate.
_logger.LogWarning(ex, "Failed to decrypt back-office token cookie '{CookieName}'. The user will need to re-authenticate.", cookieName);
RemoveCookie(httpContext, cookieName);
}
}
value = null;
@@ -145,6 +145,12 @@ public static class UmbracoBuilderAuthExtensions
.UseSingletonHandler<HideBackOfficeTokensHandler>()
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest<OpenIddictServerEvents.ExtractTokenRequestContext>.Descriptor.Order + 1);
});
options.AddEventHandler<OpenIddictServerEvents.ExtractRevocationRequestContext>(configuration =>
{
configuration
.UseSingletonHandler<HideBackOfficeTokensHandler>()
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest<OpenIddictServerEvents.ExtractRevocationRequestContext>.Descriptor.Order + 1);
});
})
// Register the OpenIddict validation components.
@@ -7,6 +7,7 @@ using Umbraco.Cms.Api.Delivery.Configuration;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Features;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Controllers;
namespace Umbraco.Cms.Api.Delivery.Controllers;
@@ -14,6 +15,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers;
[JsonOptionsName(Constants.JsonOptionsNames.DeliveryApi)]
[MapToApi(DeliveryApiConfiguration.ApiName)]
[Authorize(Policy = AuthorizationPolicies.UmbracoFeatureEnabled)]
[MaintenanceModeActionFilter]
public abstract class DeliveryApiControllerBase : Controller, IUmbracoFeature
{
protected string DecodePath(string path)
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Builder;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Extensions;
/// <summary>
/// <see cref="IApplicationBuilder" /> extensions for the Umbraco Delivery API.
/// </summary>
public static class DeliveryApiApplicationBuilderExtensions
{
/// <summary>
/// Sets up routes for the Umbraco Delivery API.
/// </summary>
/// <remarks>
/// This method maps attribute-routed controllers including the Delivery API endpoints.
/// Call this when using <c>AddDeliveryApi()</c> without <c>AddBackOffice()</c>, as the
/// backoffice endpoints normally handle the controller mapping.
/// </remarks>
/// <param name="builder">The Umbraco endpoint builder context.</param>
/// <returns>The <see cref="IUmbracoEndpointBuilderContext" /> for chaining.</returns>
public static IUmbracoEndpointBuilderContext UseDeliveryApiEndpoints(this IUmbracoEndpointBuilderContext builder)
{
builder.EndpointRouteBuilder.MapControllers();
return builder;
}
}
@@ -22,7 +22,6 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Infrastructure.Security;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
@@ -30,8 +29,20 @@ namespace Umbraco.Extensions;
public static class UmbracoBuilderExtensions
{
/// <summary>
/// Add services for the Umbraco Delivery API (headless content delivery).
/// </summary>
/// <remarks>
/// This method assumes that either <c>AddBackOffice()</c> or <c>AddCore()</c> has already been called.
/// It registers Delivery API-specific services such as controllers, output caching, and member authentication.
/// </remarks>
/// <param name="builder">The Umbraco builder.</param>
/// <returns>The Umbraco builder.</returns>
public static IUmbracoBuilder AddDeliveryApi(this IUmbracoBuilder builder)
{
// Delivery API supports member authentication for protected content
builder.AddMembersIdentity();
builder.Services.AddScoped<IRequestStartItemProvider, RequestStartItemProvider>();
builder.Services.AddScoped<RequestContextOutputExpansionStrategy>();
builder.Services.AddScoped<RequestContextOutputExpansionStrategyV2>();
@@ -4,7 +4,11 @@ using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Filters;
internal sealed class DeliveryApiAccessAttribute : TypeFilterAttribute
/// <summary>
/// An action filter attribute that verifies public or preview access to the Delivery API, returning
/// a <c>401 Unauthorized</c> result if access is denied.
/// </summary>
public sealed class DeliveryApiAccessAttribute : TypeFilterAttribute
{
public DeliveryApiAccessAttribute()
: base(typeof(DeliveryApiAccessFilter))
@@ -4,7 +4,11 @@ using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Filters;
internal sealed class DeliveryApiMediaAccessAttribute : TypeFilterAttribute
/// <summary>
/// An action filter attribute that verifies public access to the media Delivery API, returning
/// a <c>401 Unauthorized</c> result if access is denied.
/// </summary>
public sealed class DeliveryApiMediaAccessAttribute : TypeFilterAttribute
{
public DeliveryApiMediaAccessAttribute()
: base(typeof(DeliveryApiMediaAccessFilter))
@@ -1,12 +1,13 @@
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Examine;
namespace Umbraco.Cms.Api.Delivery.Indexing.Selectors;
public sealed class AncestorsSelectorIndexer : IContentIndexHandler
{
// NOTE: "id" is a reserved field name
internal const string FieldName = "itemId";
internal const string FieldName = UmbracoExamineFieldNames.DeliveryApiContentIndex.ItemId;
public IEnumerable<IndexFieldValue> GetFieldValues(IContent content, string? culture)
=> new[] { new IndexFieldValue { FieldName = FieldName, Values = new object[] { content.Key } } };
@@ -2,7 +2,10 @@ using Umbraco.Cms.Web.Common.Routing;
namespace Umbraco.Cms.Api.Delivery.Routing;
internal sealed class VersionedDeliveryApiRouteAttribute : BackOfficeRouteAttribute
/// <summary>
/// A routing attribute that ensures consistent Delivery API endpoint paths.
/// </summary>
public sealed class VersionedDeliveryApiRouteAttribute : BackOfficeRouteAttribute
{
public VersionedDeliveryApiRouteAttribute(string template)
: base($"delivery/api/v{{version:apiVersion}}/{template.TrimStart('/')}")
@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Api.Delivery.Services;
/// </summary>
internal sealed class ApiContentQueryProvider : IApiContentQueryProvider
{
private const string ItemIdFieldName = "itemId";
private const string ItemIdFieldName = UmbracoExamineFieldNames.DeliveryApiContentIndex.ItemId;
private readonly IExamineManager _examineManager;
private readonly ILogger<ApiContentQueryProvider> _logger;
private readonly ApiContentQuerySelectorBuilder _selectorBuilder;
+2 -2
View File
@@ -26,7 +26,8 @@ RESTful API for Umbraco backoffice operations. Manages content, media, users, an
- **Validation**: FluentValidation via base controllers
- **Serialization**: System.Text.Json with custom converters
- **Mapping**: Manual presentation factories (no AutoMapper)
- **Patching**: JsonPatch.Net for PATCH operations
- **Patching**: Custom patch engine for PATCH operations (Umbraco.Cms.Api.Management.Patching)
- ⚠️ Legacy JsonPatch.Net support (IJsonPatchService) still available but **obsolete** - scheduled for removal in v19
- **Real-time**: SignalR hubs (`BackofficeHub`, `ServerEventHub`)
- **DI**: Microsoft.Extensions.DependencyInjection via `ManagementApiComposer`
@@ -70,7 +71,6 @@ src/Umbraco.Cms.Api.Management/
- **Umbraco.Cms.Api.Common** - Shared API infrastructure (base controllers, OpenAPI config)
- **Umbraco.Infrastructure** - Service implementations, data access
- **Umbraco.PublishedCache.HybridCache** - Published content queries
- **JsonPatch.Net** - JSON Patch (RFC 6902) support
- **Swashbuckle.AspNetCore** - OpenAPI generation
### Design Patterns
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Api.Management.Controllers.Document.GetPublicAccessDocumentController.GetPublicAccess(System.Threading.CancellationToken,System.Guid)</Target>
<Left>lib/net10.0/Umbraco.Cms.Api.Management.dll</Left>
<Right>lib/net10.0/Umbraco.Cms.Api.Management.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Api.Management.Controllers.UrlSegment.ResizeImagingController.Urls(System.Collections.Generic.HashSet{System.Guid},System.Int32,System.Int32,System.Nullable{Umbraco.Cms.Core.Models.ImageCropMode})</Target>
<Left>lib/net10.0/Umbraco.Cms.Api.Management.dll</Left>
<Right>lib/net10.0/Umbraco.Cms.Api.Management.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
@@ -140,15 +140,20 @@ public class ConfigureBackOfficeCookieOptions : IConfigureNamedOptions<CookieAut
await securityStampValidator.ValidateAsync(ctx);
// We have to manually specify Issued and Expires,
// because the SecurityStampValidator refreshes the principal every 30 minutes,
// When the principal is refreshed the Issued is update to time of refresh, however, the Expires remains unchanged
// When we then try and renew, the difference of issued and expires effectively becomes the new ExpireTimeSpan
// meaning we effectively lose 30 minutes of our ExpireTimeSpan for EVERY principal refresh if we don't
// https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/CookieAuthenticationHandler.cs#L115
ctx.Properties.IssuedUtc = _timeProvider.GetUtcNow();
ctx.Properties.ExpiresUtc = _timeProvider.GetUtcNow().Add(_globalSettings.TimeOut);
ctx.ShouldRenew = true;
// Only reset timestamps when a renewal was already triggered (by the SecurityStampValidator
// or by EnsureTicketRenewalIfKeepUserLoggedIn above).
// When the SecurityStampValidator refreshes the principal, it sets ShouldRenew but updates
// IssuedUtc without updating ExpiresUtc, causing the effective cookie lifetime to shrink
// with each validation. The manual reset here fixes that drift.
// IMPORTANT: Do NOT unconditionally set ShouldRenew or reset IssuedUtc - doing so prevents
// the SecurityStampValidator from ever exceeding its ValidationInterval during active use,
// which breaks AllowConcurrentLogins enforcement.
if (ctx.ShouldRenew)
{
DateTimeOffset now = _timeProvider.GetUtcNow();
ctx.Properties.IssuedUtc = now;
ctx.Properties.ExpiresUtc = now.Add(_globalSettings.TimeOut);
}
},
OnSigningIn = ctx =>
{
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Configuration;
/// <summary>
/// Used to configure <see cref="CookieAuthenticationOptions" /> for the back office "exposed" authentication type
/// </summary>
public class ConfigureBackOfficeExposedCookieOptions : IConfigureNamedOptions<CookieAuthenticationOptions>
{
private readonly SecuritySettings _securitySettings;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureBackOfficeExposedCookieOptions" /> class.
/// </summary>
/// <param name="securitySettings">The <see cref="SecuritySettings" /> options</param>
public ConfigureBackOfficeExposedCookieOptions(IOptions<SecuritySettings> securitySettings)
=> _securitySettings = securitySettings.Value;
/// <inheritdoc />
public void Configure(string? name, CookieAuthenticationOptions options)
{
if (name != Constants.Security.BackOfficeExposedAuthenticationType)
{
return;
}
Configure(options);
}
/// <inheritdoc />
public void Configure(CookieAuthenticationOptions options)
{
options.Cookie.Name = _securitySettings.AuthCookieName.IsNullOrWhiteSpace()
? Constants.Security.BackOfficeExposedCookieName
: $"{_securitySettings.AuthCookieName}{Constants.Security.BackOfficeExposedCookieNamePostfix}";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.SlidingExpiration = true;
}
}
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Web.Common.Security;
@@ -13,6 +13,11 @@ public class ConfigureBackOfficeSecurityStampValidatorOptions : IConfigureOption
private readonly SecuritySettings _securitySettings;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureBackOfficeSecurityStampValidatorOptions"/> class with the specified security settings and time provider.
/// </summary>
/// <param name="securitySettings">The <see cref="IOptions{SecuritySettings}"/> used to access security-related configuration options.</param>
/// <param name="timeProvider">The <see cref="TimeProvider"/> used for time-based operations.</param>
public ConfigureBackOfficeSecurityStampValidatorOptions(IOptions<SecuritySettings> securitySettings, TimeProvider timeProvider)
{
_timeProvider = timeProvider;
@@ -23,6 +28,6 @@ public class ConfigureBackOfficeSecurityStampValidatorOptions : IConfigureOption
public void Configure(BackOfficeSecurityStampValidatorOptions options)
{
options.TimeProvider = _timeProvider;
ConfigureSecurityStampOptions.ConfigureOptions(options, _securitySettings);
ConfigureSecurityStampOptions.ConfigureOptions(options, _securitySettings.GetUserAllowConcurrentLogins());
}
}
@@ -9,15 +9,30 @@ using Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Management.Configuration;
/// <summary>
/// Provides configuration for Swagger generation options specific to the Umbraco Management API.
/// This class is used to customize the Swagger documentation for the API endpoints.
/// </summary>
public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureUmbracoManagementApiSwaggerGenOptions"/> class.
/// </summary>
/// <param name="umbracoJsonTypeInfoResolver">An instance of <see cref="IUmbracoJsonTypeInfoResolver"/> used to resolve JSON type information for Umbraco.</param>
public ConfigureUmbracoManagementApiSwaggerGenOptions(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}
/// <summary>
/// Configures the <see cref="SwaggerGenOptions"/> for the Umbraco Management API.
/// Sets up the Swagger documentation, including API metadata, security definitions for OAuth2 authentication,
/// operation filters for response headers and security requirements, and schema filters for non-nullable properties.
/// Also configures polymorphism handling and discriminator properties for OpenAPI schemas.
/// </summary>
/// <param name="swaggerGenOptions">The <see cref="SwaggerGenOptions"/> instance to configure for the Management API.</param>
public void Configure(SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SwaggerDoc(
@@ -7,6 +7,9 @@ using Umbraco.Cms.Core.Hosting;
namespace Umbraco.Cms.Api.Management;
/// <summary>
/// Provides endpoints for managing back office user authentication and login operations.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
[Route(LoginPath)]
public class BackOfficeLoginController : Controller
@@ -15,6 +18,11 @@ public class BackOfficeLoginController : Controller
private readonly IHostingEnvironment _hostingEnvironment;
private readonly GlobalSettings _globalSettings;
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeLoginController"/> class.
/// </summary>
/// <param name="globalSettings">A snapshot of the application's global settings options.</param>
/// <param name="hostingEnvironment">The current hosting environment for the application.</param>
public BackOfficeLoginController(
IOptionsSnapshot<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment)
@@ -24,6 +32,16 @@ public class BackOfficeLoginController : Controller
}
// GET
/// <summary>
/// Handles the GET request for the back office login page.
/// If the user is already authenticated, updates the model accordingly.
/// Ensures the return URL is a relative path and sets default values if necessary.
/// </summary>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <param name="model">The model containing login information and the return URL.</param>
/// <returns>
/// An <see cref="IActionResult"/> that renders the login view with the model, or a bad request result if the return URL is invalid.
/// </returns>
public async Task<IActionResult> Index(CancellationToken cancellationToken, BackOfficeLoginModel model)
{
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType);
@@ -2,6 +2,9 @@ using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Cms.Api.Management;
/// <summary>
/// Represents a model containing the credentials required for logging into the Umbraco back office.
/// </summary>
[BindProperties]
public class BackOfficeLoginModel
{
@@ -16,5 +19,8 @@ public class BackOfficeLoginModel
/// </summary>
public string? UmbracoUrl { get; set; }
/// <summary>
/// Indicates whether the user is already logged in to the back office.
/// </summary>
public bool UserIsAlreadyLoggedIn { get; set; }
}
@@ -14,6 +14,13 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Content;
/// <summary>
/// Serves as a base controller for managing collections of content items, providing shared functionality for handling content collections and their variants.
/// </summary>
/// <typeparam name="TContent">The content entity type.</typeparam>
/// <typeparam name="TCollectionResponseModel">The response model type for the content collection.</typeparam>
/// <typeparam name="TValueResponseModelBase">The base type for value response models within the collection.</typeparam>
/// <typeparam name="TVariantResponseModel">The response model type for content variants.</typeparam>
public abstract class ContentCollectionControllerBase<TContent, TCollectionResponseModel, TValueResponseModelBase, TVariantResponseModel> : ManagementApiControllerBase
where TContent : class, IContentBase
where TCollectionResponseModel : ContentResponseModelBase<TValueResponseModelBase, TVariantResponseModel>
@@ -8,6 +8,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Content;
/// <summary>
/// Serves as the base controller for content management operations in the Umbraco CMS API, providing shared functionality for content-related controllers.
/// </summary>
public abstract class ContentControllerBase : ManagementApiControllerBase
{
protected IActionResult ContentEditingOperationStatusResult(ContentEditingOperationStatus status)
@@ -53,6 +56,15 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
ContentEditingOperationStatus.PropertyTypeNotFound => NotFound(problemDetailsBuilder
.WithTitle("One or more property types could not be found")
.Build()),
ContentEditingOperationStatus.PropertyTypeCultureVarianceMismatch => BadRequest(problemDetailsBuilder
.WithTitle("Property type culture variance mismatch")
.WithDetail("One or more property values specify a culture for an invariant property, or are missing a culture for a culture-variant property. "
+ "This can happen when a property is inherited from a variant composition on an invariant content type, which downgrades it to invariant.")
.Build()),
ContentEditingOperationStatus.PropertyTypeSegmentVarianceMismatch => BadRequest(problemDetailsBuilder
.WithTitle("Property type segment variance mismatch")
.WithDetail("One or more property values have a segment that does not match the property type's segment variance.")
.Build()),
ContentEditingOperationStatus.InTrash => BadRequest(problemDetailsBuilder
.WithTitle("Content is in the recycle bin")
.WithDetail("Could not perform the operation because the targeted content was in the recycle bin.")
@@ -79,11 +91,11 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
.Build()),
ContentEditingOperationStatus.CannotDeleteWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle("Cannot delete a referenced content item")
.WithDetail("Cannot delete a referenced document, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.WithDetail("Cannot delete a referenced content item, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.Build()),
ContentEditingOperationStatus.CannotMoveToRecycleBinWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle("Cannot move a referenced document to the recycle bin")
.WithDetail("Cannot move a referenced document to the recycle bin, while the setting ContentSettings.DisableUnpublishWhenReferenced is enabled.")
.WithTitle("Cannot move a referenced content item to the recycle bin")
.WithDetail("Cannot move a referenced content item to the recycle bin, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.Build()),
ContentEditingOperationStatus.Unknown => StatusCode(
StatusCodes.Status500InternalServerError,
@@ -9,18 +9,33 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Culture;
/// <summary>
/// API controller responsible for retrieving and managing culture information in the system.
/// </summary>
[ApiVersion("1.0")]
public class AllCultureController : CultureControllerBase
{
private readonly IUmbracoMapper _umbracoMapper;
private readonly ICultureService _cultureService;
/// <summary>
/// Initializes a new instance of the <see cref="AllCultureController"/> class with the specified Umbraco mapper and culture service.
/// </summary>
/// <param name="umbracoMapper">An instance of <see cref="IUmbracoMapper"/> used for mapping Umbraco objects.</param>
/// <param name="cultureService">An instance of <see cref="ICultureService"/> used for managing culture information.</param>
public AllCultureController(IUmbracoMapper umbracoMapper, ICultureService cultureService)
{
_umbracoMapper = umbracoMapper;
_cultureService = cultureService;
}
/// <summary>
/// Retrieves a paginated list of all available cultures, including their English and localized names.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="skip">The number of cultures to skip before starting to collect the result set.</param>
/// <param name="take">The maximum number of cultures to return.</param>
/// <returns>A task representing the asynchronous operation. The task result contains a <see cref="PagedViewModel{CultureReponseModel}"/> with the paginated cultures.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<CultureReponseModel>), StatusCodes.Status200OK)]
@@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
namespace Umbraco.Cms.Api.Management.Controllers.Culture;
/// <summary>
/// Serves as the base controller for API endpoints that manage culture-related operations in the Umbraco CMS.
/// </summary>
[VersionedApiBackOfficeRoute("culture")]
[ApiExplorerSettings(GroupName = "Culture")]
public abstract class CultureControllerBase : ManagementApiControllerBase
@@ -0,0 +1,60 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Provides an API controller for retrieving the full details for multiple data types by key.
/// </summary>
[ApiVersion("1.0")]
public class BatchDataTypesController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="BatchDataTypesController"/> class.
/// </summary>
/// <param name="dataTypeService">The data type service.</param>
/// <param name="umbracoMapper">The presentation model mapper.</param>
public BatchDataTypesController(IDataTypeService dataTypeService, IUmbracoMapper umbracoMapper)
{
_dataTypeService = dataTypeService;
_umbracoMapper = umbracoMapper;
}
[HttpGet("batch")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(BatchResponseModel<DataTypeResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets multiple data types.")]
[EndpointDescription("Gets multiple data types identified by the provided Ids.")]
public async Task<IActionResult> Batch(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
Guid[] requestedIds = [.. ids];
if (requestedIds.Length == 0)
{
return Ok(new BatchResponseModel<DataTypeResponseModel>());
}
IEnumerable<IDataType> dataTypes = await _dataTypeService.GetAllAsync(requestedIds);
List<IDataType> ordered = OrderByRequestedIds(dataTypes, requestedIds);
var responseModels = ordered.Select(dt => _umbracoMapper.Map<DataTypeResponseModel>(dt)!).ToList();
return Ok(new BatchResponseModel<DataTypeResponseModel>
{
Total = responseModels.Count,
Items = responseModels,
});
}
}
@@ -0,0 +1,71 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Controller for retrieving multiple data type value schemas in a single request.
/// </summary>
[ApiVersion("1.0")]
public class BatchSchemasDataTypeController : DataTypeControllerBase
{
private readonly IPropertyEditorSchemaService _schemaService;
/// <summary>
/// Initializes a new instance of the <see cref="BatchSchemasDataTypeController"/> class.
/// </summary>
/// <param name="schemaService">The property editor schema service.</param>
public BatchSchemasDataTypeController(IPropertyEditorSchemaService schemaService)
=> _schemaService = schemaService;
/// <summary>
/// Gets the value schemas for multiple data types.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
/// <param name="ids">The unique identifiers of the data types.</param>
/// <returns>The schema information for the requested data types.</returns>
/// <remarks>
/// Returns schema information for property editors that implement <c>IValueSchemaProvider</c>.
/// Each item includes an error field if the schema could not be retrieved (e.g., data type not found or schema not supported).
/// </remarks>
[HttpGet("schemas/batch")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FetchResponseModel<DataTypeSchemaItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetSchemas(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] Guid[] ids)
{
Guid[] requestedIds = [.. ids.Distinct()];
if (requestedIds.Length == 0)
{
return Ok(new FetchResponseModel<DataTypeSchemaItemResponseModel>());
}
var items = new List<DataTypeSchemaItemResponseModel>();
foreach (Guid id in requestedIds)
{
Attempt<PropertyValueSchema, PropertyEditorSchemaOperationStatus> attempt = await _schemaService.GetSchemaAsync(id);
items.Add(new DataTypeSchemaItemResponseModel
{
Id = id,
ValueTypeName = attempt.Success ? attempt.Result.ValueType?.FullName : null,
JsonSchema = attempt.Success ? attempt.Result.JsonSchema : null,
Error = attempt.Success ? null : attempt.Status.ToString(),
});
}
return Ok(new FetchResponseModel<DataTypeSchemaItemResponseModel>
{
Total = items.Count,
Items = items,
});
}
}
@@ -8,18 +8,34 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Controller for managing data types by their unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="ByKeyDataTypeController"/> class.
/// </summary>
/// <param name="dataTypeService">Service used for managing and retrieving data types.</param>
/// <param name="umbracoMapper">The mapper used to map between Umbraco domain models and API models.</param>
public ByKeyDataTypeController(IDataTypeService dataTypeService, IUmbracoMapper umbracoMapper)
{
_dataTypeService = dataTypeService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Retrieves a data type by its unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the data type to retrieve.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing the data type if found; otherwise, a 404 Not Found result.
/// </returns>
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DataTypeResponseModel), StatusCodes.Status200OK)]
@@ -8,13 +8,25 @@ using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Controller responsible for managing configuration for data types in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class ConfigurationDataTypeController : DataTypeControllerBase
{
private readonly DataTypesSettings _dataTypesSettings;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationDataTypeController"/> class.
/// </summary>
/// <param name="dataTypesSettings">An <see cref="IOptionsSnapshot{T}"/> containing the <see cref="DataTypesSettings"/> configuration options.</param>
public ConfigurationDataTypeController(IOptionsSnapshot<DataTypesSettings> dataTypesSettings) => _dataTypesSettings = dataTypesSettings.Value;
/// <summary>
/// Retrieves the configuration settings for data types, including whether data types can be changed and the identifiers for document and media list views.
/// </summary>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>An <see cref="IActionResult"/> containing a <see cref="DatatypeConfigurationResponseModel"/> with the data type configuration settings.</returns>
[HttpGet("configuration")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DatatypeConfigurationResponseModel), StatusCodes.Status200OK)]
@@ -12,6 +12,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// API controller responsible for handling requests to copy data types within the Umbraco CMS management interface.
/// </summary>
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class CopyDataTypeController : DataTypeControllerBase
@@ -19,12 +22,26 @@ public class CopyDataTypeController : DataTypeControllerBase
private readonly IDataTypeService _dataTypeService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="CopyDataTypeController"/> class.
/// </summary>
/// <param name="dataTypeService">An instance of <see cref="IDataTypeService"/> used to manage data types.</param>
/// <param name="backOfficeSecurityAccessor">An instance of <see cref="IBackOfficeSecurityAccessor"/> used to access back office security information.</param>
public CopyDataTypeController(IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_dataTypeService = dataTypeService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Creates a copy of the specified data type.
/// The new data type will have a unique Id and its name will have " (copy)" appended.
/// Optionally, the copy can be placed in a specified container if a target container Id is provided.
/// </summary>
/// <param name="cancellationToken">Token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the data type to copy.</param>
/// <param name="copyDataTypeRequestModel">The request model containing copy options, such as the target container Id.</param>
/// <returns>A result indicating the outcome of the copy operation.</returns>
[HttpPost("{id:guid}/copy")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -13,6 +13,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// API controller responsible for handling requests to create new data types in Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class CreateDataTypeController : DataTypeControllerBase
@@ -21,6 +24,12 @@ public class CreateDataTypeController : DataTypeControllerBase
private readonly IDataTypePresentationFactory _dataTypePresentationFactory;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="CreateDataTypeController"/> class.
/// </summary>
/// <param name="dataTypeService">The <see cref="IDataTypeService"/> used to manage data types.</param>
/// <param name="dataTypePresentationFactory">The <see cref="IDataTypePresentationFactory"/> used to create data type presentation models.</param>
/// <param name="backOfficeSecurityAccessor">The <see cref="IBackOfficeSecurityAccessor"/> used to access back office security information.</param>
public CreateDataTypeController(IDataTypeService dataTypeService, IDataTypePresentationFactory dataTypePresentationFactory, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_dataTypeService = dataTypeService;
@@ -28,6 +37,14 @@ public class CreateDataTypeController : DataTypeControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Creates a new data type using the configuration provided in the request model.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="createDataTypeRequestModel">The model containing the configuration details for the new data type.</param>
/// <returns>
/// An <see cref="IActionResult"/> that represents the result of the create operation. Returns <c>201 Created</c> on success, or an appropriate error response on failure.
/// </returns>
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
@@ -9,6 +9,10 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Serves as the base controller for managing data types in the Umbraco CMS API.
/// This class is intended to be inherited by controllers that handle data type operations.
/// </summary>
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.DataType)]
[ApiExplorerSettings(GroupName = "Data Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrMediaOrMembersOrContentTypes)]
@@ -55,6 +59,21 @@ public abstract class DataTypeControllerBase : ManagementApiControllerBase
protected IActionResult DataTypeNotFound() => OperationStatusResult(DataTypeOperationStatus.NotFound, DataTypeNotFound);
protected IActionResult PropertyEditorSchemaOperationStatusResult(PropertyEditorSchemaOperationStatus status) =>
OperationStatusResult(status, problemDetailsBuilder => status switch
{
PropertyEditorSchemaOperationStatus.DataTypeNotFound => NotFound(problemDetailsBuilder
.WithTitle("The data type could not be found")
.Build()),
PropertyEditorSchemaOperationStatus.SchemaNotSupported => NotFound(problemDetailsBuilder
.WithTitle("Schema not supported")
.WithDetail("The property editor for this data type does not support schema information.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
.WithTitle("Unknown property editor schema operation status.")
.Build()),
});
private IActionResult DataTypeNotFound(ProblemDetailsBuilder problemDetailsBuilder)
=> NotFound(problemDetailsBuilder
.WithTitle("The data type could not be found")
@@ -11,6 +11,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// API controller responsible for handling requests to delete data types in the system.
/// </summary>
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class DeleteDataTypeController : DataTypeControllerBase
@@ -18,12 +21,23 @@ public class DeleteDataTypeController : DataTypeControllerBase
private readonly IDataTypeService _dataTypeService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteDataTypeController"/> class.
/// </summary>
/// <param name="dataTypeService">Service used to manage and delete data types.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context and authentication.</param>
public DeleteDataTypeController(IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_dataTypeService = dataTypeService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Deletes a data type identified by the provided Id.
/// </summary>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <param name="id">The unique identifier of the data type to delete.</param>
/// <returns>An <see cref="IActionResult"/> indicating the result of the delete operation.</returns>
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
@@ -6,6 +6,10 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Filter;
/// <summary>
/// Serves as the base controller for implementing data type filtering operations in the API.
/// Provides common functionality for derived controllers handling data type filters.
/// </summary>
[ApiExplorerSettings(GroupName = "Data Type")]
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Filter}/{Constants.UdiEntityType.DataType}")]
// This auth policy might become problematic, as when getting DataTypes on Media types, you don't need access to the document tree.
@@ -11,18 +11,36 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Filter;
/// <summary>
/// Controller responsible for handling operations related to filters on data types in the management API.
/// </summary>
[ApiVersion("1.0")]
public class FilterDataTypeFilterController : DataTypeFilterControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IUmbracoMapper _mapper;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.DataType.Filter.FilterDataTypeFilterController"/> class, responsible for filtering data types.
/// </summary>
/// <param name="dataTypeService">The <see cref="IDataTypeService"/> used to manage data types.</param>
/// <param name="mapper">The <see cref="IUmbracoMapper"/> used for mapping entities.</param>
public FilterDataTypeFilterController(IDataTypeService dataTypeService, IUmbracoMapper mapper)
{
_dataTypeService = dataTypeService;
_mapper = mapper;
}
/// <summary>
/// Retrieves a paginated and filtered list of data types based on the specified criteria.
/// </summary>
/// <param name="cancellationToken">A token to observe while waiting for the task to complete.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set (used for pagination).</param>
/// <param name="take">The maximum number of items to return (used for pagination).</param>
/// <param name="name">An optional filter to match data type names.</param>
/// <param name="editorUiAlias">An optional filter to match the editor UI alias.</param>
/// <param name="editorAlias">An optional filter to match the editor alias.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="IActionResult"/> with a paged collection of filtered data types.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<DataTypeItemResponseModel>), StatusCodes.Status200OK)]
@@ -7,9 +7,17 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
/// <summary>
/// Controller for managing data type folders by their unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyDataTypeFolderController : DataTypeFolderControllerBase
{
/// <summary>
/// Constructor for <see cref="Umbraco.Cms.Api.Management.Controllers.DataType.Folder.ByKeyDataTypeFolderController"/>.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Provides access to back office security features.</param>
/// <param name="dataTypeContainerService">Service for managing data type containers.</param>
public ByKeyDataTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IDataTypeContainerService dataTypeContainerService)
@@ -17,6 +25,14 @@ public class ByKeyDataTypeFolderController : DataTypeFolderControllerBase
{
}
/// <summary>
/// Retrieves a data type folder by its unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the data type folder to retrieve.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing a <see cref="FolderResponseModel"/> with the folder data if found; otherwise, a <see cref="ProblemDetails"/> with status 404 if not found.
/// </returns>
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)]
@@ -7,9 +7,17 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
/// <summary>
/// Provides API endpoints for creating folders used to organize data types in the system.
/// </summary>
[ApiVersion("1.0")]
public class CreateDataTypeFolderController : DataTypeFolderControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="CreateDataTypeFolderController"/> class, responsible for handling requests related to creating data type folders.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Provides access to back office security features for authorization and authentication.</param>
/// <param name="dataTypeContainerService">Service used to manage data type containers (folders) within the system.</param>
public CreateDataTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IDataTypeContainerService dataTypeContainerService)
@@ -17,6 +25,12 @@ public class CreateDataTypeFolderController : DataTypeFolderControllerBase
{
}
/// <summary>
/// Creates a new data type folder using the specified details.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="createFolderRequestModel">The request model containing the folder name and parent location.</param>
/// <returns>A <see cref="Task{IActionResult}"/> representing the asynchronous operation result.</returns>
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
@@ -9,6 +9,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
/// <summary>
/// Serves as the base controller for operations related to data type folders in the Umbraco CMS Management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.DataType}/folder")]
[ApiExplorerSettings(GroupName = "Data Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
@@ -6,9 +6,17 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
/// <summary>
/// Controller responsible for handling requests to delete data type folders in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class DeleteDataTypeFolderController : DataTypeFolderControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="DeleteDataTypeFolderController"/> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security operations.</param>
/// <param name="dataTypeContainerService">Service for managing data type containers (folders).</param>
public DeleteDataTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IDataTypeContainerService dataTypeContainerService)
@@ -16,6 +24,12 @@ public class DeleteDataTypeFolderController : DataTypeFolderControllerBase
{
}
/// <summary>
/// Deletes a data type folder identified by the provided Id.
/// </summary>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <param name="id">The unique identifier of the data type folder to delete.</param>
/// <returns>An <see cref="IActionResult"/> representing the result of the delete operation.</returns>
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -7,9 +7,17 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
/// <summary>
/// API controller responsible for handling requests to update data type folders in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class UpdateDataTypeFolderController : DataTypeFolderControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="UpdateDataTypeFolderController"/> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context and authentication.</param>
/// <param name="dataTypeContainerService">Service used to manage data type folders (containers).</param>
public UpdateDataTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IDataTypeContainerService dataTypeContainerService)
@@ -7,6 +7,9 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Controller for checking whether a data type is currently in use.
/// </summary>
[ApiVersion("1.0")]
public class IsUsedDataTypeController : DataTypeControllerBase
{
@@ -17,6 +20,14 @@ public class IsUsedDataTypeController : DataTypeControllerBase
_dataTypeUsageService = dataTypeUsageService;
}
/// <summary>
/// Determines whether the data type specified by the given <paramref name="id"/> is currently used in any content, media, or member types.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the data type to check for usage.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing a boolean value: <c>true</c> if the data type is used; <c>false</c> otherwise. Returns <see cref="StatusCodes.Status404NotFound"/> if the data type does not exist.
/// </returns>
[HttpGet("{id:guid}/is-used")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
@@ -0,0 +1,39 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.ViewModels.Item;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Item;
[ApiVersion("1.0")]
public class AncestorsDataTypeItemController : DatatypeItemControllerBase
{
private readonly IItemAncestorService _itemAncestorService;
public AncestorsDataTypeItemController(IItemAncestorService itemAncestorService)
=> _itemAncestorService = itemAncestorService;
[HttpGet("ancestors")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<ItemAncestorsResponseModel<NamedItemResponseModel>>), StatusCodes.Status200OK)]
[EndpointSummary("Gets ancestors for a collection of data type items.")]
[EndpointDescription("Gets the ancestor chains for data type items identified by the provided Ids.")]
public async Task<IActionResult> Ancestors(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<ItemAncestorsResponseModel<NamedItemResponseModel>>());
}
IEnumerable<ItemAncestorsResponseModel<NamedItemResponseModel>> result = await _itemAncestorService.GetAncestorsAsync(
UmbracoObjectTypes.DataType,
UmbracoObjectTypes.DataTypeContainer,
ids);
return Ok(result);
}
}
@@ -1,9 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Item;
/// <summary>
/// Serves as the base controller for operations related to data type items in the management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.DataType}")]
[ApiExplorerSettings(GroupName = "Data Type")]
public class DatatypeItemControllerBase : ManagementApiControllerBase
@@ -8,12 +8,20 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Item;
/// <summary>
/// API controller responsible for managing individual data type items within the Umbraco CMS management interface.
/// </summary>
[ApiVersion("1.0")]
public class ItemDatatypeItemController : DatatypeItemControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IUmbracoMapper _mapper;
/// <summary>
/// Initializes a new instance of the <see cref="ItemDatatypeItemController"/> class, which manages item-level operations for data types in the Umbraco CMS Management API.
/// </summary>
/// <param name="dataTypeService">Service used to manage and retrieve data type information.</param>
/// <param name="mapper">The Umbraco mapper used for mapping between domain and API models.</param>
public ItemDatatypeItemController(IDataTypeService dataTypeService, IUmbracoMapper mapper)
{
_dataTypeService = dataTypeService;
@@ -9,6 +9,9 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Item;
/// <summary>
/// Controller responsible for handling search operations for data type items in the management API.
/// </summary>
[ApiVersion("1.0")]
public class SearchDataTypeItemController : DatatypeItemControllerBase
{
@@ -16,6 +19,12 @@ public class SearchDataTypeItemController : DatatypeItemControllerBase
private readonly IDataTypeService _dataTypeService;
private readonly IUmbracoMapper _mapper;
/// <summary>
/// Initializes a new instance of the <see cref="SearchDataTypeItemController"/> class, which handles search operations for data type items.
/// </summary>
/// <param name="entitySearchService">Service used to perform entity search operations.</param>
/// <param name="dataTypeService">Service for managing data types.</param>
/// <param name="mapper">The mapper used to convert between domain and API models.</param>
public SearchDataTypeItemController(IEntitySearchService entitySearchService, IDataTypeService dataTypeService, IUmbracoMapper mapper)
{
_entitySearchService = entitySearchService;
@@ -23,6 +32,14 @@ public class SearchDataTypeItemController : DatatypeItemControllerBase
_mapper = mapper;
}
/// <summary>
/// Searches for data type items matching the specified query, with support for pagination.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="query">The search query used to filter data type items.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set (used for pagination).</param>
/// <param name="take">The maximum number of items to return in the result set (used for pagination).</param>
/// <returns>A task representing the asynchronous operation. The task result contains an <see cref="IActionResult"/> with a <see cref="PagedModel{DataTypeItemResponseModel}"/> containing the search results.</returns>
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<DataTypeItemResponseModel>), StatusCodes.Status200OK)]
@@ -12,6 +12,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Controller responsible for moving data types within the system.
/// </summary>
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class MoveDataTypeController : DataTypeControllerBase
@@ -19,12 +22,25 @@ public class MoveDataTypeController : DataTypeControllerBase
private readonly IDataTypeService _dataTypeService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="MoveDataTypeController"/> class.
/// </summary>
/// <param name="dataTypeService">Service used to manage data types.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public MoveDataTypeController(IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_dataTypeService = dataTypeService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Moves an existing data type identified by the specified <paramref name="id"/> to a different container.
/// The target container Id must be provided in the <paramref name="moveDataTypeRequestModel"/>.
/// </summary>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <param name="id">The unique identifier of the data type to move.</param>
/// <param name="moveDataTypeRequestModel">The request model containing the target container information.</param>
/// <returns>An <see cref="IActionResult"/> indicating the result of the move operation.</returns>
[HttpPut("{id:guid}/move")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -9,12 +9,20 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.References;
/// <summary>
/// Controller responsible for managing and retrieving information about where specific data types are referenced within the system.
/// </summary>
[ApiVersion("1.0")]
public class ReferencedByDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ReferencedByDataTypeController"/> class, which handles API requests related to data types referenced by other entities.
/// </summary>
/// <param name="dataTypeService">Service used to manage and retrieve data type information.</param>
/// <param name="relationTypePresentationFactory">Factory for creating presentation models for relation types.</param>
public ReferencedByDataTypeController(IDataTypeService dataTypeService, IRelationTypePresentationFactory relationTypePresentationFactory)
{
_dataTypeService = dataTypeService;
@@ -22,8 +30,15 @@ public class ReferencedByDataTypeController : DataTypeControllerBase
}
/// <summary>
/// Gets a paged list of references for the current data type, so you can see where it is being used.
/// Gets a paged list of entities that reference the specified data type, allowing you to see where it is being used.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the data type to find references for.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set (used for paging).</param>
/// <param name="take">The maximum number of items to return (used for paging).</param>
/// <returns>
/// A task representing the asynchronous operation. The result contains an <see cref="ActionResult{T}"/> with a <see cref="PagedViewModel{IReferenceResponseModel}"/> listing entities that reference the specified data type.
/// </returns>
[HttpGet("{id:guid}/referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
@@ -0,0 +1,54 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Controller for retrieving data type value schemas.
/// </summary>
[ApiVersion("1.0")]
public class SchemaDataTypeController : DataTypeControllerBase
{
private readonly IPropertyEditorSchemaService _schemaService;
/// <summary>
/// Initializes a new instance of the <see cref="SchemaDataTypeController"/> class.
/// </summary>
/// <param name="schemaService">The property editor schema service.</param>
public SchemaDataTypeController(IPropertyEditorSchemaService schemaService)
=> _schemaService = schemaService;
/// <summary>
/// Gets the value schema for a data type.
/// </summary>
/// <param name="id">The unique identifier of the data type.</param>
/// <returns>The schema information for the data type's values.</returns>
/// <remarks>
/// Returns schema information for property editors that implement <c>IValueSchemaProvider</c>.
/// Returns 404 if the data type is not found or doesn't support schema information.
/// </remarks>
[HttpGet("{id:guid}/schema")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DataTypeSchemaResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Schema(Guid id)
{
Attempt<PropertyValueSchema, PropertyEditorSchemaOperationStatus> attempt = await _schemaService.GetSchemaAsync(id);
if (attempt.Success is false)
{
return PropertyEditorSchemaOperationStatusResult(attempt.Status);
}
PropertyValueSchema result = attempt.Result;
return Ok(new DataTypeSchemaResponseModel
{
ValueTypeName = result.ValueType?.FullName,
JsonSchema = result.JsonSchema,
});
}
}
@@ -8,15 +8,29 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
/// <summary>
/// Controller responsible for handling operations related to the ancestors tree structure of data types.
/// </summary>
[ApiVersion("1.0")]
public class AncestorsDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDataTypeTreeController"/> class, which provides API endpoints for retrieving ancestor data types in the tree structure.
/// </summary>
/// <param name="entityService">Service used for entity operations within the API.</param>
/// <param name="dataTypeService">Service used for data type management and retrieval.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public AncestorsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDataTypeTreeController"/> class, which manages operations related to ancestor data type trees in the Umbraco CMS.
/// </summary>
/// <param name="entityService">Service used for entity-related operations.</param>
/// <param name="flagProviders">A collection of providers that supply flags for tree nodes.</param>
/// <param name="dataTypeService">Service used for data type management operations.</param>
[ActivatorUtilitiesConstructor]
public AncestorsDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
@@ -9,21 +9,44 @@ using Umbraco.Cms.Api.Management.Services.Flags;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
/// <summary>
/// Controller responsible for handling operations related to the child nodes of the data type tree in the management API.
/// </summary>
[ApiVersion("1.0")]
public class ChildrenDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the system.</param>
/// <param name="dataTypeService">Service used for managing and retrieving data types.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public ChildrenDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the system.</param>
/// <param name="flagProviders">A collection of providers that supply additional flags or metadata for entities.</param>
/// <param name="dataTypeService">Service responsible for operations related to data types.</param>
[ActivatorUtilitiesConstructor]
public ChildrenDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
{
}
/// <summary>
/// Retrieves a paginated collection of data type tree items that are children of the specified parent ID.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="parentId">The unique identifier of the parent data type tree item whose children are to be retrieved.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set (used for pagination).</param>
/// <param name="take">The maximum number of items to return (used for pagination).</param>
/// <param name="foldersOnly">If set to <c>true</c>, only folder items will be included in the results.</param>
/// <returns>A <see cref="PagedViewModel{T}"/> containing <see cref="DataTypeTreeItemResponseModel"/> instances representing the child items.</returns>
[HttpGet("children")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
@@ -15,6 +15,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
/// <summary>
/// Serves as the base controller for handling operations related to data type trees in the Umbraco CMS Management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.DataType}")]
[ApiExplorerSettings(GroupName = "Data Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
@@ -22,6 +25,11 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase<DataTypeTreeI
{
private readonly IDataTypeService _dataTypeService;
/// <summary>
/// Initializes a new instance of the <see cref="DataTypeTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service for managing Umbraco entities.</param>
/// <param name="dataTypeService">Service for managing data types within Umbraco.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DataTypeTreeControllerBase(IEntityService entityService, IDataTypeService dataTypeService)
: this(
@@ -31,8 +39,30 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase<DataTypeTreeI
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DataTypeTreeControllerBase"/> class with the specified services.
/// </summary>
/// <param name="entityService">Service used for entity operations within the data type tree.</param>
/// <param name="flagProviders">A collection of providers that supply flags for entities.</param>
/// <param name="dataTypeService">Service used for managing data types.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public DataTypeTreeControllerBase(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders) =>
: this(
entityService,
flagProviders,
StaticServiceProvider.Instance.GetRequiredService<IEntitySearchService>(),
StaticServiceProvider.Instance.GetRequiredService<IIdKeyMap>(),
dataTypeService)
{
}
public DataTypeTreeControllerBase(
IEntityService entityService,
FlagProviderCollection flagProviders,
IEntitySearchService entitySearchService,
IIdKeyMap idKeyMap,
IDataTypeService dataTypeService)
: base(entityService, flagProviders, entitySearchService, idKeyMap) =>
_dataTypeService = dataTypeService;
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DataType;
@@ -9,21 +9,43 @@ using Umbraco.Cms.Api.Management.Services.Flags;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
/// <summary>
/// API controller responsible for handling operations related to the root of the data type tree in Umbraco.
/// </summary>
[ApiVersion("1.0")]
public class RootDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within Umbraco.</param>
/// <param name="dataTypeService">Service used for managing data types in Umbraco.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public RootDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RootDataTypeTreeController"/> class, which manages the root of the data type tree in the Umbraco management API.
/// </summary>
/// <param name="entityService">Service used for entity operations within the tree.</param>
/// <param name="flagProviders">A collection of providers that supply flags for tree nodes.</param>
/// <param name="dataTypeService">Service used for data type management and retrieval.</param>
[ActivatorUtilitiesConstructor]
public RootDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
{
}
/// <summary>
/// Retrieves a paginated list of data type items from the root of the tree, with optional folder-only filtering.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set.</param>
/// <param name="take">The maximum number of items to return.</param>
/// <param name="foldersOnly">If true, only folders are included in the results.</param>
/// <returns>A paged view model containing data type tree item response models.</returns>
[HttpGet("root")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
@@ -0,0 +1,30 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
[ApiVersion("1.0")]
public class SearchDataTypeTreeController : DataTypeTreeControllerBase
{
public SearchDataTypeTreeController(
IEntityService entityService,
FlagProviderCollection flagProviders,
IEntitySearchService entitySearchService,
IIdKeyMap idKeyMap,
IDataTypeService dataTypeService)
: base(entityService, flagProviders, entitySearchService, idKeyMap, dataTypeService)
{
}
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<DataTypeTreeItemResponseModel>>> Search(CancellationToken cancellationToken, string? query, int skip = 0, int take = 100, TreeItemKind itemKind = TreeItemKind.All)
=> await SearchTreeEntities(query, skip, take, itemKind);
}
@@ -8,20 +8,43 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
/// <summary>
/// Provides API endpoints for managing sibling data types in the Umbraco CMS tree.
/// </summary>
public class SiblingsDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within Umbraco.</param>
/// <param name="dataTypeService">Service used for managing data types in Umbraco.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SiblingsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDataTypeTreeController"/> class, which manages operations related to sibling data type trees in the Umbraco CMS.
/// </summary>
/// <param name="entityService">Service used for entity operations within the CMS.</param>
/// <param name="flagProviders">A collection of providers that supply flags for tree nodes.</param>
/// <param name="dataTypeService">Service used for managing data types.</param>
[ActivatorUtilitiesConstructor]
public SiblingsDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
{
}
/// <summary>
/// Gets a paged collection of data type tree items that are siblings of the specified data type identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="target">The unique identifier of the data type whose siblings are to be retrieved.</param>
/// <param name="before">The number of sibling items to retrieve before the target item.</param>
/// <param name="after">The number of sibling items to retrieve after the target item.</param>
/// <param name="foldersOnly">If set to <c>true</c>, only folders will be included in the results; otherwise, both folders and data types are returned.</param>
/// <returns>A task representing the asynchronous operation. The task result contains an <see cref="ActionResult{T}"/> with a <see cref="SubsetViewModel{T}"/> of <see cref="DataTypeTreeItemResponseModel"/> representing the sibling items.</returns>
[HttpGet("siblings")]
[ProducesResponseType(typeof(SubsetViewModel<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of data type tree sibling items.")]
@@ -13,6 +13,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// API controller responsible for handling requests to update data types in the Umbraco CMS management interface.
/// </summary>
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class UpdateDataTypeController : DataTypeControllerBase
@@ -21,6 +24,12 @@ public class UpdateDataTypeController : DataTypeControllerBase
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private IDataTypePresentationFactory _dataTypePresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateDataTypeController"/> class, responsible for handling data type update operations in the management API.
/// </summary>
/// <param name="dataTypeService">Service used to manage data types.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="dataTypePresentationFactory">Factory for creating data type presentation models.</param>
public UpdateDataTypeController(IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDataTypePresentationFactory dataTypePresentationFactory)
{
_dataTypeService = dataTypeService;
@@ -28,6 +37,13 @@ public class UpdateDataTypeController : DataTypeControllerBase
_dataTypePresentationFactory = dataTypePresentationFactory;
}
/// <summary>
/// Updates the data type with the specified ID using the provided request model.
/// </summary>
/// <param name="cancellationToken">Token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the data type to update.</param>
/// <param name="updateDataTypeViewModel">The model containing the updated data type details.</param>
/// <returns>An <see cref="IActionResult"/> indicating the result of the update operation.</returns>
[HttpPut("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -10,18 +10,34 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// API controller responsible for retrieving and managing all dictionary items within the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class AllDictionaryController : DictionaryControllerBase
{
private readonly IDictionaryItemService _dictionaryItemService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="AllDictionaryController"/> class.
/// </summary>
/// <param name="dictionaryItemService">An instance of <see cref="IDictionaryItemService"/> used to manage dictionary items.</param>
/// <param name="umbracoMapper">An instance of <see cref="IUmbracoMapper"/> used for mapping between models.</param>
public AllDictionaryController(IDictionaryItemService dictionaryItemService, IUmbracoMapper umbracoMapper)
{
_dictionaryItemService = dictionaryItemService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Retrieves a paginated list of dictionary items, optionally filtered by name.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="filter">An optional string to filter dictionary items by name.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set.</param>
/// <param name="take">The maximum number of items to return.</param>
/// <returns>A paginated view model containing dictionary overview response models.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<DictionaryOverviewResponseModel>), StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
@@ -8,18 +8,36 @@ using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// API controller for managing Umbraco dictionary items by their unique key.
/// Provides endpoints for retrieving, updating, and deleting dictionary entries identified by key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyDictionaryController : DictionaryControllerBase
{
private readonly IDictionaryItemService _dictionaryItemService;
private readonly IDictionaryPresentationFactory _dictionaryPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ByKeyDictionaryController"/> class, which manages dictionary items by key.
/// </summary>
/// <param name="dictionaryItemService">The <see cref="IDictionaryItemService"/> used to manage dictionary items.</param>
/// <param name="dictionaryPresentationFactory">The <see cref="IDictionaryPresentationFactory"/> used to create dictionary item presentations.</param>
public ByKeyDictionaryController(IDictionaryItemService dictionaryItemService, IDictionaryPresentationFactory dictionaryPresentationFactory)
{
_dictionaryItemService = dictionaryItemService;
_dictionaryPresentationFactory = dictionaryPresentationFactory;
}
/// <summary>
/// Retrieves a dictionary item by its unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the dictionary item to retrieve.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing the <see cref="DictionaryItemResponseModel"/> if the item is found;
/// otherwise, a 404 Not Found response.
/// </returns>
[HttpGet($"{{{nameof(id)}:guid}}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DictionaryItemResponseModel), StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -15,6 +15,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// API controller responsible for handling requests to create new dictionary items in Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class CreateDictionaryController : DictionaryControllerBase
{
@@ -23,6 +26,14 @@ public class CreateDictionaryController : DictionaryControllerBase
private readonly IDictionaryPresentationFactory _dictionaryPresentationFactory;
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Dictionary.CreateDictionaryController"/> class,
/// providing dependencies required for managing dictionary items in the Umbraco back office API.
/// </summary>
/// <param name="dictionaryItemService">Service for managing dictionary items.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="dictionaryPresentationFactory">Factory for creating dictionary presentation models.</param>
/// <param name="authorizationService">Service for handling authorization checks.</param>
public CreateDictionaryController(
IDictionaryItemService dictionaryItemService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
@@ -35,6 +46,12 @@ public class CreateDictionaryController : DictionaryControllerBase
_authorizationService = authorizationService;
}
/// <summary>
/// Creates a new dictionary item using the details provided in the request model.
/// </summary>
/// <param name="cancellationToken">Token to monitor for cancellation requests.</param>
/// <param name="createDictionaryItemRequestModel">The model containing the details of the dictionary item to create, including translations.</param>
/// <returns>An <see cref="IActionResult"/> indicating the result of the create operation, including possible status codes for success or failure.</returns>
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
@@ -9,12 +9,20 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// Controller responsible for handling requests to delete dictionary items in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class DeleteDictionaryController : DictionaryControllerBase
{
private readonly IDictionaryItemService _dictionaryItemService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteDictionaryController"/> class, used for handling requests to delete dictionary items in the Umbraco CMS.
/// </summary>
/// <param name="dictionaryItemService">Service for managing dictionary items.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public DeleteDictionaryController(IDictionaryItemService dictionaryItemService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_dictionaryItemService = dictionaryItemService;
@@ -8,6 +8,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// Serves as the base controller for API endpoints that manage dictionary-related operations in the Umbraco CMS.
/// </summary>
[VersionedApiBackOfficeRoute("dictionary")]
[ApiExplorerSettings(GroupName = "Dictionary")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDictionary)]
@@ -1,4 +1,4 @@
using System.Net.Mime;
using System.Net.Mime;
using System.Text;
using System.Xml.Linq;
using Asp.Versioning;
@@ -10,18 +10,36 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// API controller responsible for exporting dictionary entries from the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class ExportDictionaryController : DictionaryControllerBase
{
private readonly IDictionaryItemService _dictionaryItemService;
private readonly IEntityXmlSerializer _entityXmlSerializer;
/// <summary>
/// Initializes a new instance of the <see cref="ExportDictionaryController"/> class, providing services for dictionary item management and XML serialization.
/// </summary>
/// <param name="dictionaryItemService">Service used to manage dictionary items.</param>
/// <param name="entityXmlSerializer">Service used to serialize entities to XML.</param>
public ExportDictionaryController(IDictionaryItemService dictionaryItemService, IEntityXmlSerializer entityXmlSerializer)
{
_dictionaryItemService = dictionaryItemService;
_entityXmlSerializer = entityXmlSerializer;
}
/// <summary>
/// Exports the dictionary item identified by the provided <paramref name="id"/> as a downloadable file.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the dictionary item to export.</param>
/// <param name="includeChildren">If <c>true</c>, child dictionary items will also be included in the export; otherwise, only the specified item is exported.</param>
/// <returns>
/// A <see cref="FileContentResult"/> containing the exported dictionary data as a file if the dictionary item is found;
/// otherwise, a <see cref="NotFoundResult"/> if the item does not exist.
/// </returns>
[HttpGet("{id:guid}/export")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
@@ -11,12 +11,20 @@ using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// Provides API endpoints for importing dictionary items into the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class ImportDictionaryController : DictionaryControllerBase
{
private readonly IDictionaryItemImportService _dictionaryItemImportService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="ImportDictionaryController"/> class, which handles dictionary item import operations in the Umbraco backoffice API.
/// </summary>
/// <param name="dictionaryItemImportService">Service used to import dictionary items.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public ImportDictionaryController(
IDictionaryItemImportService dictionaryItemImportService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
@@ -25,6 +33,15 @@ public class ImportDictionaryController : DictionaryControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Imports a dictionary from a provided UDT file upload.
/// </summary>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the import operation.</param>
/// <param name="importDictionaryRequestModel">The model containing the uploaded UDT file and optional parent dictionary item information.</param>
/// <returns>
/// An <see cref="IActionResult"/> indicating the result of the import operation:
/// returns <c>201 Created</c> on success, <c>400 Bad Request</c> for invalid file types or content, and <c>404 Not Found</c> if the parent or file is missing.
/// </returns>
[HttpPost("import")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -1,9 +1,13 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Item;
/// <summary>
/// Serves as the base controller for managing dictionary items in the Umbraco CMS Management API.
/// Provides common functionality and endpoints for derived controllers handling dictionary item operations.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/dictionary")]
[ApiExplorerSettings(GroupName = "Dictionary")]
public class DictionaryItemControllerBase : ManagementApiControllerBase
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Dictionary.Item;
@@ -8,18 +8,32 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Item;
/// <summary>
/// Provides API endpoints for managing individual dictionary items within the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class ItemDictionaryItemController : DictionaryItemControllerBase
{
private readonly IDictionaryItemService _dictionaryItemService;
private readonly IUmbracoMapper _mapper;
/// <summary>
/// Initializes a new instance of the <see cref="ItemDictionaryItemController"/> class with the specified services.
/// </summary>
/// <param name="dictionaryItemService">An instance of <see cref="IDictionaryItemService"/> used to manage dictionary items.</param>
/// <param name="mapper">An instance of <see cref="IUmbracoMapper"/> used for mapping objects.</param>
public ItemDictionaryItemController(IDictionaryItemService dictionaryItemService, IUmbracoMapper mapper)
{
_dictionaryItemService = dictionaryItemService;
_mapper = mapper;
}
/// <summary>
/// Retrieves a collection of dictionary items matching the specified IDs.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="ids">A set of dictionary item IDs to retrieve.</param>
/// <returns>A task representing the asynchronous operation. The result contains an <see cref="IActionResult"/> with the collection of dictionary items.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<DictionaryItemItemResponseModel>), StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
@@ -10,12 +10,20 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// Provides API endpoints for moving dictionary items within the Umbraco CMS management interface.
/// </summary>
[ApiVersion("1.0")]
public class MoveDictionaryController : DictionaryControllerBase
{
private readonly IDictionaryItemService _dictionaryItemService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="MoveDictionaryController"/> class, responsible for handling dictionary item move operations in the management API.
/// </summary>
/// <param name="dictionaryItemService">Service used to manage dictionary items.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public MoveDictionaryController(IDictionaryItemService dictionaryItemService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_dictionaryItemService = dictionaryItemService;
@@ -8,21 +8,41 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
/// <summary>
/// Controller responsible for managing and exposing operations related to the ancestors of dictionary items in the dictionary tree.
/// </summary>
[ApiVersion("1.0")]
public class AncestorsDictionaryTreeController : DictionaryTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within Umbraco.</param>
/// <param name="dictionaryItemService">Service used for managing dictionary items in the Umbraco dictionary tree.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public AncestorsDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: base(entityService, dictionaryItemService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDictionaryTreeController"/> class, which handles operations related to retrieving ancestor dictionary tree items.
/// </summary>
/// <param name="entityService">The service used for entity operations.</param>
/// <param name="flagProviders">A collection of providers for entity flags.</param>
/// <param name="dictionaryItemService">The service used for dictionary item operations.</param>
[ActivatorUtilitiesConstructor]
public AncestorsDictionaryTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDictionaryItemService dictionaryItemService)
: base(entityService, flagProviders, dictionaryItemService)
{
}
/// <summary>
/// Retrieves all ancestor dictionary items for the specified descendant item.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="descendantId">The unique identifier of the descendant dictionary item whose ancestors are to be retrieved.</param>
/// <returns>A task representing the asynchronous operation. The task result contains an <see cref="ActionResult{T}"/> with a collection of ancestor <see cref="NamedEntityTreeItemResponseModel"/> items.</returns>
[HttpGet("ancestors")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<NamedEntityTreeItemResponseModel>), StatusCodes.Status200OK)]
@@ -10,21 +10,43 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
/// <summary>
/// API controller responsible for managing and retrieving the child nodes of dictionary items in the Umbraco dictionary tree.
/// </summary>
[ApiVersion("1.0")]
public class ChildrenDictionaryTreeController : DictionaryTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the system.</param>
/// <param name="dictionaryItemService">Service used for managing dictionary items.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public ChildrenDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: base(entityService, dictionaryItemService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service for managing and retrieving entities within Umbraco.</param>
/// <param name="flagProviders">A collection of providers that supply additional flags or metadata for entities.</param>
/// <param name="dictionaryItemService">Service for managing dictionary items used for localization.</param>
[ActivatorUtilitiesConstructor]
public ChildrenDictionaryTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDictionaryItemService dictionaryItemService)
: base(entityService, flagProviders, dictionaryItemService)
{
}
/// <summary>
/// Retrieves a paginated list of dictionary tree items that are direct children of the specified parent dictionary item.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="parentId">The unique identifier of the parent dictionary item whose children are to be retrieved.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set (used for pagination).</param>
/// <param name="take">The maximum number of items to return (used for pagination).</param>
/// <returns>A paged view model containing the child dictionary tree items.</returns>
[HttpGet("children")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<NamedEntityTreeItemResponseModel>), StatusCodes.Status200OK)]
@@ -14,6 +14,10 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
/// <summary>
/// Serves as the base controller for operations related to dictionary tree structures in the Umbraco CMS Management API.
/// Provides common functionality for derived controllers managing dictionary items in a hierarchical format.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/dictionary")]
[ApiExplorerSettings(GroupName = "Dictionary")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDictionaryOrTemplates)]
@@ -21,6 +25,11 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
// tree controller base. We'll keep it though, in the hope that we can mend EntityService.
public class DictionaryTreeControllerBase : NamedEntityTreeControllerBase<NamedEntityTreeItemResponseModel>
{
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the Umbraco system.</param>
/// <param name="dictionaryItemService">Service used for managing and retrieving dictionary items for localization.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DictionaryTreeControllerBase(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: this(
@@ -30,7 +39,16 @@ public class DictionaryTreeControllerBase : NamedEntityTreeControllerBase<NamedE
{
}
public DictionaryTreeControllerBase(IEntityService entityService, FlagProviderCollection flagProviders, IDictionaryItemService dictionaryItemService)
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service for managing entities within the system.</param>
/// <param name="flagProviders">A collection of providers for entity flags.</param>
/// <param name="dictionaryItemService">Service for managing dictionary items.</param>
public DictionaryTreeControllerBase(
IEntityService entityService,
FlagProviderCollection flagProviders,
IDictionaryItemService dictionaryItemService)
: base(entityService, flagProviders) =>
DictionaryItemService = dictionaryItemService;
@@ -10,20 +10,44 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
/// <summary>
/// Controller for managing the root of the dictionary tree in the Umbraco CMS Management API.
/// </summary>
[ApiVersion("1.0")]
public class RootDictionaryTreeController : DictionaryTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the dictionary tree.</param>
/// <param name="dictionaryItemService">Service used for managing dictionary items.</param>
public RootDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: base(entityService, dictionaryItemService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RootDictionaryTreeController"/> class, which manages the root nodes of the dictionary tree in the Umbraco management API.
/// </summary>
/// <param name="entityService">Service for managing entities within Umbraco.</param>
/// <param name="flagProviders">A collection of providers that supply flags for entities.</param>
/// <param name="dictionaryItemService">Service for managing dictionary items.</param>
[ActivatorUtilitiesConstructor]
public RootDictionaryTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDictionaryItemService dictionaryItemService)
: base(entityService, flagProviders, dictionaryItemService)
{
}
/// <summary>
/// Retrieves a paginated collection of dictionary items from the root of the dictionary tree.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="skip">The number of items to skip for pagination. Defaults to 0.</param>
/// <param name="take">The maximum number of items to return for pagination. Defaults to 100.</param>
/// <returns>
/// An <see cref="ActionResult{T}"/> containing a <see cref="PagedViewModel{NamedEntityTreeItemResponseModel}"/>,
/// which represents the paginated dictionary items from the root of the tree.
/// </returns>
[HttpGet("root")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<NamedEntityTreeItemResponseModel>), StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -16,6 +16,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
/// <summary>
/// API controller responsible for handling requests to update dictionary items in the Umbraco CMS management interface.
/// </summary>
[ApiVersion("1.0")]
public class UpdateDictionaryController : DictionaryControllerBase
{
@@ -24,6 +27,13 @@ public class UpdateDictionaryController : DictionaryControllerBase
private readonly IDictionaryPresentationFactory _dictionaryPresentationFactory;
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateDictionaryController"/> class, which handles API requests for updating dictionary items in Umbraco.
/// </summary>
/// <param name="dictionaryItemService">Service for managing dictionary items.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="dictionaryPresentationFactory">Factory for creating dictionary item presentation models.</param>
/// <param name="authorizationService">Service for handling authorization checks.</param>
public UpdateDictionaryController(
IDictionaryItemService dictionaryItemService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
@@ -36,6 +46,13 @@ public class UpdateDictionaryController : DictionaryControllerBase
_authorizationService = authorizationService;
}
/// <summary>
/// Updates an existing dictionary item with the specified identifier using the provided details.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the dictionary item to update.</param>
/// <param name="updateDictionaryItemRequestModel">The model containing the updated dictionary item details.</param>
/// <returns>An <see cref="IActionResult"/> representing the result of the update operation.</returns>
[HttpPut($"{{{nameof(id)}:guid}}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -16,6 +16,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Provides API endpoints for retrieving and managing available segments associated with documents.
/// </summary>
[Obsolete("This controller is temporary. A more permanent solution will follow. Scheduled for removal in Umbraco 20.")]
[ApiVersion("1.0")]
public class AvailableSegmentsController : DocumentControllerBase
@@ -24,6 +27,12 @@ public class AvailableSegmentsController : DocumentControllerBase
private readonly ISegmentService _segmentService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="AvailableSegmentsController"/> class, which manages API endpoints for retrieving available document segments in Umbraco.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to controller actions.</param>
/// <param name="segmentService">Service for retrieving and managing document segments.</param>
/// <param name="umbracoMapper">The mapper used to convert between Umbraco domain models and API models.</param>
public AvailableSegmentsController(
IAuthorizationService authorizationService,
ISegmentService segmentService,
@@ -34,6 +43,14 @@ public class AvailableSegmentsController : DocumentControllerBase
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Retrieves a paged collection of available content segments for a specified document.
/// </summary>
/// <param name="id">The unique identifier of the document for which to retrieve available segments.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="skip">The number of segments to skip before starting to collect the result set (used for paging).</param>
/// <param name="take">The maximum number of segments to return (used for paging).</param>
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="IActionResult"/> with a <see cref="PagedViewModel{SegmentResponseModel}"/> representing the paged collection of available segments.</returns>
[HttpGet("{id:guid}/available-segment-options")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<SegmentResponseModel>), StatusCodes.Status200OK)]
@@ -12,6 +12,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller for managing documents in Umbraco that are identified by their unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyDocumentController : DocumentControllerBase
{
@@ -19,6 +22,12 @@ public class ByKeyDocumentController : DocumentControllerBase
private readonly IDocumentPresentationFactory _documentPresentationFactory;
private readonly IContentQueryService _contentQueryService;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.ByKeyDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to document resources.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
/// <param name="contentQueryService">Service for querying content data within the CMS.</param>
public ByKeyDocumentController(
IAuthorizationService authorizationService,
IDocumentPresentationFactory documentPresentationFactory,
@@ -29,6 +38,15 @@ public class ByKeyDocumentController : DocumentControllerBase
_contentQueryService = contentQueryService;
}
/// <summary>
/// Retrieves a document by its unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique <see cref="Guid"/> of the document to retrieve.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing a <see cref="DocumentResponseModel"/> if the document is found;
/// otherwise, a 404 Not Found or 403 Forbidden error response.
/// </returns>
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DocumentResponseModel), StatusCodes.Status200OK)]
@@ -13,6 +13,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Provides API endpoints for managing published documents identified by their unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyPublishedDocumentController : DocumentControllerBase
{
@@ -20,6 +23,12 @@ public class ByKeyPublishedDocumentController : DocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IDocumentPresentationFactory _documentPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ByKeyPublishedDocumentController"/> class, which handles published document operations by key.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to document operations.</param>
/// <param name="contentEditingService">Service used for editing content.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
public ByKeyPublishedDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
@@ -30,6 +39,15 @@ public class ByKeyPublishedDocumentController : DocumentControllerBase
_documentPresentationFactory = documentPresentationFactory;
}
/// <summary>
/// Retrieves a published document identified by the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the document to retrieve.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing the <see cref="PublishedDocumentResponseModel"/> if the published document is found;
/// otherwise, returns <c>404 Not Found</c> if the document does not exist or is not published, or <c>403 Forbidden</c> if the user is not authorized.
/// </returns>
[HttpGet("{id:guid}/published")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PublishedDocumentResponseModel), StatusCodes.Status200OK)]
@@ -16,6 +16,9 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Document.Collection;
/// <summary>
/// Controller responsible for managing collections of documents identified by their unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyDocumentCollectionController : DocumentCollectionControllerBase
{
@@ -23,6 +26,15 @@ public class ByKeyDocumentCollectionController : DocumentCollectionControllerBas
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IDocumentCollectionPresentationFactory _documentCollectionPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.Collection.ByKeyDocumentCollectionController"/> class,
/// which handles document collection operations by document key.
/// </summary>
/// <param name="contentListViewService">Service for retrieving and managing content list views.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="mapper">The Umbraco object mapper used for mapping between models.</param>
/// <param name="documentCollectionPresentationFactory">Factory for creating document collection presentation models.</param>
/// <param name="flagProviders">A collection of providers for document collection flags.</param>
[ActivatorUtilitiesConstructor]
public ByKeyDocumentCollectionController(
IContentListViewService contentListViewService,
@@ -37,6 +49,13 @@ public class ByKeyDocumentCollectionController : DocumentCollectionControllerBas
_documentCollectionPresentationFactory = documentCollectionPresentationFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.Collection.ByKeyDocumentCollectionController"/> class.
/// </summary>
/// <param name="contentListViewService">Service for managing content list views.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security operations.</param>
/// <param name="mapper">Maps Umbraco objects to API models.</param>
/// <param name="documentCollectionPresentationFactory">Factory for creating document collection presentation models.</param>
[Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 18.")]
public ByKeyDocumentCollectionController(
IContentListViewService contentListViewService,
@@ -52,6 +71,19 @@ public class ByKeyDocumentCollectionController : DocumentCollectionControllerBas
{
}
/// <summary>
/// Retrieves a paged collection of documents identified by the provided unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (key) of the document collection.</param>
/// <param name="dataTypeId">An optional data type identifier to filter the collection.</param>
/// <param name="orderBy">The field by which to order the collection. Defaults to <c>"updateDate"</c>.</param>
/// <param name="orderCulture">An optional culture code to use for ordering.</param>
/// <param name="orderDirection">The direction to order the collection. Defaults to <see cref="Direction.Ascending"/>.</param>
/// <param name="filter">An optional filter string to filter the collection.</param>
/// <param name="skip">The number of items to skip for paging. Defaults to 0.</param>
/// <param name="take">The number of items to take for paging. Defaults to 100.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. The task result contains an <see cref="IActionResult"/> with a paged list of <see cref="DocumentCollectionResponseModel"/>.</returns>
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<DocumentCollectionResponseModel>), StatusCodes.Status200OK)]
@@ -13,6 +13,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Document.Collection;
/// <summary>
/// Serves as the base controller for handling operations related to document collections within the Umbraco CMS Management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Document}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
@@ -6,15 +6,27 @@ using Umbraco.Cms.Api.Management.ViewModels.Document;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// API controller responsible for handling operations related to configuration documents within the Umbraco CMS management area.
/// </summary>
[ApiVersion("1.0")]
public class ConfigurationDocumentController : DocumentControllerBase
{
private readonly IConfigurationPresentationFactory _configurationPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationDocumentController"/> class, responsible for managing configuration documents.
/// </summary>
/// <param name="configurationPresentationFactory">Factory used to create configuration presentation models for documents.</param>
public ConfigurationDocumentController(
IConfigurationPresentationFactory configurationPresentationFactory) =>
_configurationPresentationFactory = configurationPresentationFactory;
/// <summary>
/// Retrieves the configuration settings for documents.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An <see cref="IActionResult"/> containing a <see cref="DocumentConfigurationResponseModel"/> with the document configuration settings.</returns>
[HttpGet("configuration")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DocumentConfigurationResponseModel), StatusCodes.Status200OK)]
@@ -16,6 +16,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// API controller for handling copy operations on documents in Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class CopyDocumentController : DocumentControllerBase
{
@@ -23,6 +26,12 @@ public class CopyDocumentController : DocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.CopyDocumentController"/> class, which handles document copy operations in the management API.
/// </summary>
/// <param name="authorizationService">Service used to authorize user actions.</param>
/// <param name="contentEditingService">Service for editing and managing content.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public CopyDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
@@ -33,6 +42,13 @@ public class CopyDocumentController : DocumentControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Creates a copy of an existing document identified by the specified <paramref name="id"/>.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the document to copy.</param>
/// <param name="copyDocumentRequestModel">The request model containing details about the copy operation, such as the target location and copy options.</param>
/// <returns>An <see cref="IActionResult"/> representing the result of the copy operation.</returns>
[HttpPost("{id:guid}/copy")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -12,6 +12,9 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// API controller responsible for handling operations related to the creation of content documents in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class CreateDocumentController : CreateDocumentControllerBase
{
@@ -19,6 +22,13 @@ public class CreateDocumentController : CreateDocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="CreateDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to document creation operations.</param>
/// <param name="documentEditingPresentationFactory">Factory for creating document editing presentation models.</param>
/// <param name="contentEditingService">Service responsible for content editing functionality.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public CreateDocumentController(
IAuthorizationService authorizationService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
@@ -31,6 +41,12 @@ public class CreateDocumentController : CreateDocumentControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Creates a new document using the specified request model.
/// </summary>
/// <param name="cancellationToken">Token to monitor for cancellation requests.</param>
/// <param name="requestModel">The details of the document to create.</param>
/// <returns>An <see cref="IActionResult"/> representing the result of the operation.</returns>
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Security.Authorization.Content;
using Umbraco.Cms.Api.Management.ViewModels.Document;
@@ -9,6 +9,10 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Serves as the base controller for document creation endpoints in the Umbraco CMS Management API.
/// Provides shared functionality for creating documents.
/// </summary>
public abstract class CreateDocumentControllerBase : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -16,6 +16,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// API controller responsible for handling requests to create documents with public access permissions in Umbraco.
/// </summary>
[ApiVersion("1.0")]
public class CreatePublicAccessDocumentController : DocumentControllerBase
{
@@ -23,6 +26,12 @@ public class CreatePublicAccessDocumentController : DocumentControllerBase
private readonly IPublicAccessPresentationFactory _publicAccessPresentationFactory;
private readonly IPublicAccessService _publicAccessService;
/// <summary>
/// Initializes a new instance of the <see cref="CreatePublicAccessDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to perform permission checks.</param>
/// <param name="publicAccessPresentationFactory">Factory for creating public access presentation models.</param>
/// <param name="publicAccessService">Service for managing public access settings.</param>
public CreatePublicAccessDocumentController(
IAuthorizationService authorizationService,
IPublicAccessPresentationFactory publicAccessPresentationFactory,
@@ -33,6 +42,19 @@ public class CreatePublicAccessDocumentController : DocumentControllerBase
_publicAccessService = publicAccessService;
}
/// <summary>
/// Creates public access rules for the specified document, restricting or allowing access based on the provided access details.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the document to protect.</param>
/// <param name="publicAccessRequestModel">The model containing the public access configuration for the document.</param>
/// <returns>
/// An <see cref="IActionResult"/> indicating the result of the operation:
/// <list type="bullet">
/// <item><description><c>201 Created</c> if the public access rules were successfully created.</description></item>
/// <item><description><c>404 Not Found</c> if the document does not exist.</description></item>
/// </list>
/// </returns>
[MapToApiVersion("1.0")]
[HttpPost("{id:guid}/public-access")]
[ProducesResponseType(StatusCodes.Status201Created)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -15,6 +15,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// API controller responsible for handling requests to delete documents in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class DeleteDocumentController : DocumentControllerBase
{
@@ -22,6 +25,12 @@ public class DeleteDocumentController : DocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">An <see cref="IAuthorizationService"/> used to authorize document deletion requests.</param>
/// <param name="contentEditingService">An <see cref="IContentEditingService"/> used to perform content editing operations.</param>
/// <param name="backOfficeSecurityAccessor">An <see cref="IBackOfficeSecurityAccessor"/> providing access to back office security information.</param>
public DeleteDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
@@ -32,6 +41,12 @@ public class DeleteDocumentController : DocumentControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Deletes the document with the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the document to delete.</param>
/// <returns>An <see cref="IActionResult"/> indicating the outcome of the delete operation.</returns>
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -13,18 +13,35 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller for deleting public access settings from documents.
/// </summary>
[ApiVersion("1.0")]
public class DeletePublicAccessDocumentController : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IPublicAccessService _publicAccessService;
/// <summary>
/// Initializes a new instance of the <see cref="DeletePublicAccessDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to document operations.</param>
/// <param name="publicAccessService">Service used to manage public access settings for documents.</param>
public DeletePublicAccessDocumentController(IAuthorizationService authorizationService, IPublicAccessService publicAccessService)
{
_authorizationService = authorizationService;
_publicAccessService = publicAccessService;
}
/// <summary>
/// Removes public access protection and rules from the specified document.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the document from which to remove public access.</param>
/// <returns>
/// An <see cref="IActionResult"/> indicating the result of the operation:
/// returns <c>200 OK</c> if successful, or <c>404 Not Found</c> if the document or public access settings do not exist.
/// </returns>
[MapToApiVersion("1.0")]
[HttpDelete("{id:guid}/public-access")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -12,6 +12,10 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Serves as the base controller for implementing document management operations within the Umbraco CMS Management API.
/// Provides shared functionality for derived document controllers.
/// </summary>
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.Document)]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]

Some files were not shown because too many files have changed in this diff Show More