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 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 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
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
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
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 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
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
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
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 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 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
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 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 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 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
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
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 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
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 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
2773 changed files with 66814 additions and 5304 deletions
+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
+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.
+81 -54
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
@@ -419,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
@@ -486,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
@@ -507,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 |
+1 -1
View File
@@ -89,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>
-1
View File
@@ -614,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:
+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:
@@ -1,6 +1,8 @@
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;
@@ -41,6 +43,7 @@ internal sealed class HideBackOfficeTokensHandler
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
@@ -51,11 +54,13 @@ 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
@@ -63,6 +68,7 @@ internal sealed class HideBackOfficeTokensHandler
{
_httpContextAccessor = httpContextAccessor;
_dataProtectionProvider = dataProtectionProvider;
_logger = logger;
_backOfficeTokenCookieSettings = backOfficeTokenCookieSettings.Value;
_globalSettings = globalSettings.Value;
@@ -291,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;
@@ -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 } } };
@@ -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
@@ -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;
@@ -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.")
@@ -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,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)]
@@ -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,6 +39,12 @@ 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)
: this(
@@ -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)]
@@ -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,9 +39,15 @@ public class DictionaryTreeControllerBase : NamedEntityTreeControllerBase<NamedE
{
}
/// <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,
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)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
@@ -9,6 +9,9 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Provides API endpoints for generating and retrieving preview URLs for documents.
/// </summary>
[ApiVersion("1.0")]
public class DocumentPreviewUrlController : DocumentControllerBase
{
@@ -23,6 +26,12 @@ public class DocumentPreviewUrlController : DocumentControllerBase
_documentUrlFactory = documentUrlFactory;
}
/// <summary>Retrieves the preview URL for a document by its unique identifier.</summary>
/// <param name="id">The unique identifier (GUID) of the document.</param>
/// <param name="providerAlias">The alias of the provider used to generate the preview URL.</param>
/// <param name="culture">An optional culture code for the preview URL.</param>
/// <param name="segment">An optional segment for the preview URL.</param>
/// <returns>An <see cref="IActionResult"/> containing the preview URL information if found; otherwise, a <see cref="ProblemDetails"/> response indicating the error.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/preview-url")]
[ProducesResponseType(typeof(DocumentUrlInfo), 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;
@@ -9,12 +9,21 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller responsible for managing and retrieving URLs for documents within the Umbraco CMS.
/// Provides endpoints for operations related to document URLs.
/// </summary>
[ApiVersion("1.0")]
public class DocumentUrlController : DocumentControllerBase
{
private readonly IContentService _contentService;
private readonly IDocumentUrlFactory _documentUrlFactory;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.DocumentUrlController"/> class.
/// </summary>
/// <param name="contentService">An instance of <see cref="IContentService"/> used to manage content operations.</param>
/// <param name="documentUrlFactory">An instance of <see cref="IDocumentUrlFactory"/> used to generate document URLs.</param>
public DocumentUrlController(
IContentService contentService,
IDocumentUrlFactory documentUrlFactory)
@@ -23,6 +32,11 @@ public class DocumentUrlController : DocumentControllerBase
_documentUrlFactory = documentUrlFactory;
}
/// <summary>
/// Retrieves the URLs for the documents identified by the specified set of IDs.
/// </summary>
/// <param name="ids">A set of document IDs for which to retrieve URLs.</param>
/// <returns>A task representing the asynchronous operation. The task result contains an <see cref="IActionResult"/> with a collection of URL information for each requested document.</returns>
[MapToApiVersion("1.0")]
[HttpGet("urls")]
[ProducesResponseType(typeof(IEnumerable<DocumentUrlInfoResponseModel>), StatusCodes.Status200OK)]
@@ -15,6 +15,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Provides API endpoints for managing domains associated with documents.
/// </summary>
[ApiVersion("1.0")]
public class DomainsController : DocumentControllerBase
{
@@ -23,6 +26,11 @@ public class DomainsController : DocumentControllerBase
private readonly IUmbracoMapper _umbracoMapper;
[ActivatorUtilitiesConstructor]
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.DomainsController"/> class.
/// </summary>
/// <param name="domainService">Service used to manage domain-related operations.</param>
/// <param name="umbracoMapper">The mapper used to map Umbraco objects to API models.</param>
public DomainsController(IAuthorizationService authorizationService, IDomainService domainService, IUmbracoMapper umbracoMapper)
{
_authorizationService = authorizationService;
@@ -39,6 +47,15 @@ public class DomainsController : DocumentControllerBase
{
}
/// <summary>
/// Retrieves the list of domains and their associated culture settings assigned to the specified document.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the document for which to retrieve domain assignments.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing a <see cref="DomainsResponseModel"/> with the assigned domains and culture settings, or a <see cref="ProblemDetails"/> if the document is not found.
/// </returns>
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/domains")]
[ProducesResponseType(typeof(DomainsResponseModel), 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.Document;
/// <summary>
/// Controller responsible for retrieving the audit log of a document.
/// </summary>
[ApiVersion("1.0")]
public class GetAuditLogDocumentController : DocumentControllerBase
{
@@ -22,6 +25,12 @@ public class GetAuditLogDocumentController : DocumentControllerBase
private readonly IAuditService _auditService;
private readonly IAuditLogPresentationFactory _auditLogPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.GetAuditLogDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service for handling authorization and access control.</param>
/// <param name="auditService">Service for retrieving audit log entries.</param>
/// <param name="auditLogPresentationFactory">Factory for creating audit log presentation models.</param>
public GetAuditLogDocumentController(
IAuthorizationService authorizationService,
IAuditService auditService,
@@ -32,6 +41,16 @@ public class GetAuditLogDocumentController : DocumentControllerBase
_auditLogPresentationFactory = auditLogPresentationFactory;
}
/// <summary>
/// Retrieves a paginated list of audit log entries for the specified document.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the document whose audit log is requested.</param>
/// <param name="orderDirection">The sort direction for the audit log entries (ascending or descending).</param>
/// <param name="sinceDate">An optional date; only audit log entries created on or after this date are included.</param>
/// <param name="skip">The number of entries to skip (for pagination).</param>
/// <param name="take">The maximum number of entries to return (for pagination).</param>
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="IActionResult"/> with a paged view model of audit log entries.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/audit-log")]
[ProducesResponseType(typeof(PagedViewModel<AuditLogResponseModel>), StatusCodes.Status200OK)]
@@ -15,6 +15,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller responsible for handling API requests to retrieve documents with public access settings.
/// </summary>
[ApiVersion("1.0")]
public class GetPublicAccessDocumentController : DocumentControllerBase
{
@@ -22,6 +25,12 @@ public class GetPublicAccessDocumentController : DocumentControllerBase
private readonly IPublicAccessService _publicAccessService;
private readonly IPublicAccessPresentationFactory _publicAccessPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="GetPublicAccessDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to resources.</param>
/// <param name="publicAccessService">Service for managing public access rules for documents.</param>
/// <param name="publicAccessPresentationFactory">Factory for creating presentation models for public access data.</param>
public GetPublicAccessDocumentController(
IAuthorizationService authorizationService,
IPublicAccessService publicAccessService,
@@ -32,6 +41,12 @@ public class GetPublicAccessDocumentController : DocumentControllerBase
_publicAccessPresentationFactory = publicAccessPresentationFactory;
}
/// <summary>
/// Retrieves the public access protection settings for the specified document.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the document whose public access settings are to be retrieved.</param>
/// <returns>An <see cref="IActionResult"/> containing the public access settings if found; otherwise, an error result.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/public-access")]
[ProducesResponseType(typeof(PublicAccessResponseModel), StatusCodes.Status200OK)]
@@ -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.Document.Item;
/// <summary>
/// Serves as the base controller for document item operations in the Umbraco CMS Management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Document}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))]
public class DocumentItemControllerBase : ManagementApiControllerBase
@@ -10,12 +10,22 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Document.Item;
/// <summary>
/// API controller responsible for handling operations related to item documents within the Umbraco CMS management area.
/// Provides endpoints for retrieving, updating, or managing document items.
/// </summary>
[ApiVersion("1.0")]
public class ItemDocumentItemController : DocumentItemControllerBase
{
private readonly IEntityService _entityService;
private readonly IDocumentPresentationFactory _documentPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.Item.ItemDocumentItemController"/> class.
/// This controller is responsible for handling API requests related to individual document items in the Umbraco CMS.
/// </summary>
/// <param name="entityService">The service used to manage and retrieve entities within the CMS.</param>
/// <param name="documentPresentationFactory">The factory responsible for creating document presentation models.</param>
[ActivatorUtilitiesConstructor]
public ItemDocumentItemController(
IEntityService entityService,
@@ -25,6 +35,12 @@ public class ItemDocumentItemController : DocumentItemControllerBase
_documentPresentationFactory = documentPresentationFactory;
}
/// <summary>
/// Gets a collection of document items identified by the provided Ids.
/// </summary>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <param name="ids">The set of document item Ids to retrieve.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains an IActionResult with the collection of document items.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<DocumentItemResponseModel>), StatusCodes.Status200OK)]
@@ -12,6 +12,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document.Item;
/// <summary>
/// Provides API endpoints for searching document items within the management interface.
/// </summary>
[ApiVersion("1.0")]
public class SearchDocumentItemController : DocumentItemControllerBase
{
@@ -19,6 +22,12 @@ public class SearchDocumentItemController : DocumentItemControllerBase
private readonly IDocumentPresentationFactory _documentPresentationFactory;
private readonly IDataTypeService _dataTypeService;
/// <summary>
/// Initializes a new instance of the <see cref="SearchDocumentItemController"/> class.
/// </summary>
/// <param name="indexedEntitySearchService">Service for searching indexed entities.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
/// <param name="dataTypeService">Service for managing data types.</param>
[ActivatorUtilitiesConstructor]
public SearchDocumentItemController(
IIndexedEntitySearchService indexedEntitySearchService,
@@ -30,6 +39,11 @@ public class SearchDocumentItemController : DocumentItemControllerBase
_dataTypeService = dataTypeService;
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchDocumentItemController"/> class.
/// </summary>
/// <param name="indexedEntitySearchService">The service used to perform searches on indexed entities. This dependency is injected.</param>
/// <param name="documentPresentationFactory">The factory responsible for creating document presentation models. This dependency is injected.</param>
[Obsolete("Use the non-obsolete constructor instead. Scheduled for removal in Umbraco 18.")]
public SearchDocumentItemController(
IIndexedEntitySearchService indexedEntitySearchService,
@@ -41,6 +55,18 @@ public class SearchDocumentItemController : DocumentItemControllerBase
{
}
/// <summary>
/// Searches for document items, including those in the recycle bin, using the specified query and filters.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="query">The search query string.</param>
/// <param name="trashed">If set, filters results to include only trashed items, only non-trashed items, or both (when null).</param>
/// <param name="culture">An optional culture code to filter search results.</param>
/// <param name="skip">The number of items to skip (for pagination).</param>
/// <param name="take">The maximum number of items to return (for pagination).</param>
/// <param name="parentId">An optional parent ID to filter results by parent.</param>
/// <param name="allowedDocumentTypes">An optional list of allowed document type IDs to filter results.</param>
/// <returns>A task representing the asynchronous operation, with an action result containing the search results.</returns>
[Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 18.")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> SearchWithTrashed(
@@ -63,6 +89,19 @@ public class SearchDocumentItemController : DocumentItemControllerBase
allowedDocumentTypes,
null);
/// <summary>
/// Searches for document items, including those in the recycle bin, based on the specified query and filters.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="query">The search query string.</param>
/// <param name="trashed">Whether to include trashed (recycled) items in the search. Optional.</param>
/// <param name="culture">The culture to filter the search results by. Optional.</param>
/// <param name="skip">The number of items to skip for paging.</param>
/// <param name="take">The number of items to return for paging.</param>
/// <param name="parentId">The parent ID to filter the search results by. Optional.</param>
/// <param name="allowedDocumentTypes">A list of allowed document type IDs to filter the search results by. Optional.</param>
/// <param name="dataTypeId">The data type ID to filter the search results by. Optional.</param>
/// <returns>A task that represents the asynchronous operation. The result contains an <see cref="IActionResult"/> with the search results.</returns>
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
@@ -16,6 +16,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller responsible for handling document move operations within the Umbraco CMS API.
/// </summary>
[ApiVersion("1.0")]
public class MoveDocumentController : DocumentControllerBase
{
@@ -23,6 +26,13 @@ public class MoveDocumentController : DocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.MoveDocumentController"/> class,
/// providing services required for document move operations.
/// </summary>
/// <param name="authorizationService">Service used to authorize user actions related to document movement.</param>
/// <param name="contentEditingService">Service responsible for editing and managing content documents.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context and user information.</param>
public MoveDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
@@ -33,6 +43,16 @@ public class MoveDocumentController : DocumentControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Moves the specified document to a new location within the content tree.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the document to move.</param>
/// <param name="moveDocumentRequestModel">The request model containing information about the target location.</param>
/// <returns>
/// An <see cref="IActionResult"/> indicating the result of the move operation:
/// returns <c>200 OK</c> if the move is successful, or <c>404 Not Found</c> if the document or target location does not exist.
/// </returns>
[HttpPut("{id:guid}/move")]
[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;
@@ -15,6 +15,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller for moving documents to the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class MoveToRecycleBinDocumentController : DocumentControllerBase
{
@@ -22,6 +25,12 @@ public class MoveToRecycleBinDocumentController : DocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="MoveToRecycleBinDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize user actions for moving documents to the recycle bin.</param>
/// <param name="contentEditingService">Service responsible for editing and managing content documents.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context and user information.</param>
public MoveToRecycleBinDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
@@ -14,6 +14,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Provides API endpoints for managing notifications related to documents.
/// </summary>
[ApiVersion("1.0")]
public class NotificationsController : DocumentControllerBase
{
@@ -21,6 +24,12 @@ public class NotificationsController : DocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IDocumentNotificationPresentationFactory _documentNotificationPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="NotificationsController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize user actions for document notifications.</param>
/// <param name="contentEditingService">Service responsible for editing and managing document content.</param>
/// <param name="documentNotificationPresentationFactory">Factory for creating presentation models for document notifications.</param>
public NotificationsController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
@@ -31,6 +40,12 @@ public class NotificationsController : DocumentControllerBase
_documentNotificationPresentationFactory = documentNotificationPresentationFactory;
}
/// <summary>
/// Retrieves notifications for the specified document.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the document.</param>
/// <returns>An <see cref="IActionResult"/> containing the notifications for the specified document, or a 404 response if not found.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/notifications")]
[ProducesResponseType(typeof(IEnumerable<DocumentNotificationResponseModel>), StatusCodes.Status200OK)]
@@ -0,0 +1,75 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.OperationStatus;
using Umbraco.Cms.Api.Management.Patchers;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.Patching;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
public class PatchDocumentController : PatchDocumentControllerBase
{
private readonly IContentEditingService _contentEditingService;
private readonly IDocumentPatcher _documentPatcher;
private readonly IDocumentEditingPresentationFactory _presentationFactory;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public PatchDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
IDocumentPatcher documentPatcher,
IDocumentEditingPresentationFactory presentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_contentEditingService = contentEditingService;
_documentPatcher = documentPatcher;
_presentationFactory = presentationFactory;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPatch("{id:guid}/patch")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
[EndpointSummary("Make partial updates to a document. For more information, see the documentation at https://docs.umbraco.com/umbraco-cms/reference/management-api/patching/document-endpoint-guide or https://docs.umbraco.com/umbraco-cms/reference/management-api/patching/document-endpoint-spec")]
[Consumes("application/json-patch+json")]
public async Task<IActionResult> Patch(
CancellationToken cancellationToken,
Guid id,
PatchDocumentRequestModel requestModel)
=> await HandleRequest(id, async () =>
{
ContentPatchModel patchModel = _presentationFactory.MapPatchModel(requestModel);
// Apply PATCH operations to create an update request model
Attempt<UpdateDocumentRequestModel, ContentPatchingOperationStatus> patchResult =
await _documentPatcher.ApplyPatchAsync(id, patchModel);
if (patchResult.Success is false)
{
return ContentPatchingOperationStatusResult(patchResult.Status);
}
ContentUpdateModel contentUpdateModel = _presentationFactory.MapUpdateModel(patchResult.Result);
// Use the standard update method to save the patched content
Attempt<ContentUpdateResult, ContentEditingOperationStatus> updateResult =
await _contentEditingService.UpdateAsync(id, contentUpdateModel, CurrentUserKey(_backOfficeSecurityAccessor));
return updateResult.Success
? Ok()
: ContentEditingOperationStatusResult(updateResult.Status);
});
}
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
public abstract class PatchDocumentControllerBase : UpdateDocumentControllerBase
{
protected PatchDocumentControllerBase(IAuthorizationService authorizationService)
: base(authorizationService)
{
}
/// <summary>
/// Maps ContentPatchingOperationStatus to appropriate HTTP responses for PATCH operations.
/// </summary>
protected IActionResult ContentPatchingOperationStatusResult(ContentPatchingOperationStatus status)
=> OperationStatusResult(status, problemDetailsBuilder => status switch
{
ContentPatchingOperationStatus.InvalidOperation => BadRequest(problemDetailsBuilder
.WithTitle("Invalid operation")
.WithDetail("One or more PATCH operations were invalid. Check operation structure, path syntax, and operation types.")
.Build()),
ContentPatchingOperationStatus.NotFound => NotFound(problemDetailsBuilder
.WithTitle("The document could not be found")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
.WithTitle("Unknown error")
.WithDetail("An unexpected error occurred during the PATCH operation.")
.Build()),
});
}
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -17,6 +17,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Provides API endpoints for publishing documents within the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class PublishDocumentController : DocumentControllerBase
{
@@ -25,6 +28,13 @@ public class PublishDocumentController : DocumentControllerBase
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IDocumentPresentationFactory _documentPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="PublishDocumentController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize user actions.</param>
/// <param name="contentPublishingService">Service responsible for publishing content.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
public PublishDocumentController(
IAuthorizationService authorizationService,
IContentPublishingService contentPublishingService,
@@ -37,6 +47,20 @@ public class PublishDocumentController : DocumentControllerBase
_documentPresentationFactory = documentPresentationFactory;
}
/// <summary>
/// Publishes the specified document by its unique identifier, using the provided publish schedule and related data.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the document to be published.</param>
/// <param name="requestModel">The request model containing publish schedules, cultures, and other publishing options.</param>
/// <returns>
/// An <see cref="IActionResult"/> representing the result of the publish operation:
/// <list type="bullet">
/// <item><description><c>200 OK</c> if the document was published successfully.</description></item>
/// <item><description><c>400 Bad Request</c> if the request is invalid or publishing fails due to validation errors.</description></item>
/// <item><description><c>404 Not Found</c> if the document does not exist.</description></item>
/// </list>
/// </returns>
[HttpPut("{id:guid}/publish")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -16,6 +16,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller responsible for publishing a document and all of its descendant documents in the content tree.
/// </summary>
[ApiVersion("1.0")]
public class PublishDocumentWithDescendantsController : DocumentControllerBase
{
@@ -23,6 +26,12 @@ public class PublishDocumentWithDescendantsController : DocumentControllerBase
private readonly IContentPublishingService _contentPublishingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="PublishDocumentWithDescendantsController"/> class, which handles publishing a document and its descendants in the Umbraco CMS.
/// </summary>
/// <param name="authorizationService">Service used to authorize publishing actions.</param>
/// <param name="contentPublishingService">Service responsible for executing content publishing operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context and user information.</param>
public PublishDocumentWithDescendantsController(
IAuthorizationService authorizationService,
IContentPublishingService contentPublishingService,
@@ -33,6 +42,15 @@ public class PublishDocumentWithDescendantsController : DocumentControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Publishes the specified document and all of its descendants.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the root document to publish along with its descendants.</param>
/// <param name="requestModel">The request model specifying cultures to publish and additional publishing options.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing a <see cref="PublishWithDescendantsResultModel"/> if the operation is accepted, or a <see cref="ProblemDetails"/> if the request is invalid or the document is not found.
/// </returns>
[HttpPut("{id:guid}/publish-with-descendants")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PublishWithDescendantsResultModel), StatusCodes.Status200OK)]
@@ -14,12 +14,20 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
/// <summary>
/// Controller that handles results of publishing a document and its descendants.
/// </summary>
[ApiVersion("1.0")]
public class PublishDocumentWithDescendantsResultController : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IContentPublishingService _contentPublishingService;
/// <summary>
/// Initializes a new instance of the <see cref="PublishDocumentWithDescendantsResultController"/> class.
/// </summary>
/// <param name="authorizationService">An instance of <see cref="IAuthorizationService"/> used to check user permissions.</param>
/// <param name="contentPublishingService">An instance of <see cref="IContentPublishingService"/> used to publish content and its descendants.</param>
public PublishDocumentWithDescendantsResultController(
IAuthorizationService authorizationService,
IContentPublishingService contentPublishingService)
@@ -28,6 +36,13 @@ public class PublishDocumentWithDescendantsResultController : DocumentController
_contentPublishingService = contentPublishingService;
}
/// <summary>
/// Retrieves the status and result of a publish operation for a document and its descendants.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the document being published.</param>
/// <param name="taskId">The unique identifier of the publish operation task.</param>
/// <returns>An <see cref="IActionResult"/> containing the status and details of the publish operation, including whether it is complete and any relevant results.</returns>
[HttpGet("{id:guid}/publish-with-descendants/result/{taskId:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PublishWithDescendantsResultModel), StatusCodes.Status200OK)]
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Services;
@@ -8,14 +8,32 @@ using Umbraco.Cms.Api.Management.ViewModels.Document.RecycleBin;
namespace Umbraco.Cms.Api.Management.Controllers.Document.RecycleBin;
/// <summary>
/// Controller responsible for managing operations related to the child documents within the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class ChildrenDocumentRecycleBinController : DocumentRecycleBinControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDocumentRecycleBinController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the recycle bin.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
public ChildrenDocumentRecycleBinController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, documentPresentationFactory)
{
}
/// <summary>
/// Retrieves a paginated list of documents that are children of the specified parent document in the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="parentId">The unique identifier (GUID) of the parent document whose children are to be retrieved.</param>
/// <param name="skip">The number of items to skip before starting to collect the result set. Defaults to 0.</param>
/// <param name="take">The maximum number of items to return. Defaults to 100.</param>
/// <returns>
/// An <see cref="ActionResult{T}"/> containing a <see cref="PagedViewModel{T}"/> of <see cref="DocumentRecycleBinItemResponseModel"/> representing the child documents in the recycle bin.
/// </returns>
[HttpGet("children")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<DocumentRecycleBinItemResponseModel>), StatusCodes.Status200OK)]
@@ -16,6 +16,10 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document.RecycleBin;
/// <summary>
/// Controller responsible for handling requests to delete items from the document recycle bin.
/// Provides endpoints for permanently removing documents that have been moved to the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class DeleteDocumentRecycleBinController : DocumentRecycleBinControllerBase
{
@@ -23,6 +27,14 @@ public class DeleteDocumentRecycleBinController : DocumentRecycleBinControllerBa
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IContentEditingService _contentEditingService;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteDocumentRecycleBinController"/> class, which handles operations related to deleting items from the document recycle bin.
/// </summary>
/// <param name="entityService">The service used for entity operations.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
/// <param name="authorizationService">Service for handling authorization checks.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="contentEditingService">Service for editing content.</param>
public DeleteDocumentRecycleBinController(
IEntityService entityService,
IDocumentPresentationFactory documentPresentationFactory,
@@ -36,6 +48,12 @@ public class DeleteDocumentRecycleBinController : DocumentRecycleBinControllerBa
_contentEditingService = contentEditingService;
}
/// <summary>
/// Permanently deletes a document from the recycle bin, 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 document to permanently delete from the recycle bin.</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.Controllers.RecycleBin;
using Umbraco.Cms.Api.Management.Factories;
@@ -13,6 +13,9 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Document.RecycleBin;
/// <summary>
/// Serves as the base controller for handling operations related to the document recycle bin in the management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.RecycleBin}/{Constants.UdiEntityType.Document}")]
[RequireDocumentTreeRootAccess]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))]
@@ -21,6 +24,11 @@ public class DocumentRecycleBinControllerBase : RecycleBinControllerBase<Documen
{
private readonly IDocumentPresentationFactory _documentPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentRecycleBinControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the recycle bin.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
public DocumentRecycleBinControllerBase(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService)
=> _documentPresentationFactory = documentPresentationFactory;

Some files were not shown because too many files have changed in this diff Show More