Compare commits

..
Author SHA1 Message Date
Ronald Barendse e93419c18d Add IRecurringBackgroundJobTrigger<TJob> for opt-in job triggering 2026-04-17 10:30:56 +02:00
Ronald Barendse 4182e858a8 Tidy RecurringBackgroundJobBase docs and runner error handling 2026-04-17 10:04:35 +02:00
Ronald Barendse 0631f0a749 Replace Task.Yield with semaphore timeouts in negative assertions 2026-04-09 16:19:42 +02:00
Ronald Barendse 9c3ac1fb37 Remove hosted service from dictionary before stopping to prevent triggering during shutdown 2026-04-09 16:19:41 +02:00
Ronald Barendse 7e97d46c81 Use Interlocked for _period reads/writes and implement thread-safe dispose pattern 2026-04-09 16:19:40 +02:00
Ronald Barendse 7795e974e3 Fix trigger state race, simplify ReleaseSignal, and add canceled notification test
Fix trigger state
2026-04-09 16:19:20 +02:00
Ronald Barendse 48f407b2e6 Ensure PeriodChanged event is unsubscribed again 2026-04-09 15:42:07 +02:00
Ronald Barendse ed4136804e Set up Period and Delay on mock job to satisfy constructor validation 2026-04-09 11:03:00 +02:00
Ronald Barendse 312c20fbb9 Validate period is positive and use GetOrAdd to avoid creating unused hosted services 2026-04-08 11:06:10 +02:00
Ronald Barendse 9c9518d723 Clarify TriggerExecution(TimeSpan) docs and add ChangePeriod test 2026-04-08 10:08:08 +02:00
Ronald Barendse a2e11c73d6 Configure IEventMessagesFactory mock to return real EventMessages 2026-04-08 08:57:40 +02:00
Ronald Barendse 3f1325ecf1 Avoid disposing period-change CTS while wait loop may still reference it 2026-04-08 08:55:21 +02:00
Ronald Barendse 5d1b7da959 Fix Exception_In_PerformExecuteAsync_Does_Not_Kill_Loop test 2026-04-07 23:25:17 +02:00
Ronald BarendseandGitHub d245c0299f Merge branch 'main' into v17/feature/recurringbackgroundjob-signalling 2026-04-07 22:35:09 +02:00
Ronald Barendse 2a3df70e4d Use DelayCalculator.GetDelay instead of RecurringHostedServiceBase.GetDelay 2026-04-07 22:27:03 +02:00
Ronald Barendse e984e0e56f Inject TimeProvider into RecurringHostedServiceBase for deterministic testing
Fix timeprovider
2026-04-07 22:25:13 +02:00
Ronald Barendse 524a56d87d Use semaphore signaling instead of Task.Delay in trigger tests
Use semaphore signaling instead of Task.Delay in trigger tests 2
2026-04-07 22:11:23 +02: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
Ronald Barendse 34e51e4fde Remove failed hosted service from dictionary when StartAsync throws 2026-04-07 12:53:58 +02:00
Ronald Barendse 417c7ae3af Register RecurringBackgroundJobHostedServiceRunner as resolvable singleton 2026-04-07 12:53:58 +02:00
bfa3c3234b Media Picker: Fix folder selection regression for developer-configured media pickers (closes #22349) (#22350)
* Fixes "files/folders/files or folders" selections for the various media picker components, re-allowing folder selection from a media picker.

* Import and use enim instead of hardcoded enum value

---------

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

* show ancestor breadcrumb path in media search results

* Show the document ancestors name by culture variant

* Extract ancestor fetching to reduce cyclomatic complexity

* Add early return inside #fetchAncestors

* Handle errors from the api call.

* Add fallback title when the name doesn't exist

* Use full item models for search ancestor types
2026-04-07 09:38:27 +02:00
Andy ButlandandGitHub cd541b66f4 Builder Extensions: Make AddWebComponents() idempotent (closes #22344) (#22347)
Ensure AddWebComponents is idempotent.
2026-04-07 07:07:19 +02:00
Andy Butland 9e510e0940 Removed unneeded using. 2026-04-03 11:53:32 +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
Ronald Barendse 7d82afa6ad Fix API compatibility errors 2026-04-02 22:54:00 +02:00
Ronald Barendse 3012138267 Remove hosted services from dictionary on stop 2026-04-02 22:12:37 +02:00
Ronald Barendse 3607092e85 Use ConcurrentDictionary for thread-safe hosted service lookup 2026-04-02 22:09:07 +02:00
Ronald Barendse 7f290696e9 Consolidate trigger state into an immutable record for thread safety 2026-04-02 21:39:21 +02:00
Ronald Barendse 42f15d898b Combine ComputeNextDelay tests 2026-04-02 17:22:47 +02:00
Ronald Barendse 7aaa786a88 Clear _nextExecutionSkipOnOvershoot unconditionally 2026-04-02 17:21:13 +02:00
Ronald Barendse 504a56ce12 Clear trigger state when initial delay is interrupted 2026-04-02 17:21:13 +02:00
Ronald Barendse d226d8c8ff Extract shared helper for TriggerExecution tests 2026-04-02 17:21:13 +02:00
Ronald Barendse 4eca474770 Match hosted services by Type instead of type name string 2026-04-02 17:21:13 +02:00
Ronald Barendse de7e72e06c Handle cancellation (application shutdown) and publish RecurringBackgroundJobCanceledNotification 2026-04-02 17:21:12 +02:00
Ronald Barendse 4f50356588 Merge branch 'main' into v17/feature/recurringbackgroundjob-signalling
# Conflicts:
#	src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs
#	src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedService.cs
#	src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs
#	src/Umbraco.Infrastructure/Extensions/ServiceCollectionExtensions.cs
#	src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs
#	tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBaseTests.cs
2026-04-02 13:29:53 +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
Ronald BarendseandClaude Opus 4.6 9f8b533b1e Add TriggerExecution methods to RecurringBackgroundJobHostedServiceRunner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 00:08:09 +02:00
Ronald Barendse d1ec78d2a8 Add NextExecutionStrategy parameter to adjust the schedule after triggered executions 2026-04-01 23:32:59 +02:00
Ronald Barendse 8fe60d6569 Add RecurringBackgroundJobBase to contain default values and hide obsoleted method 2026-04-01 23:32:59 +02:00
Ronald Barendse a2d97f8464 Use SemaphoreSlim to properly handle exceptions, cancellation tokens and triggering immediate executions 2026-04-01 23:32:57 +02:00
2a44169e0b Block Editors: Fix preset values for composition properties on non-varying element types (closes follow-up on #22320) (#22320)
Correct preset values for composition properties on non-varying element types

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

* Updated client-side instruction docs

for clean code and style guide.

* Updated "Full API surface" point

* Update CLAUDE.md

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

---------

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

* Apply suggestion from @Copilot

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

* Handle fragment-only routes before parsing node id

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

---------

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

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

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

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

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

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

* refactor: tighten types and overload _peekError with UmbPeekErrorArgs

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

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

* refactor: extract duplicate errors fallback to #validationErrors getter

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

* refactor: remove unnecessary null handling in interceptor #peekError

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

* fix: resolve tsc errors from type tightening

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

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

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

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

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

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

---------

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

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

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

---------

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

* fix paths

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

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

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

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

* Export workspace elements as element

* consolidate information

* adjust skill to make use of generic name component

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

* Update SKILL.md

* clean up create package skill

* use data type package as reference

* add initial repository doc + skill

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

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

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

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

* Update workspaces.md

* clean up

* Update SKILL.md

* Delete Repositories.md

* Create repositories.md

* Update repositories.md

* Normalize repositories doc links to lowercase

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

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

* Fix data flow link path casing

* fix casing

* export as api + inline store in manifest

---------

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

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

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

* Addressed feedback from code review.

* Move startup application URL logging to a handler.

* Clean up ambiguous log message

---------

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

* Add history panel

* update document type route

* remove comment

* move time options format to ultils

* add blueprint auditlog model

* save move action and add authorization for audit log request

* add default implement

* update open api json

* Reused the `workspaceInfoApp: auditLog` kind

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

* UI tweaks + linting

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

* Restored the "UmbDocumentBlueprintAuditLog" types

* Add JSDoc to document blueprint audit log repository

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

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

* Export audit-log module from document-blueprints index

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

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

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

---------

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

* rename to review

* auto-detect target-branch via GH CLI

* Verify GH CLI is Available

* update table to fit github markdown format

* condensed the output to the essense

* State if the PR is too bad

* using the word `and´

* only relevant suggestions

* clean up

* narrow the scope for large PRs

* diff-first approach with selective reads

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

* Complexity detection

* Classification of the PR

* improve other changes

* Ensure Types are kept intact in their type Hierarchy

* align test naming with project, and clean up instructions

* remove hardcoded Claude.md file table for a pattern

* improve skill description

* improved breaking change detection for front-end

* do not suggest breaking changes for PRs targeting main

* rename skill to umb-review

* less nit picky

* first version of skill evals

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

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

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

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

* remove mentioning the skill action it self

* split out GH CLI guideline

* improve file loading strategy

* make feedback extremely concise

* improve skipped files output

* latests eval

* added further evals

* move summaries into references

* separate Complexity Assessment into a reference file

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

* dont include gen.ts files

* iter 9 evals

* latests eval of 4

* keep only one test for complexity-advisory

* adjusted skill and Evals to match expectations

* improve sibling lookups

* improve skill regarding nit picks and C# patterns

* remove insecure manifest check

* final eval run

* eval grading

* remove review workspace

* remove umb review workspace part 2

---------

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

* Export workspace elements as element

* consolidate information

* adjust skill to make use of generic name component

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

* Update SKILL.md

* clean up create package skill

* use data type package as reference

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

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

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

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

* Update workspaces.md

---------

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

* Moved acceptance-test-helper to umbraco-cms

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

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

* Avoid concurrent loads.

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

* Addressed code review feedback.

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: address Copilot review feedback

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

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

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

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

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

* fix: select figure before replacing when editing from figcaption

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

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

* fix: export UMB_TIPTAP_NODE_DBLCLICK_EVENT from tiptap constants

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

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

* Apply suggestion from @AndyButland

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

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

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

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

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

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

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

* feat: embeds should be constrained by default

* feat: defaults embed constrain to true

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

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

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

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

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

* chore: cleanup

* fix: use general_clear localization key for reset button

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

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

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

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

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

* chore: add comment explaining generic selector for oEmbed markup

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* feat: address review feedback on dimensions and preview

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

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

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

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

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

* fix: recalculate aspect ratio when dimensions are set externally

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Use splitStringToArray for config parsing consistency

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

* Include media name in validation error for disallowed media types

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

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

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

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

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

* fix: Address Copilot review feedback

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

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

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

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

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

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

---------

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

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

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

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

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

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

Fixes #21942

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

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

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

* Added test for get-initials function

* Added note about duplicate get-initials function

* Replicated the logic from the UUI avatar

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

---------

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

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

* Addressed code review feedback.

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

In order to show the right validation message:

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

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

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

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

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

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

---------

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

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

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

* update with developer roles

* tighten up for llm consumption

* add information about localization

* add section about kinds

* include test priority

* add llm docs for core primitives and data flow

* add info about caching

* add skills

* organize in folders

* flat list of skills

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

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

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

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

* update skill name

* format tech stack based on claude recommendations

* add context about entities

---------

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

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

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

* inlcude namespace for suggested code change

---------

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

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

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

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

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

* #22114 worked on SVG width height implementation

* #22244 Code style fixes

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

* #22244 XmlReaderSettings and using

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

* #22244 Cleanup

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

* #22244 Correction if statement

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

* #22244 Refactor log message

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

* #22244 Correction if statment

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

* #22244 Cleanup

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

* #22244 Cleanup

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

* #22244 Code style adjustments

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

* #22244 Adjust if statement

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

* #22244 Adjust documentation comments

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

* #22244 Fix log comment

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

* #22244 Refactoring SVG parser, no support for decimals

* #22244 Migration, consistent logging

* #22244 Create vector umbracoWidth and umbracoHeight during clean install

* #22244 Remove SupportedImageType from ISvgDimensionsExtractor

* #22244 pass culture and segment to SetValue

* Add DtdProcessing.Prohibit security hardening to SvgDimensionExtractor.

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

* Add further unit tests.

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

* Add unit tests for media saving handler.

* Move the dimensions extractor implementation into infrastructure.

---------

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

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

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

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

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

* Validate length

* Refactor to enable us to have a validator

---------

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

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

* Apply suggestions from code review

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

* Improve variable naming.

---------

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

* Updated ui helper for block grid area

* Updated tests for block grid area

* Updated json builder for blockGridSpecifiedAllowance

* Formatted code

* Updated ui helper for specifiedAllowance

* Updated tests

* Fixed ui helper for enterSpecifiedAllowanceMinByIndex

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

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

* Format code

* Make tests run in the pipeline

* Fixed tests

* Fixed comments

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

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

* Add MaxVersionsToDeletePerRun configuration setting.

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

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

* Handle deletes using optimised process using temp tables.

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

* Addressed code review feedback.

* Fix to reporting of cap reached.

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

* Add TODOs for removal of default implementations from interfaces.

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

* Revert timing for ContentVersionCleanupJob.

* Add index to versionDate on umbracoContentVersion.

* Ensure long command timeout for upgrade.

---------

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

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

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

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

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

* Renamed

* Updated api helper for creating document with multi url picker

* Added tests for mandatory multi url picker

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

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

* Make tests run in the pipeline

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

* Renamed tests

* Make tests run in the pipeline

* Updated name

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

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

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

This reverts commit 203294e287.

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

---------

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

* Addressed code review feedback.

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

* align member collection create action with other content types

* remove hardcoded icon

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

* restore label

* Remove ellipsis from document blueprint label

* Add collection create actions for tree item children

* Show ellipsis for labels with additional options

* Enable additional options for create actions

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

* Update UiBaseLocators.ts

* Add additionalOptions to create manifests

* Add ellipsis to names in create content modals

* Update DataTypeUiHelper.ts

* Update DocumentTypeUiHelper.ts

* Update creation action locators and tests

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

to Entity Data Picker tree source confguration

* Changes the example Document data-source

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

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

* Renamed `requestStartNode` to `requestTreeStartNode`

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

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

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

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

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

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

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

* Address code review feedback.

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

---------

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

* Add details of integration tests to memory files.

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

* Add further test verifying the fixed behaviour.

* Test clean-up.

---------

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

* Moved to specific test files

* Added tests with compositions

* Updated helper

* Run tests on pipeline

* Fixed

* Updated helpers

* Added tests for variants

* Added tests

* Updated smoke

* Fixed

* Cleaned up

* Moved to before each

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

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

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

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

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

---------

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

* Added ui helper for public access

* Added api helper for setup and delete public access

* Added api helper for create default member group

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

* Added tests for setting public access on content

* Added api helper for verify public access

* Updated ui helper for verify public access

* Updated tests for public access

* Make tests run in the pipeline

* Fixed comment

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

* Added appsetting file for imaging setting config tests

* Updates name

* Added project for imagingSettingConfig

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

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

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

* Makes test run in the pipeline

* Fixed comment

* Clean code

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

* Removed unused condition

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

* style: apply linter comment punctuation fix

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

* Removed unnessary test.

---------

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

Add to backoffice hosts, rather than completely replacing the array

* Add unit tests verifying fix.

---------

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

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

* Remove obsoletion on helper.

* Address code review feedback.

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

* Fixed build error in tests.

---------

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

* Add template tree item-children collection and views

* add base class

* Use Settings section for document blueprint paths

* Inline customElement names and update typings

* Make table collection view buttons compact

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

* move file

* fix export

* fix const exports

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

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

* Remove obsoletion on helper.

* Address code review feedback.

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

* Fixed build error in tests.

---------

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

Add to backoffice hosts, rather than completely replacing the array

* Add unit tests verifying fix.

---------

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

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

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

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

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

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

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

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

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

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

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

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

* Adding tests

* Alternative lazy injection

* Adding delegate unit tests and making classes internal sealed.

* Adding a check to see if database is readonly

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

* Use unique id in media picker selection handlers

* Add dateTime formatter and use in media picker

* Potential fix for pull request finding

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

* Add dateTime localization tests

* localize view labels

* Persist media picker view in interaction memory

---------

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

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

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

Closes #22106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

* Routing: Refactor URL segment collision to direct segment comparison

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

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

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

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

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

* Updated usages of obsolete constructors.

* Avoid second look-up of siblings data.

* Make EnsureUniqueUrlSegment unit testable, and add tests.

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

---------

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

* Delete inadvertently added file

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

* Pushed missing files.

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

* Delete inadvertently added file

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

* Pushed missing files.

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

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

* Updated path to test files

* created tests

* Reverted retries change

* Updated imports

* Added step

* updates based on comments and clean up

* Added vars

---------

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

* Updated tests due to UI changes

* Format code

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

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

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

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

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

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

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

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

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

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

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

* Fixing a missing closing brace on return docs.

* Fixing issue raised by copilot.

Issue was:

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

* Fix broken <returns> tags.

* Fixed incorrect descriptions.

* Added missing description.

* Fix positioning of comments.

* Fixed indentation.

* Use standard text for view model properties.

---------

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

* Sort by weight rather than alphabetically.

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

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

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

* Update src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs

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

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

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

* Fixing small issues and adding some more missing docs.

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

* Fixed messages in UserRepository.

* Fixed indents in file scope namespaced files.

---------

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

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

Closes #22096

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

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

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

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

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

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

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

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

---------

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

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

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

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

* Added ui helper for card collection view in content

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

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

* Removed unused code

* Fixed comments

* Removed unused test folder

* Updated auth to clear storage

---------

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

* Tests and schema validation

* Attemp refactor

* Fix json single parent bug

* Surface doctype schema validation to management api

* Improve block schema and make validation errors less verbose

* fix validation error cleanup

* Improved GUID handling | added schema for all propertyEditors

* Add ContentTypeInputSchema

* move contenttype schemas to be actual jsonschemas

* Fix block limit on blocklist and grid

* add datatype schema batch

* Refactoring blocks json schema generation and add to richtext

* Package version update and more tests!

* ConvertToJsonNode optimization

* async refactor

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

* Removed JsonSchema.net due to possible license issues

* Use void editor in the noop schema test

* Cleanup leftovers from Schema validation removal

* Move batch logic into batchcontroller

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

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

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

Improve lookup on building propertymetadata

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

* Fixed build error.

---------

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

* Improved messaging and fixed integration tests.

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

* Suppress breaking changes in integration tests.

---------

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

* Added tests for disableDeleteWhenReferenced setting

* Added constant variable for descendingReferenceHeadline

* Moved doesModalHaveText to uiBaseLocator

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

* Updated warningMessageForBulk

* Fixed comments

---------

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

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

* Add suppression for renamed integration test.

* Simplified solution by moving disableDeleteWhenReferenced setting to modal.

* Fix flicker.

* Apply disable on delete handling to bulk trash dialog.

* Add additional translations.

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

* Update OpenApi.json.

---------

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

* Added regression test.

* Addressed test feedback.

* Fix after merge.

---------

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

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

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

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

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

* Update to use constant and HTTP scheme.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

---------

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

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

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

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

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

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

* build(deps): bumps vite in umbracoextension template

---------

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

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

---------

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

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

* Switch to GET endpoints.

* generate new managment api types + sdk

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

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

* Add endpoint summaries and descriptions.

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

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

* Add readMany for document type details

* Add batch read methods to detail interfaces

* Pre-register content-type structures and bulk load

* Add readMany support to detail request managers

* Simplify loadType and delegate to setType

* add js docs to detail data request manager

* add unit tests for detail data request manager

* Add byUniques support to detail store/repository

* implement readMany for data types

* fix typescript errors

* Preload and pass data type details to properties

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

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

* Deduplicate inflight detail read/readMany requests

* Use 'read:' inflight cache key prefix

* Add bulk detail requests & status helpers

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

* use observe controller instead of rxjs

* adjust to new apis

* rename prop to make it easier to read

* throw on error

---------

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

* remove unused import

* Fixes to failing E2E tests.

---------

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

* Update translations using legacy mail format.

* Delete inadvertently added file

---------

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

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

* Cleaning up pull request

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

* pass in the includeAncestors parameter

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

* Complete resolution of breaking changes on IPublicAccessPresentationFactory.

* Update call to controller from integration tests.

* Fixed variable name typo and whitespace.

* Added clarifying comment to client-side behaviour.

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

* Added unit tests for PublicAccessPresentationFactory.

* Added localisation for ancestor label.

* Typo and whitespace.

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

* update locatlizations

---------

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

* update model for link and rte when have culture

* resolve illegal imports

* update ApiLink

* Update ApiLink create content

* Update LocalLinkTag

* remove culture from picker modal

* remove culture from picker input

* update unit test, process culture from modalValue

* Use compile time regular expressions.

* wip custom document picker for multi url picker

* Set document link picker modal size to small

* render variant aware picked document item

* utiliza variant context in link picker modal

* Update link-picker-modal.element.ts

* remove unused

* remove unused

* remove unused

* remove unused

* Update types.ts

* Update tree-picker-modal.element.ts

* Update document-picker-modal.token.ts

* Update document-item-ref.element.ts

* Update document-item-data-resolver.ts

* Update document-item-data-resolver.ts

* Update tree-picker-modal.element.ts

* Update tree-picker-modal.element.ts

* Update tree-picker-modal.element.ts

* remove unused

* fix lint errors

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

* Skip language selector when <=1 language

* Fix typo in variant context comment

* Use strict equality for document type check

* Localize document picker headline

* remove unused default language

* Don't fallback culture when updating link

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

* use uui-combobox for a11y benefits

* remove unused code

* Cache repositories and reuse data resolvers

* clean up

* Update input-multi-url.element.ts

* Add multi-url-picker constants exports

* Update index.ts

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

* Initialize link picker in async firstUpdated

* Await pickerSelect in onPickerSelection

* Await variant context & picker select calls

* Await setCulture in language handler

* Include culture in document edit href

* Add data type config for culture specific document links

* Localize culture-specific document link UI

* change of wording for configuration

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

* localization updates

---------

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

* dismantle currentScope immediately when disconnected

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

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

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

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

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

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

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

Two issues:

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

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

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

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

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

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

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

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

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

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

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

* chore: adds null guard

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

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

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

---------

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

* Add unit tests for AliasUrlProvider.

---------

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

* Apply suggestions from code review

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

---------

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

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

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

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

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

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

---------

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

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

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

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

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

* Moved the "exposed" cookie config to IConfigureNamedOptions

* Add missing constants

* Add unit tests to prove the site postfix

---------

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

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

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

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

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

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

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

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

* Backoffice: Fix circular dependencies - part 2

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

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

---------

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

* Reduce SecurityStampValidatorOptions validation interval for users to zero.

* Apply member security stamp options.

* Addressed code review feedback.

* Add separate settings for AllowConcurrentLogins for members and users.

* Clarify comment.

* Further unit tests as suggested by code review.

---------

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

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

* Add maintenance protection to surface controllers.

* Add protection for delivery API in upgrading state.

* Add protection for management API in upgrading state.

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

* Fix regression in attended upgrade state.

* Addressed code review feedback.

* Tidied up comments.

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

* Fixed failing integration test.

* Removed TestCase from test with only a single case.

* Fix localization for "backoffice"

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

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

---------

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

* Reduce SecurityStampValidatorOptions validation interval for users to zero.

* Apply member security stamp options.

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

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

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

Closes #21837

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

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

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

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

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

---------

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

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

* Remove duplicate 403 ProducesResponseType attributes.

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

---------

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

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

Introduced an 'alias' property to the UmbCollectionItemPickerModalCollectionConfig interface

* render collection element when modal is configured with an alias

* expose a picker modal route

* use collection in use picker

* adjust spacing

* add config option for selectOnly

* dynamic modal alias

* support selectable entity item ref

* wip entity data picker collection + ref and card views

* Add entity collection item card extension type + default elements

* implement user collection item card

* fix selection events

* map to prop

* add prop/attr for href

* add support for which detail properties to show

* update type import

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

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

* import card in correct file

* Fix event listener binding for selection events

* implement disabled property for collection item cards

* init commit of collection item ref extension

* fix imports

* add element interface

* Implement UmbEntityCollectionItemElement interface in item cards

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

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

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

* Refactor entity collection item elements to use shared base

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

* use class instead of magic string

* Use entity collection item card in picker view

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

* Update entity item ref to collection item ref

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

* utilise ref and card kind for picker views

* introduce ref and card collection view kinds

* Utilise card kind for user collection view

* Add item-specific href support to collection views

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

* Update ManifestCollectionView import path

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

* remove unused

* use size medium for entity collection item picker

* use box

* render entity actions

* use edit path builder for user links

* rename method

* Revert "rename method"

This reverts commit 4df577688e.

* Update collection-default.context.ts

* make type lint ignore unused args with an underscore

* temp remove unused

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

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

* fix imports

* Render selection actions only if bulk actions exist

* use selectable state

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

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

* Update card-collection-view.element.ts

* clean up

* Refactor collection views to use shared base class

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

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

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

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

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

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

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

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

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

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

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

* Handle missing user href in name column layout

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

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

* pass modal data and value to routable modal

* Update picker-input.context.ts

* support selectableFilter

* scaffolding of a collection text filter extension

* Refactor collection text filter to use API interface

* Fix incorrect tag

* Update types.ts

* Update collection-text-filter.extension.ts

* Add cancelation to debounced search on destroy

* clean up

* add js docs

* two way binding of filter value

* clean up

* Add collection text filter manifest example

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

* Delete unused element and context

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

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

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

* support search for tree item and collection item pickers

* add spacing between collection ref items

* add margin between picker search result items

* remove spacing after last item

* remove padding in search results

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

* move select only logic to collection selection manager

* add tests for collection selection manager

* change to filter label instead of search

* delete unused user grid collection view

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

* prepare umb table for pickers

* utilize UmbCollectionViewElementBase in user table collection view

* remove console log

* handle select all and select item from same event

* bulk actions workaround

* add bulk action in collections feature toggle

* remove unused method

* make fields optional to avoid a breaking change

* remove unused import

* fix typescript errors

* adjust search styling

* hide with css

* fix ts errors

* Add modal data support to picker input context

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

* Fix bulk action manager test initialization

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

* Update tree-picker-modal.element.ts

* Update picker-search-result.element.ts

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

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

* Use ifDefined for modal route in user input button

* Use ifDefined for href binding in entity data picker

* Fix collection alias binding in item picker modal

* wire up user table collection view with selectableFilter

* clean up controller aliases

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

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

* Add support for collection items with thumbnails

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

* Improve card grid responsiveness and card sizing

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

* add example image to thumbnail example

* introduce generic card component

* wip picker views configuration

* Update manifests.ts

* store value as alias

* Improve handling of missing collection view manifests

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

* add sorting

* rename

* Add confirmation modal before removing picker view

* remove unused

* move collection selectOnly logic to context

* Update user-picker-modal.token.ts

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

* Add optional description to collection items

* introduce extension picker data source

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

* nest manifest data

* Hide pagination when all items are shown

* Add a fallback page size

* merge extension insight code with extension code

* clean up

* Add optional description support to default item ref

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

This reverts commit e02881e8b6.

* fix post merge

* add input-extension utilizing input-entity-data

* proxy value and selection

* add todo

* temp hardcode config

* add typed config model

* Support multiple extension types in filters

* Use extensionTypes filter and deprecate type

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

* More explicit type name

* Expose allowedExtensionTypes as a @property on UmbInputExtensionElement

* Add text filter support for entity data picker

* remove reexport as this is not public available

* remove unused

* clean up

* clean up

* Add storage and getter for allowedExtensionTypes

* Inline collection view alias and remove constant

* Update vite.config.ts

* Update manifests.ts

* Update extension.picker-data-source.ts

* add tests for extension picker data source

* change to an observable feature config

* make feature object optional

* add unit tests

* Reference condition class directly in manifests

* Simplify collection view types and refactor setup

* remove todo

* implement input-extension on picker views configuration

* Add collection view aliases and defaults

* use correct type

* make name optional

* remove debugger

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

* map views to layouts

* Add viewsOverride to enforce collection layout order

* clean up observers if data source type changes

* remove unused

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Auth: Prevent auth modal from being dismissed via ESC

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

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

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

* chore: renames file and adds appropriate exports

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

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

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

---------

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

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

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

* Auth: Rewrite auth context with BroadcastChannel and Web Locks

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

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

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

* Auth: Update session timeout controller and SharedWorker

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

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

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

* Auth: Simplify OAuth completion flow and API interceptor

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

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

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

* Auth: Deprecate external/openid package and storage constant

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

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

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

* Auth: Update auth context tests for new implementation

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

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

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

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

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

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

Two bugs fixed:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Four improvements from a fresh design review:

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

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

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

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

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

* Auth: Fix timeout modal not showing during buffer zone

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

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

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

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

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

* Auth: Bind default interceptors via configureClient()

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

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

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

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

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

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

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

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

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

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

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

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

* docs: adds links to deprecations

* docs: adds deprecation notices

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

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

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

* fix: overrides options after applying defaults

* Auth: Supply keepUserLoggedIn from backend via HTML attribute

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

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

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

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

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

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

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

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

- Export UmbAuthSession interface for extension developers observing
  session$.

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

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

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

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

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

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

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

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

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

* Auth: Add default Authorization header to umbHttpClient

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

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

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

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

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

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

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

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

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

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

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

Also fixes prettier formatting in UmbAuthClient constructor.

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

* fix: do not assume that any endpoint is authenticated or accepts an Authorization header (this should come from the OpenAPI spec)

* E2E: QA: updated acceptance tests to match the authorization changes in  #21830 (#22021)

Updated tests to the updated auth

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2026-03-09 12:17:56 +00:00
c48c594e51 Redirect Tracking: Fix segment duplication when domain has a path segment (closes #21763) (#21772)
* Increase precision available to decimal data types.

* Fix redirect URL segment duplication when domain has a path segment.

* Revert accidental commit.

* Fix multiple enumeration

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2026-03-09 10:19:21 +00:00
Jacob Overgaard 74c76be952 internal: removes accidentally committed claude settings file 2026-03-09 11:06:22 +01:00
Andy ButlandandGitHub 10ba3519ec Management API: Add server-side validation preventing element types from varying by segment (closes #21643) (#21728)
Validate at management API to prevent create or update of an element type that varies by segment.
2026-03-09 10:47:48 +01:00
Niels LyngsøandGitHub 1958dfe3d2 Content: Only validate selected Cultures (#21361)
* correct comments

* poc

* refactor validation of variants

* refactor to enable parsing alternative validation methods

* only validate selected variants

* validateVariantsAndSubmit method

* refactor for better diff view

* refactor for better diff view

* remove empty comment

* minor refactor

* ensure segment-variants are included when validating

* turn into arrow method

* clean up

* adjust types
2026-03-09 10:23:43 +01:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>iOvergaard
d29b7d26b8 Docs: Reference CLAUDE.md from copilot-instructions instead of duplicating content (#22032)
* Initial plan

* Docs: Update .github/copilot-instructions.md to match CLAUDE.md content

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

* Docs: Reference CLAUDE.md from copilot-instructions instead of duplicating content

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: iOvergaard <752371+iOvergaard@users.noreply.github.com>
2026-03-09 10:00:19 +01:00
c6c8254386 fix: combine external-supplied pickableFilter with internal filter in picker input contexts (closes #21859) (#21989)
* fix: compose user-supplied pickableFilter with internal filter in picker input contexts (#21859)

The openPicker method in UmbDocumentPickerInputContext, UmbMediaPickerInputContext,
and UmbMemberPickerInputContext unconditionally overwrites the user-supplied
pickableFilter with the internal implementation. This prevents package developers
from providing custom filtering logic (e.g., filtering out unpublished items).

The fix composes both filters using a logical AND: the internal filter runs first
(access checks, allowedContentTypes), and if it passes, the user-supplied filter
is also evaluated. This preserves the existing behavior while enabling extensibility.

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

* refactor: extract _composePickableFilters into base UmbPickerInputContext class

Move duplicated filter composition logic from document, media, and member
picker input contexts into a shared protected method on the parent class.
This reduces cyclomatic complexity in each openPicker override and
eliminates code duplication across the three picker contexts.

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

* Rename picker filter helper to _combinePickableFilters

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-03-09 09:55:13 +01:00
e8a521eaa0 Backoffice: Export block-single package for external consumers (closes #22044) (#22045)
Export block-single client-side package.

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-03-09 08:23:02 +00:00
Andy ButlandandGitHub 65ad90e248 URL Picker: Fix validation error persisting after link selection (closes #21903, #21454) (#22034)
Fix validation of the multi-URL picker.
2026-03-09 07:50:06 +00:00
Andy ButlandandGitHub 9ceb317003 Media Picker: Show friendly inline validation error when uploading a file with required media type properties (closes #20295) (#22025)
* Align validation failed on image upload messaging with that used for not allowed.

* Trigger build

* Use destructuring.
2026-03-09 08:30:07 +01:00
Niels LyngsøandGitHub 8f42dfe1ee Sorter: Detecting outside drops when browser does not get events (#21664)
Sorter detecting outside drops when browser does not get events from the outside
2026-03-09 08:29:17 +01:00
Johannes LantzandGitHub ba688d4e4c Localization: Add for language picker modal (#22043)
* Added localize.term for umb-language-picker-modal

* Added missing Japanese translation keys for umb-language-picker-modal
2026-03-09 06:41:02 +01:00
Ronald Barendse 3c2f198960 Compute next delay to compensate for time drift 2026-03-07 22:33:17 +01:00
Johannes LantzandGitHub cb88588600 Localization: Export dictionary modal (#22038)
* Added localization for Export dictionary modal with Japanese translations

* Replaced unnecessary export key with actions_export
2026-03-07 08:39:01 +01:00
Johannes LantzandGitHub d2b8d02eae Add localize for restore entity action (#22040) 2026-03-06 19:04:03 +01:00
79ddf45e23 Data Types: Fix collection view references not showing in data type usages (closes #21649) (#21655)
* Fix FindListViewUsages to match ListView key instead of naming convention

* Align GuidUdi creation with FindUsages by adding .EnsureClosed()

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-06 11:52:12 +00:00
Nhu DinhandGitHub 32270e7548 E2E: QA Added acceptance tests for allowing folder selection in media entity picker (#21981)
* Added api helper for creating tiptap data type with media folder

* Added ui helper for remove image upload folder

* Updated ui helper for selecting media with name

* Added tests for selecting media link in multi url picker

* Added tests for media picker start node

* Added tests for image upload folder in tiptap data type

* Updated tests for user media start nodes

* Updated tests for user group media start nodes

* Make tests run in the pipeline

* Fixed comment

* Cleaned code

* Added tests for add multiple media start nodes to a user

* Reverted npm command
2026-03-06 10:35:20 +00:00
Jacob OvergaardandGitHub c9b4e1a141 build(deps): bumps @umbraco-ui/uui from 1.17.0 to 1.17.1 (#22029) 2026-03-06 10:08:06 +00:00
b1b5ce45c8 Backoffice: Add CSP nonce support for inline scripts (closes #21575) (#21581)
* Add CSP nonce support for inline scripts

* Add UseUmbracoCspNonceInjection middleware for NWebsec integration.

* Add unit tests for InjectNonceIntoDirective method.

* Add documented CSP rules to local website so any issues that conflict with these rules are surfaced in local development and testing.

* Test formatting.

* Addressed code review feedback.

* Use tag helper for nonce rendering.

* Apply suggestions from code review

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

* Move CspNonceInjectionOptions into it's own file.

* Reduce clutter in Program.cs in local web project, by moving use of documented CSP to an extension method.

* Trigger build

* Exclude CSP from template but keep in local project.

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2026-03-06 11:03:00 +01:00
Andy ButlandandGitHub b32c944299 Dependencies: Update server-side dependencies to latest patch or minor releases (#21860)
* Update server-side dependencies to latest patch or minor releases.

* Revert and comment upgrade to MailKit.

* Update Microsoft.NET.Test.Sdk to latest minor.
2026-03-06 15:55:00 +09:00
0ead90a7e1 Collection Views: Add sortable value column for custom property sorting (closes #21425) (#21479)
* Migration, model and repository data access for sorting via a sortable field.
Property editor sortable interface and implementation of JSON stored date fields.

* Add migration to populate sortable field for existing date property data.

* Added unit tests for GetSortableValue on datetime property editors.

* Fixed issues raised in code review.

* Re-use code in base from DocumentRepository to avoid additional call to SetEntitySortableValues.

* Move migration to 17.3.

* Fix merge issue.

* Move around migrations so they are in correct order

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Zeegaan <skrivdetud@gmail.com>
2026-03-06 07:40:27 +01:00
fd905e334e Content picker: Fix dynamic root not firing when inside block list (closes #22008) (#22011)
* Check whether picker is in a block. If so, act as with a new content node.

* re-use isNew flag to not increase complexity for the requestRoot function

* remove random whitespace added by visual studio

* remove ternary to reduce complexity

* move check to backend

* update fallback in SiteDynamicRootOriginFinder as well

* Revert "update fallback in SiteDynamicRootOriginFinder as well"

This reverts commit 0a14aa7393.

* Revert "move check to backend"

This reverts commit ca8b0c06da.

* get content workspace context - analogous to document-block-property-value-user-permission.workspace-context.ts. import interface for getIsNew().

* Use getContext.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-05 15:53:50 +00:00
dd12555e53 Configuration: Make MainDom acquisition timeout configurable (#22013)
* Make the hardcoded time for MainDom acquisition configurable.

* Fixed grammar in comment

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sven Geusens <sge@umbraco.dk>
2026-03-05 15:51:55 +01:00
96846799ba Audit Log: Abstracted History Info App into reusable auditLog kind (for documents & media) (#21898)
* feat(content): add shared types and repository interface for audit log kind

Introduces UmbAuditLogTagData types, ManifestWorkspaceInfoAppAuditLogKind manifest
interface, and UmbAuditLogHistoryRepository extending the core audit log repository
with getTagStyleAndText() method.

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

* feat(content): create shared audit log workspace info app element

Reusable element that receives manifest config with auditLogRepositoryAlias
and optional allowedActions. Uses UMB_ENTITY_WORKSPACE_CONTEXT for entity
unique resolution and createExtensionApiByAlias for repository lookup.
Includes reload event listener, pagination, and user avatar caching.

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

* feat(content): add auditLog kind definition and manifest registration

Registers the 'auditLog' kind for 'workspaceInfoApp' extension type,
mapping to the shared element. Includes info-app and audit-log manifest
aggregators.

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

* feat(documents,media): register audit log repos as extensions and add getTagStyleAndText

- Register UmbDocumentAuditLogRepository and UmbMediaAuditLogRepository as
  extension manifests with type 'repository' and dedicated alias constants
- Add getTagStyleAndText() method to both repositories implementing the
  UmbAuditLogHistoryRepository interface from content package
- Export audit-log types from @umbraco-cms/backoffice/content
- Deprecate getDocumentHistoryTagStyleAndText and getMediaHistoryTagStyleAndText
  utility functions (scheduled for removal in Umbraco 19)

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

* feat(documents,media): switch audit log info apps to use shared auditLog kind

- Update document and media info-app manifests to use kind: 'auditLog'
  with meta configuration (auditLogRepositoryAlias, allowedActions)
- Include repository manifests in document and media audit-log aggregators
- Wire audit-log kind manifests into the content package
- Deprecate UmbDocumentHistoryWorkspaceInfoAppElement and
  UmbMediaHistoryWorkspaceInfoAppElement (scheduled for removal in Umbraco 19)

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

* fix(documents,media): add `api` exports to audit log repositories

Required for the extension registry API loader pattern which expects
either a default or named 'api' export from the module.

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

* Linting

* refactor(content): extract renderHistoryItem to reduce cyclomatic complexity

Splits the repeat callback out of #renderHistory into a dedicated
#renderHistoryItem method, reducing the method's cyclomatic complexity
below the threshold of 9.

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

* fix(content): throw error when workspace entity unique is missing

Restores fail-fast behavior for missing entity unique in audit log
requests, matching the original document/media implementations.

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

* refactor(audit-log): move getTagStyleAndText to UmbAuditLogRepository as optional method

Removes UmbAuditLogHistoryRepository interface and adds optional
getTagStyleAndText() to UmbAuditLogRepository in core. Moves tag
types to core/audit-log and adds a default type parameter.

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

* fix(audit-log): export repository alias constants from package entry points

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

* Linting and tidy-up

* Fixes canceled Rollback modal error

* Removed the `allowedActions` property

* feat(content): formalize `auditLogAction` extension type

Add proper TypeScript interfaces, default kind, and dedicated element
for the `auditLogAction` extension type, replacing the previous
untyped usage that relied on `ManifestEntityAction`.

- Define `ManifestAuditLogAction` and `MetaAuditLogAction` interfaces
- Create `umb-audit-log-action` element using `uui-button` (suited for
  the audit log info-app header, unlike `uui-menu-item`)
- Register default and contentRollback kind manifests
- Move contentRollback audit-log-action kind to the content module
- Separate document-specific audit-log-action manifest into its own file

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-05 12:44:40 +00:00
Andy ButlandandGitHub 59bbcaa129 Memory Management: Dispose IDisposable resources correctly in four internal classes (#22014)
* Dispose event listener created in InMemoryAssemblyLoadContextManager.

* Use using to dispose ICryptoTransform in MemberPasswordHasher.

* Dispose CancellationTokenSource in DatabaseServerMessenger.

* Dispose deserialized JsonDocument in CacheInstructionService.

* Use try/finally to ensure dispose.
2026-03-05 11:44:44 +01:00
Niels Lyngsø 2e75da2df9 Chore: decrease threshold to 15 bidirectional imports 2026-03-05 11:07:12 +01:00
Niels Lyngsø ee68fb393c fix unit test types 2026-03-05 11:04:17 +01:00
Niels LyngsøandGitHub 71ea6f4a93 Breadcrumb variant-name logic improvment (#21617)
* Adjust name logic to adapt to current-culture and not display invariant name as inherited

* refactor
2026-03-05 09:28:15 +00:00
337139f7b2 Data Access: Modifies entity repository sibling queries to support custom database p[oviders (closes #21852) (#21671)
* quote table, column and alias names with SqlSyntaxProvider methods in raw sql

* refactoring private methods into new file as internal methods,
refactor new extensions into another file

* refactor GetAlias method

* Double check the change

* improve code health

* change new static classes into public static partial class NPocoSqlExtensions

* resolve some Copilot review suggestions

* revert Copilot suggestion because it decreases code health

* revert test

* compare in with LOWER, change two methods from private to protected in UmbracoDatabaseFactory

* revert Query.cs in this PR

* Refactor for code health and fixing raw sql

* divers small issues fixed

* refactor two methods to respect the DRY pricipal

* update IQuery interface

* clean up

* revert refactoring for CodeScene

* delete obsolete Test

* rename method

* remove new methods and updates, which are not relevat for this PR

* prepare for additional states in the future

* don't mix string building methods

* fix SQL injection danger

* fix test for reverted methods

* another SqlSyntax issue

* fix update

* fix reverted changes

* restore change for this PR

* restore change for this PR

* fix merge bug

* update formating

* extend ISqlSytax for database independent autoIkrement feature

* fix DTOs, extend ISqlSyntax

* fix tests

* revert

* updates

* diverse SqlSyntax and NPoco related updates for custom databse providers

* fix names

* squash merge v173/20453-DTO-attributes-fixed into v173/20453-final-sql-syntax-fixes

* merge

* add default implementation to interface

* fix tests

* fix PrimaryKey for multi columns

* test fix

* Resolve the issues with SqlSyntaxProvider for SQLite. If executed correctly, a single test would reveal the problem.

* add another test

* revert changes which causes even more issues

* fix SQL syntax

* fix column const naming

* ensure column const names from v17.2

* add comment for change

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs

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

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeTemplateDto.cs

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

* resolve review comments

* Make ReferenceMemberName consistent across all DTOs (use constants defined on the referenced DTO).

* Ensure [ExplicitColumns] attribute exists on all DTOs.

* Ensure we consistently use PrimaryKeyColumnName over PrimaryKeyName.

* Fix further inconsistency to use only TemplateNodeIdColumnName.

* Fixed trailing whitespace.

* Restored primary key constraint name on ContentVersionCleanupPolicyDto (it doesn't seem in scope of PR to remove this).

* Removed the confusing PrimaryKeyColumnName constants for multi-column primary key DTOs where the constant refers to only one of the key columns.

* ReferenceMemberName needs to be a C# property name, so it's safer to use nameof.

* Comment fix.

* Amended accessibility modifiers.

* revert unnecessary changes

* revert unnecessary changes

* revert unnecessary changes

* fix SQLite escape variants

* fix typo

* simple (typo) fixes of Copilot review comments

* solve another Copilot review comment

* improve comments and minimise changes

* add an detailed change comment

* resolve review and revert all integration test. Tests changes will be done in the PostgreSqlProvider-npocp branch like some unit tests.

* remove InsertWithSpecialAutoIncrement()

* update WhereIn() for case sensitive databases

* fix special char in test comment

* throw exception for invalid values

* remove values type check

* add extra check

* resolve review comments

* revert more changes with question

* refine method SiblingsSql of EntityRepository, add another AndSelect() method overload to NPocoSqlExtensions.

* resolve review

* fix replacement

* trigger new pipeline build

* trigger new pipeline build

* Added comment explaining why withAlias: false is needed.

* Add additional tests around sibling retrieval.

* Add tests for the AndSelect overloads.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-05 09:21:40 +01:00
Niels LyngsøandGitHub 684d08b631 Content Rollback: error handling (#22018) 2026-03-05 07:49:52 +00:00
Jacob Overgaard e1cc6926ab build: optimises azure static builds in order not to consume too many environments 2026-03-05 08:31:30 +01:00
f7bab4521b Content Rollback: Abstracted rollback into reusable contentRollback entity action and modal kinds (#21939)
* Content Rollback: Abstract document rollback into reusable entity action and modal kinds

Create shared `rollback` entity action kind and modal kind in the content package,
enabling reuse for upcoming entity types (e.g., Elements in v18). The document
rollback now uses these kinds via manifest meta, while old APIs are preserved
with @deprecated annotations for backward compatibility.

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

* Content Rollback: Address PR review feedback

- Add validation for manifest meta in rollback modal element, throwing
  descriptive errors if rollbackRepositoryAlias or detailRepositoryAlias
  are not configured
- Remove non-null assertions in favor of validated manifest access
- Fix deprecated requestVersionByDocumentId to delegate through the
  generic requestVersionById interface method
- Remove unused requestVersionByDocumentId deprecated method (original
  method was requestVersionById, not requestVersionByDocumentId)

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

* Renamed "rollback" to "contentRollback"

for class names and manifest kind.

* Content Rollback: Move repo aliases to entity action meta; remove modal kind

Move rollbackRepositoryAlias and detailRepositoryAlias from the modal
kind manifest meta to the entity action meta, passing them as modal
data. Remove the contentRollback modal kind entirely and register the
modal element directly. Introduce UMB_CONTENT_ROLLBACK_MODAL token so
the entity action no longer needs a configurable rollbackModalAlias.
Deprecate document-level modal constants in favor of content-level ones.

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

* Fixed linting errors

* eslint missed an export! 🤦

* refactor(backoffice): rename UMB_ENTITY_ACTION_ROLLBACK_KIND_MANIFEST to UMB_ENTITY_ACTION_CONTENT_ROLLBACK_KIND_MANIFEST

Address PR review feedback to include "Content" in the manifest constant name for consistency.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:09:56 +01:00
Andy ButlandandGitHub 31aa6e5847 Decimal Property Editor: Align step precision with database storage (closes #22003) (#22004)
Align decimal property editor precision with storage.
2026-03-04 13:33:35 +01:00
bd52e95a10 Management API: Add item ancestors endpoints returning item response models (#21874)
* Create item endpoints that return ancestor IDs for a given collection of entity IDs.

* Return item models instead of just IDs.

* Use async methods.

* Use NamedItemResponseModel for container ancestor endpoints.

* Simplify the usage of ItemAncestorService - use less assumptions about structure and use generic mapping for basic response models.

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-03-04 12:04:46 +01:00
8b9bfb3a65 URL and Alias Caches: Optimize for invariant documents (#21558)
* Optimize (memory usage, database storage, and processing time) document URL and alias cache for invariant documents.
Store invariant content with NULL languageId instead of duplicating records for each language.

* Additional integration tests verifying aspects of changed functionality.

* Implement and test that URLs and aliases are updated when a content type changes from variant to invariant or vice versa.

* Tidied up migration.

* Corrected file name.

* Apply suggestions from code review

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

* Further updates from code review, resolved warnings.

* Use rebuild key defined in constant in migration.

* Handle possibility of custom URL providers generating different URL segments per culture.

* Resolve breaking change.

* Handling breaking change in DocumentUrlDto.

* Tidied up code comments

* Fix issue where URL aliases on variant content with a shared property were not being recorded.

* Tidy up comment.

* Fix breaking change in nullability.

* Fix breaking change in nullability (2).

* Revert "Fix breaking change in nullability (2)."

This reverts commit c77a37c855.

* Fix failing integration tests.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 11:55:09 +01:00
Nhu DinhandGitHub e89cc4961b E2E: QA Updated failing acceptance tests to match the UI changes (#22002)
* Updated ui helper for verify the file uploads

* Updated tests due to test helper changes

* Updated tests for block due to UI changes

* Added comment for the failing tests
2026-03-04 17:34:51 +07:00
Andreas ZerbstandGitHub 7f9f58f559 E2E: QA: added acceptance tests for preview (#21967)
* Added preview helper

* Added preview helpers

* Added preview tests

* Updates based on comments

* reinitialize the preview locators for the pop up preview page

* Cleaned up based on comments

* Update smokeTest command in package.json
2026-03-04 10:27:13 +00:00
Andreas Lykke BorgandGitHub c313e6f112 List view: Added labels entity bulk action buttons (#21964)
* Added labels to checkboxes and buttons and fixed checkbox alignments

* Reverted u200B as label in checkboxes
2026-03-04 08:47:42 +00:00
24a01df870 Content Picker: Pass preview flag to published content cache lookups (closes #21972) (#21975)
* Ensure content picker correctly handles preview state.

* Return null for unresolvable content picker values, preserve routing properties.

* Apply suggestions from code review

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2026-03-04 09:31:12 +01:00
8ae768d4eb Update badge icon (#21911)
* Change the badge icon svg content

* Updates "badge" icon with "id-card.svg"

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-03-04 04:15:46 +00:00
59fd3938a2 Content Types: Fix API response for cancelled delete operation (#21758)
* fix(core,api,web): fix backoffice UI errors on notification cancellation

- Fix ContentTypeServiceBase.DeleteAsync() to use PublishCancelableAsync
  directly instead of delegating to the sync Delete() method, which
  silently swallowed cancellation and always returned Success.
- Extract shared deletion logic into private PerformDelete() method
  to keep both Delete() and DeleteAsync() DRY.
- Mark ProblemDetails with notificationsDeliveredViaHeader extension
  when Umb-Notifications header carries event messages, preventing
  the frontend from showing duplicate error toasts.
- Add notificationsDeliveredViaHeader to UmbProblemDetails type and
  skip redundant ProblemDetails notification in try-execute controller.
- Add integration test for DeleteAsync cancellation detection.

* fix(core,api,web): fix backoffice UI errors on notification cancellation

- Fix ContentTypeServiceBase.DeleteAsync() to use PublishCancelableAsync
  directly instead of delegating to the sync Delete() method, which
  silently swallowed cancellation and always returned Success.
- Extract shared deletion logic into private PerformDelete() method
  to keep both Delete() and DeleteAsync() DRY.
- Mark ProblemDetails with notificationsDeliveredViaHeader extension
  when Umb-Notifications header carries event messages, preventing
  the frontend from showing duplicate error toasts.
- Add notificationsDeliveredViaHeader to UmbProblemDetails type and
  skip redundant ProblemDetails notification in try-execute controller.
- Add integration test for DeleteAsync cancellation detection.

fix: #12636

* fix(core): reduce PerformDelete arguments and trim LOC

Address CodeScene quality gate failures:
- Reduce PerformDelete from 5 to 4 parameters by resolving
  EventMessages internally via EventMessagesFactory.Get()
- Trim lines of code to stay within the 1000 LOC threshold

* refactor(core): extract obsolete container methods into partial class

Split ContentTypeServiceBase into two partial class files to address
CodeScene's "Lines of Code in a Single File" quality gate (1007 > 1000).

The #region Containers block was chosen for extraction because all its
methods are already marked [Obsolete] and scheduled for removal in
Umbraco 18, replaced by IContentTypeContainerService and
IMediaTypeContainerService. The region is fully self-contained with no
inbound calls from the rest of the class.

This is a compile-time only change — partial classes produce identical
IL output. No public API, behavior, or binary compatibility impact.

* Revert "refactor(core): extract obsolete container methods into partial class"

This reverts commit 6fd8fd23f6.

* Pass eventMessages from the caller into PerformDelete instead of being re-obtaining from the factory.

* Use try/finally in test to ensure clean-up.

* Restore removed comments.

* Revert client-side updates.

* Revert client-side updates (2).

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-03 17:41:05 +00:00
Andy ButlandandGitHub c8564f33e7 Health Checks: Add check for imaging HMAC secret key (#21991)
* Added healthcheck for Imaging:HMACSecretKey configuration setting.

* Added healthcheck for Imaging:HMACSecretKey configuration setting.

* Address code review feedback.

* Removes unnecessary test.

* Use service in healthcheck.
2026-03-03 17:17:15 +00:00
Mads RasmussenandGitHub d7d3e4a613 Backoffice Toast Notifications: prevent double toast notifications on cancelled server operation statuses (#21993)
Ignore server-notified operation statuses
2026-03-03 17:46:57 +01:00
ad6813ca4a Search field: Added aria-label and name to search input for accessibility (closes #21938) (#21962)
* Add aria-label and name to search input for accessibility

- Add aria-label attribute to search input using localized placeholder text
- Add name attribute ("search-input") to provide form field identification
- Fixes Google Console warning about missing id/name on form field
- Improves WCAG 3.3.2 compliance (Labels or Instructions)
- Improves WCAG 2.5.3 compliance (Form input identifiable names)

Closes #2193

* Reused localized label

* Tidy-up/linting

---------

Co-authored-by: Emma L Garland <1649855+emmagarland@users.noreply.github.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-03-03 16:46:12 +00:00
Andy ButlandandGitHub 317319cd9f Imaging Configuration: Auto-generate HMAC secret key for new installs (#21976)
* Auto-generate HMAC secret key for imaging on new installs.

* Address code review feedback.

* Log results of configuration operations.

* Refactored to use the Attempt pattern.
2026-03-03 16:21:54 +00:00
Andy ButlandandGitHub f58894eb8a Media Picker: Allow custom folder types when creating inline folders (closes #21850) (#21959)
* Allow custom folder types when creating from media picker.

* Adjust selector padding.

* Corrected call to await.

* Addressed feedback from code review.
2026-03-03 15:41:42 +00:00
Jacob Overgaard 57c4339dd1 build(login): syncs lockfile 2026-03-03 16:18:50 +01:00
Jacob Overgaard ad6606e048 build(login): syncs package files 2026-03-03 16:16:40 +01:00
059c25fb3e Media Workspace: Fix collection view showing root items after creating new folder (closes #21700) (#21753)
* Set entity unique before scaffold processing to fix collection view for new media folders.

* Instead of moving setUnique() earlier in the provider, fix the consumers to observe the unique observable rather than reading synchronously.

* Addressed code review point on context observation.

* implement satisfies type check

* minor refactor

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-03 13:47:39 +00:00
f954a3fe74 Templating: Prevent editing of templates and partial views in production runtime mode (closes #21564) (#21600)
* Prevent save of partial view file when using runtime production mode, and verify for partial views and templates with integration tests.

* Display warning when templates and partial views are not editable in the backoffice.

* Share styles.

* Validate at the partial view API whether updates are allowed based on production runtime mode.

* Add similar checks for templates, handling case where metadata updates are allowed.

* Add integration tests for verifying behaviour in production mode.

* Fix the breaking changes on the constructor of the service classes.

* Use IOptions (we don't need live updates for this setting).

* Addressed code review feedback.

* Add IsProductionMode private property on both updated services.

* Move create template check to validate method.

* Remove entity actions create/delete/rename for templates and partial views whilst running in production mode.

* Addressed code review feedback.

* include server in condition name

* move tag to bottom right corner of workspace

* introduce info modal

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-03-03 13:24:41 +00:00
Andy ButlandandGitHub 0340d8c37d Block editors: Make block editors read-only when document is trashed (closes #21973) (#21982)
* Make block editors read-only when document is trashed.

* Hide update button on block non-line workspace if document is read-only.
2026-03-03 13:06:15 +00:00
Andy ButlandandGitHub d224251098 Backoffice Search: Default global search to current section (closes #21621) (#21636)
* Default the global search to the current section.

* Refactor to use meta element to target a section alias.
2026-03-03 13:45:11 +01:00
Andreas ZerbstandGitHub 27d03c3632 E2E: QA: Added acceptance tests for dynamic roots (#21966)
* Added tests

* Updated tests

* Updated smokeTest script, will be reverted

* Fixes based on comments

* Fixes

* Reverted command
2026-03-03 12:40:38 +00:00
Andy ButlandandGitHub 49eba63172 Hybrid Cache: Resolve IsPublished() returning false in preview mode (closes #21983) (#21985)
Resolve IsPublished() returning false in preview mode for published content.
2026-03-03 12:58:43 +01:00
88d07d5c3f Content Type: Introduce Entity Content Type Entity Context (#21817)
* introduce entity content type entity context

* introduce for document, media, member workspaces and trees

* provide for Document card

* add conditions and reorganize

* Stabilize entity content-type condition tests

* Set media content-type context in item card

* rename example

* Remove entity-type condition and refs

* add example

* Refactor entity-action components to consume UMB_ENTITY_CONTEXT instead of requiring entityType/unique props.

* update stories

* Add UmbEntityContext to collection item elements

* add tests

* Add context boundary to entity collection items

* Add test for entity context boundary

* Update umb-entity-collection-item-element-base.element.test.ts

* Apply suggestion from @Copilot

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

* Update umb-entity-collection-item-element-base.element.ts

* Apply suggestion from @Copilot

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

* Update umb-entity-collection-item-element-base.element.test.ts

* Update umb-entity-collection-item-element-base.element.ts

* Remove entity props from umb-entity-actions-bundle

* Revert "Update umb-entity-collection-item-element-base.element.ts"

This reverts commit 9e4ef79150.

* Provide entity context on host and update tests

* Revert "Provide entity context on host and update tests"

This reverts commit f618a8216e.

* fix lint errors

* Test entity context boundary for collection items

* Update entity-content-type-unique.condition.test.ts

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 11:30:11 +00:00
8e662db487 Tiptap RTE: Table node-view refactor to fix popover menus (closes #20614) (#21696)
* Tiptap Table: fix popover positioning for row and column grips

Refactored the table extension to use a proper container structure
and separate popovers for row and column context menus.

Key changes:
- Added UmbTableView with block container, inner table container,
  widgets container, and overlay container structure
- Created TableHandlePlugin to manage grips and popovers centrally
- Changed from single shared popover to separate row and column
  popovers, fixing the issue where column menu always appeared
  at the first column position
- Updated CSS styles to support the new container structure
- Added proper cleanup when tables are removed from the editor

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

* Deprecates `UmbBubbleMenu` extension

No longer used internally.
There were issues with the popover and Tiptap editor state.

* Tiptap Table node-view refactor

* Exports `UmbTableView`

* Handles `mouseleave` event

* Adds readonly guard and dynamic grip offset for table handles

Prevents grips/popovers from appearing and dispatching transactions
when the editor is in readonly mode. Replaces hardcoded 16px container
offset with dynamic bounding rect computation to stay in sync with CSS.

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

* Uses TableMap for cell indices instead of DOM child indexes

Resolves cell row/column via ProseMirror position resolution and
TableMap.findCell, which correctly handles merged cells (colspan/rowspan)
instead of relying on DOM child indexes.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-03 11:13:48 +00:00
6535a9e753 Tiptap RTE: Adds actionButton kind for toolbar extensions (closes #21682) (#21703)
* Improvement: Use `when` callback parameter in tiptap toolbar disabled button

Use the callback parameter from Lit's `when` directive instead of a
non-null assertion to access the icon value.

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

* Feature: Add `actionButton` kind for tiptap toolbar extensions

Create a new `actionButton` kind that uses the disabled button element,
replacing manual `element` overrides in the Unlink, Undo, and Redo
toolbar manifests.

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

* Improvement: Add dedicated element for `actionButton` tiptap toolbar kind

Addresses review feedback by creating a proper `umb-tiptap-toolbar-button-action`
element for the `actionButton` kind instead of reusing the `-disabled` element.

- Uses `api.isDisabled()` for the disabled state (not `!isActive`)
- Types manifest correctly via generic on base class
- Makes base `UmbTiptapToolbarButtonElement` generic so subclasses can
  specify their manifest kind
- Deprecates `umb-tiptap-toolbar-button-disabled` (scheduled for removal in v19)

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

* Improvement: Add base API class for `actionButton` toolbar extensions

Introduces UmbTiptapToolbarActionButtonApiBase with a default isDisabled
implementation that returns !isActive(editor), so third-party extensions
get meaningful disabled state without needing to override isDisabled.

Updates undo, redo, and unlink APIs to use the new base class, removing
their redundant isDisabled overrides. Adds a comment explaining the
implicit re-render dependency in the action button element.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:00:27 +00:00
005241ee4a Upload Field: Show filename after file upload (closes #21587) (#21887)
* fix(media): display filename in upload field preview

Add visible filename text to the file and image upload field preview
components. Previously, the file preview only showed an icon and the
image preview only used the filename as invisible alt text.

The filename is extracted from the File object when available (blob
URLs during upload), falling back to the last path segment for
persisted server paths.

Closes #21587

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

* fix(media): display filename in audio, video, and SVG upload previews

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

* fix(media): move filename display to parent upload field with file-info bar

Move filename rendering from 5 individual preview components into the
parent input-upload-field element. The filename and remove action now
share a bordered bar below the preview. Filename is plain text during
upload (blob URL) and a clickable link to the file when saved.

Reverts preview components to their original state (preview only).

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

* style updates

* link style adjustment

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-03-03 10:19:02 +00:00
Andy ButlandandGitHub fcbedf2d6f Service registration: Allow running Umbraco with different combinations of backoffice, website and delivery API (closes #21622) (#21630)
* Add support for running website without backoffice.

* Add support for running delivery API without website or backoffice.

* Reverted unncessary idempotent checks on individual builder extensions.

* Integration tests for service registrations.

* Integration HTTP tests for service registrations.

* Tidy up and code review feedback.

* Remove unnecessary null check.

* Ensure models builder references are added to attempt to resolve the deliver API setup.
2026-03-03 09:49:21 +01:00
43f91ef47e Account logout: Handle revocation request for cookie-stored back-office tokens (closes #21918) (#21944)
* Handle revocation request for cookie-stored back-office tokens

* Addressed feedback from code review.

* Use OpenIddict constant instead of hardcoded string

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-03-03 06:37:07 +01:00
d2a6bd0a40 Media Picker: Allow folder selection in media entity picker (closes #21885) (#21895)
* Allow folder selection in media entity picker

* Also handle user and user group media root node picker.

* Allow file selection for users and user groups, fixing failing E2E test.

* Changed media start nodes for user/user group to select folders only

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-03-03 05:12:23 +00:00
Andy ButlandandGitHub 0e6a04c4fc Public Access: Handle inherited protection gracefully in modal dialog (closes #21965) (#21971)
Handle inherited public access gracefully in modal dialog.
2026-03-02 21:08:52 +00:00
Andy ButlandandGitHub 6a00d61337 Media Dropzone: Clarify error messages when file upload is not allowed (closes #21506) (#21708)
* Improve messaging on failed file uploads.

* Introduced utility for getting the extension from a file and addressed other code review comments.
2026-03-02 18:35:51 +00:00
1d3216e08c Members: Enable sorting on member table and order member groups by name (closes #21960) (#21963)
* Add support for member sorting by member type.

* Make the backoffice member table sortable by the supported fields.

* Sort member groups by name.

* Fixed linting issue.

* Use UmbDirection for sort direction

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-03-01 21:59:39 +01:00
eaed6cb0c9 Accessibility: Added title attribute for icon in content types (#21956)
* Fix missing <title> attribute for Icons in document types in backoffice

* Move the title attribute to uui-button from umb-icon.

* Removed color option from lable and title. Added prefix "Change icon:" in the title. Prefix managed from the localization.

* Improve icon tooltip accessibility and i18n in content type header

  - Add defensive check in #iconTitle to avoid "undefined" text when icon is unset
  - Move colon separator from translation strings to component template
  - Use consistent label for both title and aria-label on icon button
  - Add Spanish and Italian translations for changeIcon key

* Fix failing test by using an exact match for a label.

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-27 18:51:27 +00:00
bbab9f8006 Move/Duplicate: Filter tree picker based on allowed parent rules. (#21646)
* Document Types returns a list of allowed parent keys

* Media types included

* Add selectable filter for duplicate action based on allowed parents.

* Add optional selectable filter provider support to move action.

* Add selectable filter provider for move action in documents.

* Add selectable filter provider for move action on media.

* Optimize filter providers by using allowedAsRoot property directly.

* Refactor document move action to use repository for selectable filter.

* Move media filter logic from provider to repository .

* Centralize allowed-parent logic in data sources.

* Remove document item lookup from duplicate action.

* Simplify custom filter assignment in move action.

* Remove unused import.

* Rename getSelectableFilter method in document duplicate action..

* Refactor move to action for documents.

* Refactor of media move to action.

* Refactor duplicate action and remove unused imports.

* Clean up.

* Use .js extension for media tree type import

* Export move action and fix imports.

* add interfaces for type safety

* local implementations

* make linter happy

* make linter happy

* Filter out current node in MoveTo action

* Return error instead of throwing on fetch

* Use typed getters for structure data sources

* remove unused

* Add UmbTreeItemModel typing to move-to actions

* align paramater naming

* Defer move repository lookup until after modal

* Fetch type data concurrently with Promise.all

---------

Co-authored-by: NillasKA <kramernicklas@gmail.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-02-27 15:36:09 +00:00
Sven GeusensandGitHub 8340ff015e Integration Tests: Fix null reference errors (#21953)
* Dont blow up GetTestOptions when inside testfixtures

* Dont blow up Reference resolving when working with proxies

* Improve GetAssemblyFolders nullability

* More verbosity
2026-02-27 16:27:08 +01:00
82f805abca Management API: Add document and data types tree search endpoints (#21628)
* Messy implementation of documenttypes and datatypes

* Formatting and move service injection to constructor

* cleanup and bubble up new constructor

* Allow folder or item only searches

* feedback pr & subsequent refactoring

* Apply review suggestions

* Update openapi file

* Used constant, resolved minor layout warnings.

* Fix parent key lookup in tree search to check both folders and items.

* Remove TreeItemKind.None from flags enum.

* Add TODOs for removing the default implementation on the interfaces.

* Add permission integration tests.

* Update OpenApi.json.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-27 14:44:23 +00:00
Andy ButlandandGitHub 53615e3d86 Repeatable Textstring: Skip empty strings in validation and persistence (closes #21912) (#21915)
* Skip empty strings in repeatable textstring validation and persistence.

* Override RequiredValidator for repeatable textstring to treat all-empty arrays as no value.
2026-02-27 15:18:23 +01:00
Nathaniel NunesandGitHub db1cf01342 Accessibility: Add tooltips to block grid entry actions (#21958)
#20422 - Add tooltips to block grid entry actions for improved accessibility
2026-02-27 14:21:37 +01:00
Niels LyngsøandGitHub a7164d56f2 Slider Preset: make it easier to read the code (#21955)
refactor to make easier to read
2026-02-27 13:06:18 +01:00
Nhu DinhandGitHub 32d7f528f1 E2E: QA Updated AllowEditInvariantFromNonDefaultIsTrue tests to match the UI changes (#21950)
* Updated ui helper for add block list button

* Make AllowEditInvariantFromNonDefaultIsTrue tests run in the pipeline

* Removed .skip since the issue is resolved

* Fixed tests for submit an empty URL in RTE property

* Make TiptapToolbar tests run in the pipeline

* Reverted npm command
2026-02-27 08:44:24 +00:00
Mads RasmussenandGitHub 6f2cd9ec8e Backoffice Performance: Add inflight request deduplication to item data request managers (#21767)
* add inflight request cache to all item request managers

* Use inflight request cache for item data

* Add tests for Item Data Request Manager
2026-02-27 08:38:50 +01:00
f3adc14a72 Performance: Optimize handling of content type updates (#21910)
* Claude's suggestions

* Rewrite for tags based hybrid cache eviction and optimize the converted, in-memory cache eviction

* Replicate cache invalidation/flushing optimizations for the media cache service

* Do not perform Examine re-indexing for "other" changes on content types

* Clean up TODOs

* Use configured batch size for indexing, and use cached structure for checking publish status

* Default implementations of new interface methods to prevent breaking changes

* Clean out more TODOs

* Refactor logic to extension methods

* Add missing notification handlers to cache tests

* Add additional test coverage.

* Remove OnChange from settings for transient notification handler.

* Adds a migration to clear the hybrid cache to ensure all items are tagged by content type.

* Clear all converted content on type change in auto models builder mode.

* Apply the same fix for data type updates.

* Apply the same fix for data type updates (2).

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-27 08:29:51 +01:00
Lars-Erik AabechandGitHub b0756cb626 Integration tests: Re-virtualized CustomMvcSetup (#21947)
Re-virtualized CustomMvcSetup

Someone finalized my beautiful "last-hook-in-setup-for-mvc-things". 🥹
2026-02-27 08:26:27 +01:00
5958198f0d Fix: Increase size of modal listing property editors (#21825)
* Changed the modal size to medium data type picker modals.

* Updated the icon and label alignment so that icon always align vertically top and label in each starts from same position.

* Linting

* Adds `justify-items: center` for "Create new" button icon

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-27 05:08:21 +00:00
39170fbf66 Entity Signs: Enable entity signs for media items (closes #21786) (#21832)
* Add support for entity signs on media items.

* Code linting tweaks

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-26 22:51:48 +00:00
1719e5d07f Member Group Picker: Add server-side paging to public access modal (closes #21790) (#21834)
* Add pagination to the member group picker.

* Linting

...and use of `when` directive ;-)

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-26 21:42:39 +00:00
7a07c1160f User History: Improve recent history display with better labels and de-duplication (#21656)
* Improve recent history display with better labels and de-duplication.

* Addressed code review comments.

* Handle race condition where wrong item would get updated.

* Uses Lit `repeat` directive

* Reverted breaking-changes

added deprecation comments (for removal in v19)

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-26 19:58:36 +00:00
Andreas ZerbstandGitHub 35fb0a74d7 E2E: Move test helpers and builders into the acceptance test project and publish as @umbraco/acceptance-test-helpers (#21773)
* Move testhelpers and builder into the acceptance test project

* Updated imports in tests

* Updated readme

* Updated postinstall to exclude setting up config

* added a cleanup when npm packing

* update tsconfig path mapping to @umbraco/acceptance-test-helpers

* Added dist to git ignore

* Adds separate README files for npm and GitHub
 README.md: contributor-focused (test docs)
 README.npm.md: consumer-focused (package docs)
 cleanse-pkg.js swaps them during npm pack

* Updated to swap READMEs on npm pack. So the consumer README is the one being released

* Add npm publish pipeline for @umbraco/acceptance-test-helpers

* Configure package.json for npm publishing as @umbraco/acceptance-test-helpers

* Updated missing imports

* Cherrypicked helper changes

* Updated tests

* Updated name of builder

* added tslib

* Fixed test

* Renamed

* Add nbgv version step for test helpers npm package

* Fixes based on comments

* More fixes

* Removed unnecessary imports

* Fix naming of storage_state_path

* Added recommend for storage state

* Create console file if not present
2026-02-26 17:58:42 +01:00
950b5861c7 Block List: consistent spacing between blocks (#21750)
* simpler and more consistent css for block list and block single

* adjust spacing only for default views

* adjust inline and support for Block Grid

* simplify gap css for Grid Entries

---------

Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-26 16:48:15 +00:00
1d9d44a470 Create new folder on enter in media picker (#20648)
* Create new folder on enter in media picker

* Move CSS properties and change value for placeholder.

* Add localization key for labels and placeholder.

---------

Co-authored-by: Emma L Garland <emmagarland77@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: engjlr <enl@umbraco.dk>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-26 14:19:11 +00:00
0780c22002 Templates: Add optional Central Package Management support to UmbracoProject and UmbracoExtension templates (#21641)
* Adding CPM into Umbraco Project

* Adding CPM for UmbracoExtension

* remove CPM from umbraco templates

* remove change from readme

* update readme for umbracoproject

* Adding CPM options to Umbraco Project and Umbraco Templates

* update name param

* make central to default option

* Update templates/UmbracoExtension/Umbraco.Extension.csproj

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

* Update templates/UmbracoExtension/.template.config/template.json

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

* Update templates/UmbracoProject/.template.config/template.json

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

* add PackageManagement into Visual studio display

* Apply suggestions from code review

* Fix ascii art and typo.

* Remove trailing commas in template.json files.

* Aligned casing and grammar between package management choices.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-26 13:33:39 +00:00
Niels LyngsøandGitHub 7c031f8ae4 is-routable-context-condition (#21428) 2026-02-26 13:29:58 +00:00
e9a6d52814 Collection: Provide UmbEntityContext for entity collection item elements (#21847)
* Add UmbEntityContext to collection item elements

* add tests

* Add context boundary to entity collection items

* Add test for entity context boundary

* Update umb-entity-collection-item-element-base.element.test.ts

* Apply suggestion from @Copilot

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

* Update umb-entity-collection-item-element-base.element.ts

* Apply suggestion from @Copilot

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

* Update umb-entity-collection-item-element-base.element.test.ts

* Update umb-entity-collection-item-element-base.element.ts

* Revert "Update umb-entity-collection-item-element-base.element.ts"

This reverts commit 9e4ef79150.

* Provide entity context on host and update tests

* Revert "Provide entity context on host and update tests"

This reverts commit f618a8216e.

* Test entity context boundary for collection items

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-26 14:25:18 +01:00
Niels LyngsøandGitHub 27618a3621 Block Editors: Align create label (#21731)
Use 'add content' to align with the create modal and other Block Editors
2026-02-26 13:31:19 +01:00
e0bb8f294e E2E: QA Added acceptance tests for for scheduled publishing (#19794)
* Cleaned up

* Make ScheduledPublishing tests run in the pipeline

* Updated npm command

* Increased timeout

* Updated npm command

* Addec console log to test in the pipeline

* Make tests run in the pipeline

* Removed step to verify that the document is published since it doesn't work in the pipeline - only works locally

* Update npm command

* Fixed tests

* Fixed comments

* Removed unnecessary comments

* Revert npm command

---------

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2026-02-26 10:47:50 +00:00
Engiber LozadaandGitHub 1cb8b2c8d2 Media Picker Modal: Fix missing tooltip on media items in picker modal. (#21913)
* Set title attribute on media card in picker.

* Add title attribute to uui-card-media in media inputs.
2026-02-26 09:55:50 +01:00
Andy Butland 2f381fe700 Fix after merge. 2026-02-26 07:05:51 +01:00
Andy Butland c3fc3c949e Merge branch 'release/17.2.1' 2026-02-26 06:59:27 +01:00
Andy ButlandandGitHub 0a40fe7364 Data Types: Use configured ValueType when creating Label data types (closes #21853) (#21914)
Use configured ValueType when creating Label data types.
2026-02-26 12:44:07 +09:00
a8526429ba Cache: Ensure local cache instructions count towards last synced ID (#21907)
* Ensure local cache instructions count towards last synced ID

* Add obsoletion message to the interface.

* Fixed failing integration tests, then refactored them so they call and test the non-obsolete method.

* Rework the solution to retain existing functionality

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-25 15:03:26 +00:00
Andy Butland 040f27c673 Bumped version to 17.2.2. 2026-02-25 15:03:55 +01:00
Andy ButlandandLaura Neto c9c16d2605 URL Info: Fix invariant content URLs missing under non-default language domains (closes #21866) (#21883)
* Show correct URLs for invariant content under non-default language domains.

* Use configured domain hosts instead of request host for fallback URL filtering.

* Addressed feedback from code review.

* Fixed code warnings.

* Update file references in integration test csproj.

* Simplify invariant URL culture filtering by determining cultures upfront

Instead of querying all cultures and post-processing to remove irrelevant
URLs, determine the relevant cultures before the loop by checking which
domains are assigned to the content's ancestor path.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-02-25 11:28:39 +01:00
df952c92a2 URL Info: Fix invariant content URLs missing under non-default language domains (closes #21866) (#21883)
* Show correct URLs for invariant content under non-default language domains.

* Use configured domain hosts instead of request host for fallback URL filtering.

* Addressed feedback from code review.

* Fixed code warnings.

* Update file references in integration test csproj.

* Simplify invariant URL culture filtering by determining cultures upfront

Instead of querying all cultures and post-processing to remove irrelevant
URLs, determine the relevant cultures before the loop by checking which
domains are assigned to the content's ancestor path.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-02-25 11:27:26 +01:00
Andy Butland 62eb91675d Database Cache: Fix full database cache rebuild dropping variant and composed property values (closes #21863, #21882) (#21890)
* Resolve full database cache rebuild dropping variant and composed property values

* Addressed feedback from code review.
2026-02-25 08:01:25 +01:00
Andy ButlandandGitHub 09206a62ac Database Cache: Fix full database cache rebuild dropping variant and composed property values (closes #21863, #21882) (#21890)
* Resolve full database cache rebuild dropping variant and composed property values

* Addressed feedback from code review.
2026-02-25 07:42:23 +01:00
Andy ButlandandGitHub d3b3661efc Dotnet Templates: Update default UmbracoVersion template value using MSBuild target (closes #21889) (#21893)
Apply version replacement to extensions template.
2026-02-25 01:31:53 +00:00
b1ca081613 Image cropper and file upload: Implemented automatic naming of uploaded file (closes #21764) (#21775)
* Added a feature to have automatic naming of the uploaded media files as per requested by #21764

* Replaced UMB_PROPERTY_DATASET_CONTEXT by UMB_NAMEABLE_PROPERTY_DATASET_CONTEXT to ensure proper use of isNameablePropertyDatasetContext

* style: fix import ordering to match eslint rules

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

* style: order furthest relative imports first

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

---------

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-02-24 13:11:26 +00:00
Andy ButlandandGitHub 15a75b1c5d Document Repository: Batch document IDs in GetContentSchedulesByIds to avoid SQL parameter limit (closes #21865) (#21868)
* Update GetContentSchedulesByIds to retrieve data in groups to avoid overrunning the SQL parameter count.

* Protect against duplicate retrieval if duplicate IDs are provided.
2026-02-24 11:45:57 +01:00
Andy ButlandandGitHub 5df7ff184f Backoffice Search: Discard stale search results when switching providers (closes #21784) (#21849)
* Discard stale search results when switching providers.

* Applied change from code review.
2026-02-24 10:44:17 +00:00
Andy Butland 5642c624d8 Remove legacy Windows path length checks and related tests (#21884)
Removed explicit 260-character path length checks from PhysicalFileSystem.GetFullPath and deleted associated unit tests. Updated tests to focus on path normalization and validity, and improved path assertions for clarity and cross-platform compatibility. No longer enforce or test for legacy Windows path length restrictions.
2026-02-24 11:21:18 +01:00
Andy ButlandandGitHub 7c0e332001 Content Picker: Fix dynamic root resolution for new unsaved documents (closes #21870) (#21880)
Correct resolution of dynamic root for new unsaved documents.
2026-02-24 09:46:45 +00:00
8b018c8178 Entity Data Picker: Add text filter feature toggle for Collection Data Sources (#21732)
* Add data-source package and integrate in input-entity-data

* Add optional description to collection items

* introduce extension picker data source

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

* nest manifest data

* Hide pagination when all items are shown

* Add a fallback page size

* merge extension insight code with extension code

* clean up

* Add optional description support to default item ref

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

This reverts commit e02881e8b6.

* fix post merge

* add input-extension utilizing input-entity-data

* proxy value and selection

* add todo

* temp hardcode config

* add typed config model

* Support multiple extension types in filters

* Use extensionTypes filter and deprecate type

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

* More explicit type name

* Expose allowedExtensionTypes as a @property on UmbInputExtensionElement

* Add text filter support for entity data picker

* remove reexport as this is not public available

* remove unused

* clean up

* clean up

* Add storage and getter for allowedExtensionTypes

* Inline collection view alias and remove constant

* Update vite.config.ts

* Update manifests.ts

* Update extension.picker-data-source.ts

* add tests for extension picker data source

* change to an observable feature config

* make feature object optional

* add unit tests

* Reference condition class directly in manifests

* clean up observers if data source type changes

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-24 09:30:51 +00:00
0f8b38c1b4 Razor Template Debugging: Allow Umbraco projects to work with the Razor cohosting editor (#21861)
* Allow Umbraco projects to work with the Razor cohosting editor

* Apply suggestions from code review

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-24 08:59:54 +01:00
Andy ButlandandGitHub 24540e11e0 Content Type: Fix null property description displaying as "null" string (closes #21873) (#21879)
Ensure an empty string is rendered for a null property description rather than a "null" string.
2026-02-24 07:44:36 +00:00
a2e3b929bd UmbracoExtension template: Use runtimeConfigPath for automatic auth (#21838)
* UmbracoExtension template: Use runtimeConfigPath for automatic auth

Use hey-api's runtimeConfigPath to pre-configure the generated client
by copying umbHttpClient's config (baseUrl, credentials, auth) at
initialization time. This eliminates the need for entrypoint auth setup
via consumeContext/getOpenApiConfiguration.

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

* feat: updates extension with newly generated SDK files

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:33:31 +00:00
9976b9f523 Examine: Keep track of rebuilding in memory on startup and move use of LongRunningOperationService to user triggered rebuilds (#21821)
* Keep track of rebuilding in memory

* Apply suggestion from @Copilot

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

* Remove comment

* Revert back to original with lock

* Apply suggestion from @Zeegaan

* Remove unused

* Adress review comments

* Improve in-memory rebuild tracking for index rebuilder.

* Add cross-server rebuild status tracking via ILongRunningOperationService.

* Ensure index is used in operations, to allow rebuild of different indexes concurrently.

* Use Task.Delay.

* Resolve breaking change in constructor.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-24 06:56:03 +01:00
Mads RasmussenandGitHub f934c88911 Extension: Introduce extension core module and umb-input-extension element (#21705)
* Add data-source package and integrate in input-entity-data

* Add optional description to collection items

* introduce extension picker data source

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

* nest manifest data

* Hide pagination when all items are shown

* Add a fallback page size

* merge extension insight code with extension code

* clean up

* Add optional description support to default item ref

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

This reverts commit e02881e8b6.

* fix post merge

* add input-extension utilizing input-entity-data

* proxy value and selection

* add todo

* temp hardcode config

* add typed config model

* Support multiple extension types in filters

* Use extensionTypes filter and deprecate type

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

* More explicit type name

* Expose allowedExtensionTypes as a @property on UmbInputExtensionElement

* remove reexport as this is not public available

* remove unused

* clean up

* clean up

* Add storage and getter for allowedExtensionTypes

* Inline collection view alias and remove constant

* Update vite.config.ts

* Update manifests.ts

* Update extension.picker-data-source.ts

* add tests for extension picker data source
2026-02-23 16:54:26 +00:00
Andy Butland 79b3058a96 Bump version to 17.2.1. 2026-02-23 16:39:49 +01:00
23062762aa Media: Mark touchstart handler as non-passive using @eventOptions decorator (#21845)
The touchstart handler on the image cropper focus setter needs to call
preventDefault() to prevent scrolling during focal point drag. Use Lit's
@eventOptions({ passive: false }) decorator to explicitly declare this,
resolving the browser warning about non-passive event listeners.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-23 11:00:26 +01:00
e5ec2a9f11 Examine: For indexing in the RTE, replace all HTML tags with spaces to make sure word boundaries are preserved (#21797)
* For indexing in the RTE, replace all HTML tags with spaces to make sure wqord boundaries are preserved. Closes #21778

* Trim the returned string and adjust test cases to match new expected output #21778

* Address review comments:
- Updated XML docs
- Removed trimming in unit tests
- Moved HTML strip implementation to an extensions method

* Removed redundant regexes

* Address comment formatting

* Address failed tests by not replacing multiple characters if the replacement is String.Empty to preserve existing behavior

* Remove unnecessary partial and using.

* Add tests for introduced overload of StripHtml, fix found issues with replacement regex, then optimised by removing second regex and replaced with string operations.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 10:54:20 +01:00
9279a951c1 Routing: Fix umbracoUrlName being ignored in DefaultUrlSegmentProvider on culture-variant content when property is invariant (closes #16791) (#21735)
* Fix umbracoUrlName not working on multi sites

* update documentUrlServiceTests

* Use "is false" for false comparison

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 09:30:20 +00:00
dc1821e86f Cache Refreshers: Fix change tracking for content types (#21856)
* Fix change tracking for content types

* Update src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs

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

* Updated comments for ContentTypeChangeTypes

* Clean up comments

* Revert "Clean up comments"

This reverts commit e17904c202.

* Actually clean up comments

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 07:56:32 +00:00
30aade8e3a Unit Testing: Add comprehensive coverage for BlockEditorVarianceHandler (#21706)
* test: add comprehensive test coverage for BlockEditorVarianceHandler

- Add tests for AlignPropertyVarianceAsync method (collection alignment)
- Add tests for AlignedExposeVarianceAsync method
- Add edge case tests for segment variations
- Add tests for multiple items and deduplication scenarios
- Remove TODO comment

Fixes #21706

* refactor: reduce code duplication in BlockEditorVarianceHandler tests

- Add CreateBlockListValue helper method to eliminate repeated setup code
- Remove redundant test cases to reduce duplication
- Consolidate similar tests while maintaining essential coverage

Fixes code duplication issues reported in PR #21706

* fix: correct assertion in AlignPropertyVarianceAsync_Removes_NonDefault_Culture_Values test

When culture variance is disabled (ContentVariation.Nothing), the culture
should be set to null, not preserved. This matches the behavior tested in
Removes_Default_Culture_When_Culture_Variance_Is_Disabled test.

* fix: always deduplicate expose entries in AlignExposeVariance

Deduplication should always occur at the end of AlignExposeVariance,
even when no alignment is needed. This ensures duplicate expose entries
are removed regardless of whether variance alignment occurred.

* fix: remove expose entries when ContentData is missing

Expose entries that don't have matching ContentData should be removed
from the expose list. This ensures data consistency and prevents orphaned
expose entries.

* test: add 8 additional test cases for BlockEditorVarianceHandler

Adds comprehensive test coverage for:
- Culture assignment scenarios
- Segment variation handling
- Multiple ContentData items
- Edge cases (missing element types, no matching expose)
- Variation matching scenarios

* refactor: eliminate code duplication in BlockEditorVarianceHandler tests

Extract common test patterns into helper methods:
- CreatePropertyValues: Creates property values from configuration tuples
- CreateBlockPropertyValues: Creates block property values with alias/culture/segment
- CreateBlockItemVariations: Creates block item variations from tuples
- ExecuteAlignPropertyVarianceAsync: Executes AlignPropertyVarianceAsync with common setup
- ExecuteAlignedExposeVarianceAsync: Executes AlignedExposeVarianceAsync with common setup
- ExecuteAlignExposeVariance: Executes AlignExposeVariance with common setup
- SetupAlignedExposeTest: Sets up test data for AlignedExposeVarianceAsync tests

This eliminates copy-pasted code patterns across multiple test methods.

* refactor: eliminate duplication in AlignedPropertyVarianceAsync tests

Extract common test setup into ExecuteAlignedPropertyVarianceAsync helper method.
This eliminates duplication in:
- Assigns_Default_Culture_When_Culture_Variance_Is_Enabled
- Removes_Default_Culture_When_Culture_Variance_Is_Disabled
- Ignores_NonDefault_Culture_When_Culture_Variance_Is_Disabled
- AlignedPropertyVarianceAsync_Returns_As_Is_When_Variation_Matches

* fix: add missing using statements for Task, IList, IEnumerable, Func

* fix: correct Assert.ThrowsAsync usage - await the task when accessing exception

* fix: await Assert.ThrowsAsync directly to get exception

* remove: AlignPropertyVarianceAsync_Throws_When_PropertyType_Is_Null test

* fix: mock should return null for unknown content types in AlignExposeVariance test

* Revert production code changes - keep only test additions

* Remove bug-fix verification tests - moved to PR #21801

* refactor: consistently use CreateBlockListValue helper in all tests

* test: restore AlignExpose_Can_Handle_Variant_Element_Type_With_All_Invariant_Block_Values test

* docs: clarify why mock returns null for unknown content types

* refactor: use configuration class to reduce argument count in CreateBlockPropertyValues

* fix: add missing closing brace for Assert.Multiple block

* fix: remove leftover merge conflict marker

* fix: remove duplicate method definitions

* Remove unused code and usings. Encapulate BlockPropertyValueConfig. Fix code warnings.

* Standardise test naming, order of methods and use of Assert.Multiple.

* Complete test coverage with additional tests for AlignedExposeVarianceAsync.

---------

Co-authored-by: root <root@dragon.second>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 07:22:28 +00:00
Niels LyngsøandGitHub 13a095934e CurrentUserModal: use getContext instead of consumeContext (#21843)
use getContext instead of consumeContext
2026-02-20 15:11:10 +01:00
Nathaniel NunesandGitHub f01ea69919 Accessibility: Add title attributes to buttons in block list entry and property editor UI (#21842)
#21841 - Add title attributes to buttons in block list entry and property editor UI for better accessibility
2026-02-20 13:03:38 +00:00
4cb81b2e0e Document Workspace: Update document status on publish and unpublish (closes #21650) (#21668)
* Ensure document status shown in the Infor workspace view is up to date after unpublish and save/publish operations.

* Apply suggestions from code review

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

* Further feedback from code review.

* Fix false-positive pending changes after save and publish by ensuring the property value preset builder reconstructs objects with the same property key order.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-02-20 13:42:03 +01:00
3ac5986f19 Fix: BlockEditorVarianceHandler deduplication and orphaned expose entries (#21801)
* Fix: BlockEditorVarianceHandler deduplication and orphaned expose entries

- Fix deduplication not running when no alignment needed
- Fix orphaned expose entries not removed when ContentData missing

Fixes #21799
Fixes #21800

* Update src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs

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

* Fix indentation and add bug-fix verification tests

* Code tidy, use helpers in tests.

---------

Co-authored-by: root <root@dragon.second>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-20 12:01:32 +00:00
Yari MariënandGitHub 8b8b1c607a Localization: Added missing translation values for field label on create member form (#21835)
fix(member-info-screen): added translation for confirmPassword to fix visible translation key on member view/info
2026-02-20 12:52:05 +01:00
Andy ButlandandGitHub e9ba715e62 Server Events: Invalidate client-side cache for composing types on composition deletion (#21831)
* Invalidate client-side cache after removal of composed content type.

* Address feedback from code review.
2026-02-20 15:12:57 +09:00
0ce31e0793 Media Querying: Fix MediaAtRoot() to use IMediaNavigationQueryService root keys (#21807)
* Add media navigation support to PublishedContentQuery

Introduced IMediaNavigationQueryService as a dependency and updated constructors to resolve it. Refactored ItemsAtRoot to accept a navigation query service, enabling MediaAtRoot to retrieve root media items via navigation queries. ContentAtRoot and MediaAtRoot now use the appropriate navigation query service for root item retrieval.

* Add IMediaNavigationQueryService support to content query

Extended PublishedContentQuery and ContentFinderByConfigured404 to accept and use IMediaNavigationQueryService alongside IDocumentNavigationQueryService. Updated constructors and service registrations to ensure both navigation services are available for enhanced content and media navigation scenarios.

* Add obsolete constuctors and expand PublishedContentQuery tests

Introduce [Obsolete] constructor overloads for PublishedContentQuery and ContentFinderByConfigured404 to support legacy usage, scheduled for removal in Umbraco 19. Refactor ItemsAtRoot for clarity. Significantly expand PublishedContentQueryTests with comprehensive unit tests covering constructor validation, Content/Media overloads, root item retrieval, and search functionality, including paging, ordering, and culture context. Add test helpers and mocks to improve test coverage and reliability.

* Fixes to constructor overloads.

* Re-organise tests into unit and integration (so the former, that don't need integration setup, will run more quickly).

* Remove low value integration tests.

---------

Co-authored-by: Fabian Beier <Fabian.Beier@aa-g.de>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-19 11:57:03 +00:00
bb567524d4 Media Collection: Introduce Entity Actions for cards (#21816)
* refactor to use the collection item extension point

* Add actions slot to media collection item card

* Set actions slot button background in media card

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

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-19 10:34:14 +01:00
a1627b82d3 RTE Link Picker: Fix media selection to allow items not permitted at root. (#21678)
* Exclude media folders and remove media-type filtering.

* Remove unused import.

* general clean up

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-02-19 09:30:43 +00:00
Andy Butland 588e93ae75 Re-ordered methods in class. 2026-02-19 09:40:29 +01:00
Andy ButlandandGitHub 9ea0520a46 Repository Caches: Fix GUID read repository cache key collision causing GetAll failures (closes #21756) (#21762)
* Fix GUID read repository cache key collision with int-keyed repositories.

* Remove GUID read repository for templates.

* Ensure GUID read repository cache keys are invalidated.

* Further optimisation of by GUD GetAll reads.

* Move default repository cache timespan to a centralised constant.

* Further use of centralised constant.

* Add GetGuidKey<T>(Guid id) and update callers to use it.
2026-02-19 07:58:07 +00:00
Andy Butland 4ca0d041f2 Merge branch 'release/17.2' 2026-02-19 07:46:06 +01:00
Andy Butland 51e91c88ae Bump version to 17.2.0. 2026-02-19 06:49:24 +01:00
Niels Lyngsø b2af37a149 Merge branch 'release/17.2'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
2026-02-18 15:39:11 +01:00
Jacob OvergaardandGitHub 725a0322ed build(deps): bumps @umbraco-ui/uui to 1.17.0 (#21765) 2026-02-18 14:59:45 +01:00
Mads RasmussenandGitHub 7f1e30a22e Document Collection: Enable Entity Actions on cards (#21802)
* Use card view kind for Document Collection

* Remove unused UmbUserDetailModel import

* Update document-collection-item-card.element.ts

* combine elements

* render actions
2026-02-18 13:29:26 +00:00
Mads RasmussenandGitHub f7661a310f Document Collection: Reuse card view kind (#21791)
* Use card view kind for Document Collection

* Remove unused UmbUserDetailModel import

* Update document-collection-item-card.element.ts
2026-02-18 12:48:21 +01:00
Andy ButlandandGitHub 48a431eeec Packaging: Fix package migration plans re-running all steps when a new step is added (closes #21730) (#21734)
* Ensure package migration steps only run once by moving the override of IgnoreCurrentState to true to the derived AutomaticPackageMigrationPlan, where it's needed.

* Add integration test to verify the fix.

* Fix failing integration test (the test migration plans were leaking outside of the new test, and being picked up by the DI container for other tests.
2026-02-17 10:17:50 +01:00
Laura NetoandGitHub d0acfa46bc Audit: Fix container update operations incorrectly logged as new (#21774)
Fix EntityTypeContainerService.UpdateAsync using wrong AuditType

UpdateAsync was logging AuditType.New instead of AuditType.Save,
causing container update operations to be recorded as creations
in the audit log.
2026-02-17 08:43:10 +01:00
Andy ButlandandGitHub dbdafbdc19 Migrations: Re-trust untrusted foreign key and check constraints on SQL Server and fix bulk inserts to prevent recurrence (#21744)
* Be explicit about creating foreign key constrains with check (already the default).

* Add a migration to attempt to ensure that all constrains a trusted.

* Updated name of migration class.

* Ensure long timeout for migration.

* Update BulkInsertRecordsSqlServer to use SqlBulkCopyOptions.CheckConstraints and verify that no untrusted constraints remain afterward.

* Ensure NPoco InsertBulk uses SqlBulkCopyOptions.CheckConstraints by introducing UmbracoSqlServerDatabaseType (subclass of SqlServer2012DatabaseType) that overrides InsertBulk to pass SqlBulkCopyOptions.CheckConstraints.

* Also handle InsertBulkAsync.
2026-02-17 07:11:24 +01:00
Mads RasmussenandGitHub 3ef02bd3fa Tree: Provide UmbEntityContext from the tree item context base (#21770)
* Provide UmbEntityContext from tree item

* add tests to ensure entity context is provided
2026-02-16 19:37:04 +01:00
Andy ButlandandGitHub ba0d865ac9 Decimal Property Editor: Increase step size precision for configuration fields (closes #21759) (#21769)
Increase precision available to decimal data types.
2026-02-16 15:30:15 +01:00
dependabot[bot]andJacob Overgaard 541958b8c3 Bump qs
Bumps the npm_and_yarn group with 1 update in the /src/Umbraco.Web.UI.Client directory: [qs](https://github.com/ljharb/qs).


Updates `qs` from 6.14.1 to 6.14.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 10:31:35 +01:00
9c1a810fc7 Permissions: Fix GetPermissionsAsync to resolve permissions from nearest ancestor (#21741)
* Fix GetPermissionsAsync to use path-based permission inheritance

GetPermissionsAsync was querying only explicit per-node permissions,
ignoring the ancestor-based inheritance model. Nodes without explicit
permissions would get group defaults instead of inheriting from their
nearest ancestor with explicit permissions. This caused tree filtering
to hide child nodes that should have been visible.

Replace per-node permission queries with GetPermissionsForPath which
walks the entity path to resolve inherited permissions correctly. Also
pass object types through to enable batched entity lookups.

* Apply suggestion from @Copilot

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

* Optimise GetPermissionsAsync.

* Add benchmark test.

* Add benchmark test.

* Add integration tests for default and isolated permission resolution

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-16 09:57:34 +01:00
695807b32e Delivery API: Make the Delivery API "access" attributes public (closes #21677) (#21760)
* Make the Delivery API "access" attributes public

* Update src/Umbraco.Cms.Api.Delivery/Filters/DeliveryApiAccessAttribute.cs

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

* Update src/Umbraco.Cms.Api.Delivery/Filters/DeliveryApiMediaAccessAttribute.cs

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

* Also make the VersionedDeliveryApiRouteAttribute public

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-16 08:20:09 +01:00
dependabot[bot]andJacob Overgaard 30c6350643 Bump the npm_and_yarn group across 2 directories with 2 updates
Bumps the npm_and_yarn group with 1 update in the /src/Umbraco.Web.UI.Client directory: [markdown-it](https://github.com/markdown-it/markdown-it).
Bumps the npm_and_yarn group with 2 updates in the /src/Umbraco.Web.UI.Login directory: [lodash](https://github.com/lodash/lodash) and [markdown-it](https://github.com/markdown-it/markdown-it).


Updates `markdown-it` from 14.1.0 to 14.1.1
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/14.1.0...14.1.1)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `markdown-it` from 14.1.0 to 14.1.1
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/14.1.0...14.1.1)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-version: 14.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: markdown-it
  dependency-version: 14.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 21:53:21 +01:00
785168cacd Persistence Models: DTO attributes fixes (#21670)
* quote table, column and alias names with SqlSyntaxProvider methods in raw sql

* refactoring private methods into new file as internal methods,
refactor new extensions into another file

* refactor GetAlias method

* Double check the change

* improve code health

* change new static classes into public static partial class NPocoSqlExtensions

* resolve some Copilot review suggestions

* revert Copilot suggestion because it decreases code health

* revert test

* compare in with LOWER, change two methods from private to protected in UmbracoDatabaseFactory

* revert Query.cs in this PR

* Refactor for code health and fixing raw sql

* divers small issues fixed

* refactor two methods to respect the DRY pricipal

* update IQuery interface

* clean up

* revert refactoring for CodeScene

* delete obsolete Test

* rename method

* remove new methods and updates, which are not relevat for this PR

* prepare for additional states in the future

* don't mix string building methods

* fix SQL injection danger

* fix test for reverted methods

* another SqlSyntax issue

* fix update

* fix reverted changes

* restore change for this PR

* restore change for this PR

* fix merge bug

* update formating

* extend ISqlSytax for database independent autoIkrement feature

* fix DTOs, extend ISqlSyntax

* fix tests

* revert

* updates

* diverse SqlSyntax and NPoco related updates for custom databse providers

* fix names

* fix PrimaryKey for multi columns

* Resolve the issues with SqlSyntaxProvider for SQLite. If executed correctly, a single test would reveal the problem.

* add another test

* revert changes which causes even more issues

* fix column const naming

* ensure column const names from v17.2

* add comment for change

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs

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

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeTemplateDto.cs

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

* resolve review comments

* Make ReferenceMemberName consistent across all DTOs (use constants defined on the referenced DTO).

* Ensure [ExplicitColumns] attribute exists on all DTOs.

* Ensure we consistently use PrimaryKeyColumnName over PrimaryKeyName.

* Fix further inconsistency to use only TemplateNodeIdColumnName.

* Fixed trailing whitespace.

* Restored primary key constraint name on ContentVersionCleanupPolicyDto (it doesn't seem in scope of PR to remove this).

* Removed the confusing PrimaryKeyColumnName constants for multi-column primary key DTOs where the constant refers to only one of the key columns.

* ReferenceMemberName needs to be a C# property name, so it's safer to use nameof.

* Comment fix.

* Amended accessibility modifiers.

* Fixed/tidied comments.

* Fixed references from UserGroupDto.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-13 11:42:36 +00:00
MoleandGitHub 8ace1e0e94 Persistence models: Fix incorrect webhook DTO (#21736)
Fix incorrect webhook dto
2026-02-13 11:48:48 +01:00
Jacob OvergaardandGitHub 4ce56e4bc9 Entity Actions: Adds a descriptive title to the first action so you know what it does (#21739)
* fix: adds a title to the first entity action in the entity actions bundle, otherwise you do not know what it does, unless the icon is very descriptive

* calculate the label once

* concatenate data-mark string better
2026-02-13 08:51:24 +01:00
Nhu DinhandGitHub 0b6507a02e E2E: QA Updated acceptance tests for adding block element to match the UI changes (#21679) 2026-02-12 23:29:11 +07:00
a374042e89 Textbox/area: Add character countdown message (closes #19505) (#21722)
* Show character count and instant exceed validation.

* Show character count for textarea editor.

* Add character-count utility and use in editors.

* Rename char count state, add tests, fix imports.

* Update textbox character messages in locales.

* Apply suggestions from code review

* Align textarea and textbox in use of #getMaxLengthMessage private helper function.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-12 13:27:27 +00:00
bc0110079c Package Manifest: Enable cache buster token replacement for extensions (closes #16893) (#21709)
* fix(manifest): replace %CACHE_BUSTER% token in extension paths served by manifest API

Move cache buster replacement to the presentation layer (manifest controllers)
instead of the infrastructure service. The importmap replacement stays in
HtmlHelperBackOfficeExtensions where it was already handled.

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

* Ordered usings.

* Add unit test for cache buster token replacement.

* Apply suggestions from code review

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

* Fix ambiguous controller constructors.

* Make ReplaceCacheBusterTokens void since it mutates in-place.

* Defensively code against special characters in the cache buster hash.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-12 11:35:58 +00:00
394de9e2d0 Imaging: Intelligent format detection for thumbnail generation (#21570)
* Imaging: Add format parameter to thumbnail component with webp default

Adds a format parameter to the imaging resize API endpoint and the
umb-imaging-thumbnail component. The component defaults to 'webp' format
for optimal browser support and smaller file sizes.

This ensures that non-image file types (like PDFs) that have custom image
providers can render thumbnails correctly by explicitly requesting an
output format instead of relying on the original file extension.

Changes:
- Add format query parameter to ResizeImagingController
- Pass format through IReziseImageUrlFactory to ImageUrlGenerationOptions
- Add format property to UmbImagingResizeModel TypeScript type
- Add format property to umb-imaging-thumbnail element (default: 'webp')

https://claude.ai/code/session_01GP7N2iTashrG1cBdYVSW97

* Imaging: Include format in cache key generation

Fix cache key to include the format parameter so that different format
requests with identical dimensions are cached separately.

https://claude.ai/code/session_01GP7N2iTashrG1cBdYVSW97

* Imaging: Refactor to use ImageResizeOptions record

Introduces ImageResizeOptions record to encapsulate resize parameters,
addressing CodeScene's "Excess Number of Function Arguments" warning.

Changes:
- Add ImageResizeOptions record with Height, Width, Mode, Format properties
- Add new CreateUrlSets overload accepting ImageResizeOptions
- Mark old CreateUrlSets overload as obsolete (removal in v19)
- Update controller to use new options pattern

https://claude.ai/code/session_01GP7N2iTashrG1cBdYVSW97

* Imaging: Add explicit obsolete method to satisfy API compatibility

The API compatibility checker requires the method to exist explicitly
in the implementation, not just via default interface method.

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

* Imaging: Add unit tests for imaging store format parameter

Tests verify that:
- Different formats are cached separately (webp vs png)
- Crops with and without format are cached separately
- Cache operations work correctly with format parameter

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

* Add API compatibility suppression for resize imaging endpoint

Suppress CP0002 for adding optional 'format' parameter to the resize
imaging controller endpoint. The HTTP API remains backward compatible
as existing clients simply won't send the new parameter.

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

* Fix API compatibility for IReziseImageUrlFactory

Restructure interface to maintain binary compatibility:
- Keep original 4-parameter method as required (marked obsolete)
- Add new ImageResizeOptions overload with default implementation
- Factory overrides new method to properly handle format parameter

This allows existing implementations to continue working while
new code uses the ImageResizeOptions overload with format support.

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

* chore(api): regenerate API compatibility suppression file

Regenerated the CompatibilitySuppressions.xml file with proper metadata
to suppress the breaking change detection for the optional format parameter
added to ResizeImagingController.Urls method. This change is backward
compatible at the HTTP API level.

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

* feat(imaging): automatic format conversion for non-image files

Move format conversion logic from frontend to backend IImageUrlGenerator
implementations to handle format defaults intelligently based on source
file types.

Why Backend Should Handle This:
1. **Source-aware decisions**: Backend has access to source file extension
   and can determine if it's a true image (jpg, png) or processable
   non-image (pdf with plugin)

2. **Consistent behavior**: All consumers (backoffice, APIs, custom code)
   get consistent format handling without duplicating logic

3. **Plugin compatibility**: When ImageSharp plugins add support for new
   file types (e.g., PDF thumbnails), the system automatically converts
   them to web-compatible image formats

4. **User override preserved**: Explicit format parameter still works as
   an override, giving users control when needed

Changes:
- Add Format property to ImageUrlGenerationOptions for explicit format requests
- ImageSharp implementations auto-detect non-image files and default to WebP
- ReziseImageUrlFactory passes format directly instead of via FurtherOptions
- Frontend imaging-thumbnail component removes hardcoded format='webp' default
- Backend now handles: format override > auto-detect non-images > keep original

Example Scenarios:
- JPEG → No format added (keeps JPEG)
- PNG → No format added (keeps PNG)
- PDF (with plugin) → Auto-adds format=webp
- Any file + explicit format param → Uses specified format

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

* fix(imaging): improve robustness and code quality

Address code review feedback with three improvements:

1. Add URI parsing error handling to prevent UriFormatException crashes
   when malformed URLs are passed to RequiresFormatConversion()

2. Extract magic string array to class-level constant (TrueImageFormats)
   to eliminate duplication and provide single source of truth

3. Remove inconsistent default interface implementation that didn't pass
   format parameter, forcing concrete implementations to handle it properly

All changes maintain backward compatibility and improve code safety.

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

* refactor(imaging): move format determination to factory layer

Refactors format conversion logic from ImageSharp implementations to the factory layer for better separation of concerns and maintainability.

Changes:
- Add ContentImagingSettings.TrueImageFormats configuration (native image formats)
- Move format determination logic to ReziseImageUrlFactory.DetermineOutputFormat()
- Simplify ImageSharp v1 & v2 generators (remove duplicate RequiresFormatConversion())
- Add backward-compatible obsolete constructor to ReziseImageUrlFactory
- Add 50 comprehensive unit tests for format determination and configuration

Benefits:
- Single Responsibility: ImageSharp generators only generate URLs, don't make business decisions
- DRY: Eliminated 70+ lines of duplicated code between ImageSharp packages
- Configurable: TrueImageFormats setting allows customization
- Testable: Format logic tested independently of ImageSharp

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

* refactor(imaging): repurpose ImageFileTypes for native format determination

Repurposes the existing unused ContentImagingSettings.ImageFileTypes setting instead of adding a new TrueImageFormats property. This provides better configuration control and eliminates the need for a new setting.

Changes:
- Repurpose ContentImagingSettings.ImageFileTypes (was unused, now active)
- Update ReziseImageUrlFactory to use ImageFileTypes for format determination
- Update TemporaryFileConfigurationPresentationFactory to use config instead of IImageUrlGenerator
- Add comprehensive XML documentation explaining usage in factory layer and backoffice UI
- Update all tests to reference ImageFileTypes

Benefits:
- No new configuration property needed (reuses existing setting)
- Frontend gets configurable format list instead of dynamic ImageSharp formats
- Better separation of concerns (config determines behavior, not infrastructure)
- Clearer documentation of where and how the setting is used

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

* test(core): remove duplicate test methods in ContentImagingSettingsTests

Removed duplicate test methods that were causing compilation errors:
- ImageFileTypes_DefaultValue_ContainsExpectedFormats (duplicate)
- ImageFileTypes_DefaultValue_MatchesStaticConstant (duplicate)
- ImageFileTypes_CanBeConfigured_WithCustomFormats (duplicate with incorrect test data)
- Contradicting assertion in StaticConstants_HaveExpectedValues

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

* fix(api): suppress CP0006 for IReziseImageUrlFactory.CreateUrlSets overload

Added API compatibility suppression for the new CreateUrlSets overload that
accepts ImageResizeOptions parameter. This change is backward compatible as the
concrete implementation already has both methods and the old method is marked
obsolete to guide users.

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

* fixes merge conflict

* formatting

* fix(api): suppress CP0002 for TemporaryFileConfigurationPresentationFactory constructor change

Added suppression for constructor signature change where IImageUrlGenerator
parameter was replaced with IOptionsSnapshot<ContentImagingSettings> to get
ImageFileTypes directly from configuration instead of from the image URL
generator.

This change is part of the WebP thumbnail feature and aligns with getting
native format information from ContentImagingSettings configuration.

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

* fix(api): maintain backward compatibility for TemporaryFileConfigurationPresentationFactory constructor

Instead of suppressing the CP0002 error, added back the old constructor marked
as [Obsolete] that chains to the new one. The old constructor:
- Accepts the original parameters (ContentSettings, RuntimeSettings, IImageUrlGenerator)
- Ignores the IImageUrlGenerator parameter (kept only for backward compatibility)
- Uses StaticServiceProvider to get ContentImagingSettings
- Chains to the new constructor

This maintains full backward compatibility while migrating to the new approach
where ImageFileTypes comes directly from ContentImagingSettings configuration.

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

* test(core): update StaticConstants_HaveExpectedValues test to match actual constant value

The test was checking for format order 'jpg,jpeg,png,gif,webp,bmp,tif,tiff' but
the actual constant StaticImageFileTypes is 'jpeg,jpg,gif,bmp,png,tiff,tif,webp'.
Updated the test to match the actual constant value.

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

* Apply suggestions from code review

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

* fix(api): resolve constructor ambiguity in TemporaryFileConfigurationPresentationFactory

Add [ActivatorUtilitiesConstructor] attribute to the new constructor to explicitly
indicate which constructor the DI container should use when both constructors have
the same number of parameters.

This fixes the "ambiguous constructors" error that was preventing the OpenAPI
contract test from running.

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

* fix: adds parameter even though it is unused to help the DI system figure out which constructor to use

* test(api): update ReziseImageUrlFactory test to reflect corrected query string handling

The implementation was fixed to correctly handle URLs with query strings by
stripping the query string before extracting the file extension. Updated the
test expectations to verify that PDFs with query strings are now processed
correctly and converted to WebP format, rather than returning empty results.

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

* chore: adds double obsolete constructor to stay persistent and be able to use only new constructor with same amount of arguments

* Apply suggestions from code review

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

* Address remaining PR #21570 review comments

- Add GetFileExtension() to UriExtensions for reusable URI extension extraction
- Simplify ReziseImageUrlFactory to use GetFileExtension() instead of manual parsing
- Add default implementation to IReziseImageUrlFactory to avoid CP0006 breaking change
- Remove CP0006 suppression from CompatibilitySuppressions.xml
- Fix Obsolete message format and remove unnecessary [ActivatorUtilitiesConstructor]
- Add TODO for ReziseImageUrlFactory typo rename
- Remove stale ObsoleteOverload test and low-value ContentImagingSettingsTests
- Add unit tests for UriExtensions.GetFileExtension()

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

* Avoid unnecessary second call to GetFileExtension().

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-12 11:21:06 +00:00
6a4b28b78a Management API: Optimize collection view performance by eliminating N+1 patterns (#21684)
* Added further integration test to verify list view permission checks.

* Replace per item GetPermissionsForPath calls with a single batch GetPermissions query across all unique path node ids.

* Added unit test verifying DocumentCollectionPresentationFactory and fixed constructors.

* Replace per-item IsProtected calls with a single batched GetAll query and in-memory path matching.
Compute shared ancestor path keys once for collection siblings instead of per item.

* Eliminate redundant GUID to int conversions in HasScheduleFlagProvider.

* Batch user profile resolution in collection view mapping.

* Addressed issues from code review.

* DRY up GetOwenerName and GetCreatorName in CommonMapper.

* Apply suggestions from code review

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Apply feedback from code review.

---------

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
2026-02-12 10:22:39 +00:00
Andy ButlandandGitHub 74858e2c44 Repositories: Fix GetAllContentTypeIds query on content type to generate correct SQL (#21612)
* Fixes the failing repository method ContentTypeRepository.GetAllContentTypeIds.

* Revert syntax change.
2026-02-12 06:40:12 +01:00
Niels Lyngsø 0b2516050f add data-marks 2026-02-11 21:44:34 +01:00
a6c363001c Chore: Hide generated files from GitHub PR diffs (#21713)
Mark hey-api generated client code and OpenApi.json as linguist-generated
so they are collapsed by default in GitHub diffs and excluded from
language statistics.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 12:36:54 +00:00
Mads Rasmussen 4286daa4b5 Lazy-init picker modal route 2026-02-11 13:12:51 +01:00
Niels Lyngsø e194776103 Merge branch 'release/17.2'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts
#	version.json
2026-02-11 12:51:59 +01:00
551865b79e Entity Data Picker: Register non-editor manifests statically (#21721)
fix(web): register entity data picker non-editor manifests statically

The entity data picker's picker infrastructure manifests (collection menu,
item, search, tree) were only registered dynamically via the entry point,
meaning they were unavailable until a picker data source was detected.
Split the registration so these manifests are registered statically through
the main property-editors manifest tree, while only property editor manifests
remain dynamically registered.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 11:45:24 +00:00
028e9affd9 Long Running Operations: Increase type column length and handle rebuilds of Examine indexes with long names (closes #21666) (#21715)
* Update length of type column in LongRunningOperation

* Adding truncation

* Update src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs

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

* Used constants.
Handled case where long strings with the same first 200 characters could end up clashing (very unlikely, but we can be defensive).
Introduced a TruncateWithUniqueHash extension method to support this.

* Reverted comment removal.

* Rename migration class.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-11 11:36:57 +00:00
b1ef0153f3 Block Workspace: Inline Editing Workspace to awaits all Content Type compositions before setting initial active tab (#21719)
* fix contentTypeLoaded reaction in Block Workspace without a router

* Update src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts

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

* revert check with todo comment

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-11 11:25:45 +00:00
114dc9bc91 API Docs: Fix and optimize DocFX API documentation pipeline (#21707)
* Use dotnet tool instead

* Uses pipeline build artifacts to reduce compilation time

* Fixed name

* Remove --noRestore

* Move DocFX metadata generation to Build stage

* Updated to use dlls

* Docs: Fix DocFX CSS reference for newer DocFX version

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

* Added conditions for generating DocFX metadata

* use glob pattern for DLLs

* Move DocFX to Build_Docs stage with separate DLLs artifac

* Fixes based on comments

* Undo commented out Upload C# Docs job

* Removed Build_Docs from Nuget Release so our docs isnt blocking

* Added dependsOn so Upload_API_Docs is only done when Build_Docs are finished

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-11 10:00:07 +01:00
Niels LyngsøGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>nielslyngsoe
a1bcabf44e (fixes #21178) (#21672)
* Initial plan

* Add tab badges for validation errors in block workspace

Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>

* Build successful - frontend changes compiled

Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>

* remove double naming

* remove double naming in no router block workspace

* fixing code

* debugger

* binding view contexts

* inline mode binding

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>
2026-02-10 14:29:38 +00:00
Andy ButlandandGitHub f3595a8ae6 User Management: Avoid discard changes dialog after enabling/disabling a user (closes #19019) (#21702)
Avoid discard changes modal when enabling or disabling a user.
2026-02-10 14:17:23 +00:00
Niels LyngsøandGitHub bfee99c5b0 Block Workspace: Tabs navigation, Cherry-pick from #21672 (#21693)
* cherry-pick from #21672

* cherry pick tab rendering to handle one more case

* move the root route down for it to stay an empty path.

* Revert empty root path commit

* fullPath for root includes 'root'

* revert claude settings commit

* refactor accordingly to feedback
2026-02-10 14:17:14 +00:00
Andy Butland ed963b2dbb Bumped version to 17.2.0-rc2. 2026-02-10 15:16:52 +01:00
Niels Lyngsø fc43d5316f Merge branch 'release/17.2' 2026-02-10 14:42:35 +01:00
Niels LyngsøandGitHub 79dfe76286 Block Workspace: renme root-tab to 'generic' (#21699)
* renme to generic

* only use label
2026-02-10 13:31:01 +00:00
Andy ButlandandGitHub ecb4ecdc1a Developer Experience: Clarify obsoletion warning messages (#21695)
* Updates all obsoletion messages to use a softer expression of intent rather than stating explicit removal in a particular version.

* Code review feedback.

* Updates from code review.
2026-02-10 11:47:49 +00:00
Laura NetoandGitHub 78d6229b8e Task: Regenerate OpenAPI definition and backoffice client SDK (#21694)
chore(api): regenerate OpenApi.json and backoffice client SDK

The OpenAPI definition and backoffice TypeScript client were out of
sync with recent Management API changes already on main. Regenerated
to bring them up to date.
2026-02-10 10:01:46 +00:00
e035c8541a Developer Experience: Clarify nullability for BackOfficeAuthenticationBuilder.SchemeForBackOffice (closes #21689) (#21690)
* #21689: signature fix

* #21689: namespace fix

* Update src/Umbraco.Cms.Api.Management/Security/BackOfficeAuthenticationBuilder.cs

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

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-10 09:47:45 +01:00
b5ec111351 Docs: Add breaking changes avoidance policy to CLAUDE.md (#21683)
* Updated Claude memory files with details on how to handle binary breaking changes.

* Apply suggestions from code review

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 09:20:37 +01:00
6db77ec155 Security settings: Add UserPassword and MemberPassword properties to SecuritySettings for appsettings.json IntelliSense (#21681)
* Fix suggestion appsettings issue

* Add todo comment to remove UserPasswordConfigurationSettings and MemberPasswordConfigurationSettings

* Apply suggestions from code review

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-10 08:00:53 +00:00
Niels LyngsøandGitHub eea17292c8 Extension Slot: JS Docs update (#21645)
* remove not existing script from Claude settings

* append JS docs for extension-slot and extension-with-api-slot
2026-02-09 15:07:03 +00:00
Niels LyngsøandGitHub 21a17a4d74 Block Grid Editor: improved extension initialization for Inline Mode Blocks (#21661)
improve extension initialization life cycle
2026-02-09 15:54:14 +01:00
c4d4c17d63 Enable usage of umb-input-entity-data element without registering a propertyEditorDataSource. (#21686)
* resolve Property Editor Data Source apis in the Property Editor UI

* load input-entity-data globally

* Update src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/entity-data-picker-property-editor-ui.element.ts

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

* Create/teardown data-source API on alias change

* Initialize _dataSourceConfig inside extractor

* Allow clearing entity data picker data source

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-09 15:50:34 +01:00
Niels LyngsøandGitHub 49be5b6e9b Block Grid Editor: adjust styling of default Grid Block views (Fixes #16375) (#21685)
adjust styling of default Grid Block views
2026-02-09 13:19:08 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>AndyButlandOwain WilliamsAndy ButlandCopilot
22f7cefcdf Add endpoint descriptions to all Management API controllers (#20690)
* Initial plan

* Add EndpointSummary and EndpointDescription to all Management API endpoints

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Add missing using directive for EndpointSummary attributes

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Fix English compound words and add missing Webhook endpoint descriptions

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Add endpoint descriptions to remaining 359 Management API endpoints

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Add final endpoint descriptions - 434 of 441 endpoints now documented

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Fix concatenated words, duplicate attributes, and missing using statements

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Fix using statement alphabetical order and remove all duplicate attributes

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Revert "Fix using statement alphabetical order and remove all duplicate attributes"

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Fix using statement order and remove duplicate attributes

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Add endpoint descriptions to final 7 Management API endpoints

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Simplify endpoint descriptions - remove redundant phrases and fix compound words

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Revert Create and Update controller description simplifications

Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>

* Updated to Controllers/Document controllers (#20917)

* [ADD] Cultures Summary and Description

* tidy up from previous merge issue

* update wording on notifications controller

* UpdateDomainsController wording update

* Update the UpdateNotificationsController description / summary

* UpdatePublicAccessDocumentController update

* naming Document Blueprint as referenced in the Docs

* renaming Document Blueprint to match Group Name

* Document Blueprint updates

* Document Type Folder controllers

* Document Type Item endpoints

* Aligned casing.

* Apply suggestions from code review

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

* Fix compound words, incorrect pluralisiation and other endpoint documentation issues.

* Add descriptions to controllers missing them.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AndyButland <1993459+AndyButland@users.noreply.github.com>
Co-authored-by: Owain Williams <owaingdwilliams@gmail.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-09 12:35:40 +01:00
56b657d389 Content Type Properties: make content type property responsive (#21559)
make content type property responsive

Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-09 09:45:06 +00:00
899beaa9d1 Initial update to a couple of Management API endpoints (#20549)
* Update README.md with information about the forum

Making a small change to the Readme to signpost the Forum now that it's the place to go for help/questions

* [TASK] Initial update of some Management API endpoints

* Update src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/UpdatePreventCleanupDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/ByKeyDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/AllDocumentVersionController.cs

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

* [UPDATE] Small grammer update

* Update src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/AllDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/AllDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/ByKeyDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/ByKeyDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/RollbackDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/RollbackDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/UpdatePreventCleanupDocumentVersionController.cs

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DocumentVersion/UpdatePreventCleanupDocumentVersionController.cs

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

* [WIP] Data Types

* [WIP] Update ByKey and Configuration DataTypes

* [AMEND] Add additional Summary and Descritpion to API endpoints on DataTypes

* [AMEND] Filter / Folder / Item / References / Tree API update

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Filter/FilterDataTypeFilterController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/ByKeyDataTypeFolderController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/ChildrenDataTypeTreeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/ByKeyDataTypeFolderController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DeleteDataTypeFolderController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/UpdateDataTypeFolderController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DeleteDataTypeFolderController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/ChildrenDataTypeTreeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/UpdateDataTypeFolderController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/CreateDataTypeFolderController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/ItemDatatypeItemController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/ItemDatatypeItemController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/AncestorsDataTypeTreeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/SearchDataTypeItemController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/AncestorsDataTypeTreeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/RootDataTypeTreeController.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Owain Williams <ow@initials.co.uk>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-09 09:39:45 +01:00
4070158f8c Preset property value: acceptance test (#21498)
* Add tests for property presest value

* save method'

* update accepntance test

* ad timeout to preset value test

* Updated yaml file to copy all .cs files but still keep folder structure

* remove timeout from preset value test

* adding time wait to tests

* adding more timeout

* Adding slow test

* update test

* Format code

* Format code and add more afterEach step to clean language

* Remove test.slow() as it is unnecessary

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Nhu Dinh <hnd@umbraco.dk>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
2026-02-09 08:09:10 +00:00
f5f6ee9bb3 Tiptap RTE: Add clipboard copy/paste support for RTE blocks (#21604)
* Localize "Copy to clipboard" button label

in other Block editor components.

* Adds "Copy to clipboard" action to RTE Block component

* Check if Clipboard Property Context is available

If not, don't show the action button.

* Register "Clipboard Property Context" for Tiptap RTE

we can't make this generic for all RTEs,
since it is bound to the property-editor UI alias.

* Implemented RTE Block's `copyToClipboard()` method

* Added Clipboard Property Value Translators

for Tiptap RTE Blocks.

* Block RTE: fix clipboard paste data structure mismatch

Change paste translator to output UmbPropertyEditorRteValueType (with
markup and blocks) instead of UmbBlockRteValueModel (flat structure).
This ensures the cloner receives the correct type and can properly
regenerate content keys.

Also optimize the cloner to skip DOM parsing when markup is empty,
which is always the case for clipboard paste operations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* TipTap: debounce block updates to prevent race condition

Add debounceTime to the contents observable to batch rapid emissions
when pasting multiple blocks from clipboard. This prevents the
#updateBlocks method from being called multiple times in quick
succession.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Block RTE: address code review feedback

- Add missing await on insert() and insertFromRtePropertyValues()
- Remove redundant optional chaining on blockContentTypes.every()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-09 07:09:31 +00:00
Niels LyngsøandGitHub 145f054c2e Block Editor: UX Flow when creating one block / inline editing (#20836)
fix create ux-flow
2026-02-07 09:34:14 +01:00
023e00a975 21599: Removed the icon from redirect URL dashboard as per request in… (#21665)
21599: Removed the icon from redirect URL dashboard as per request in the issue discussion

Co-authored-by: Pasang Tamang <45009265+pasangtamang@users.noreply.github.com>
2026-02-06 19:19:03 +01:00
Niels LyngsøandGitHub 89ff306db4 Block Editors: Sync Validation Messages when in Inline Mode (Fixes #21518) (#21669)
* setup auto report for blocks validation in inline mode

* Single + grid implementation
2026-02-06 18:56:57 +01:00
32dbaf5f57 Collection: Add description support to default collection item card and ref elements (#21654)
* Add optional description to collection items

* Add optional description support to default item ref

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-02-06 17:09:35 +00:00
Mads RasmussenandGitHub 9892b35373 Collection: Fix undefined take in collection filter and hide pagination when all items are shown (#21662)
* Hide pagination when all items are shown

* Add a fallback page size
2026-02-06 17:34:21 +01:00
Mads RasmussenandGitHub bc53a73039 Extension Insights: Fix collection by avoiding shallow copy of manifests (#21660)
* fix problem with shallow copy because of js module in object

* nest manifest data
2026-02-06 17:32:27 +01:00
Nicklas KramerandGitHub 3527c01a70 Media Item Search: Fixing missing attribute for constructor (#21659)
Adding missing attribute to constructor
2026-02-06 12:19:28 +01:00
087cc8db51 Backoffice NPM: use looser peerDependencies version ranges for plugin compatibility (#21644)
* feat(backoffice): use looser version ranges for peerDependencies

Convert hoisted dependencies to peerDependencies with more permissive version
ranges that allow plugin developers to use different versions without npm conflicts.

Version range strategy:
- Pre-release (0.x.y): >=X.Y.Z <1.0.0
  Example: @hey-api/openapi-ts 0.85.0 → >=0.85.0 <1.0.0
  Allows plugins to use 0.85.0, 0.91.1, 0.99.99 without conflicts

- Stable (major.x.y where major ≥1): major.x.x
  Example: lit ^3.3.1 → 3.x.x
  Allows any patch/minor within the major version

This allows plugin developers to:
- Use @hey-api/openapi-ts 0.91.1 while backoffice uses 0.85.0
- Install compatible deduplicated versions when available
- Override versions when needed for their specific use case

Types remain available from peerDependencies (automatically installed by npm 7+).
When @hey-api reaches 1.0.0, the range will automatically become ^1.0.0.

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

* refactor(backoffice): use semver package for version parsing in cleanse script

Replace regex-based version parsing with the semver package used by npm itself.
This ensures version parsing is consistent with npm's own semver handling and is
more robust for edge cases.

Also update the version range logic to be more explicit and correct:
- Pre-release (0.x.y): >=X.Y.Z <1.0.0
- Stable (1+.x.y): >=X.Y.Z <NEXT_MAJOR.0.0

This ensures plugin developers use at least the tested version and prevents
accidental downgrades to incompatible minor versions.

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

* chore: formats file

* chore: lockfile

* fix(backoffice): use semver.minVersion to parse version ranges

Fix parsing of version ranges like ^0.85.0 by using semver.minVersion() instead
of semver.parse(). The parse() function only handles exact versions, while
minVersion() extracts the minimum version from a range.

Example transformations:
- ^0.85.0 → 0.85.0 → >=0.85.0 <1.0.0
- ^3.3.1 → 3.3.1 → >=3.3.1 <4.0.0

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

* refactor(backoffice): keep caret ranges for stable package versions

Optimize the version range conversion logic:

- Stable versions (major ≥ 1) with caret (e.g., ^3.3.1): Keep as-is
  The caret already implements the desired range: >=3.3.1 <4.0.0

- Pre-release versions (0.x.y): Convert to explicit range
  ^0.85.0 → >=0.85.0 <1.0.0 (caret only allows 0.85.z, not 0.91.z)

- Exact versions (e.g., 3.16.0): Convert to range
  3.16.0 → >=3.16.0 <4.0.0

This simplifies the published package.json while maintaining the same semantics
and is more explicit about the intent.

Examples of published peerDependencies:
- lit: ^3.3.1 (unchanged, already has correct range)
- rxjs: ^7.8.2 (unchanged)
- @hey-api/openapi-ts: >=0.85.0 <1.0.0 (converted from ^0.85.0)
- @tiptap/core: >=3.16.0 <4.0.0 (converted from 3.16.0)

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

* refactor(backoffice): use caret for stable exact versions

Simplify stable exact versions (e.g., 3.16.0) by adding a caret prefix (^3.16.0)
instead of explicit range (>=3.16.0 <4.0.0). Both are semantically identical for
stable versions but caret is more concise and conventional.

Updated version range logic:
- Stable with caret (^3.3.1): Keep as-is
- Pre-release with caret (^0.85.0): Convert to >=0.85.0 <1.0.0
- Stable exact version (3.16.0): Convert to ^3.16.0

Examples of published peerDependencies:
- lit: ^3.3.1
- rxjs: ^7.8.2
- @hey-api/openapi-ts: >=0.85.0 <1.0.0
- @tiptap/core: ^3.16.0 (now with caret)

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

* refactor(backoffice): ensure all pre-release versions get explicit range

Reorganize version conversion logic for clarity:

1. All pre-release (0.x.y) versions → explicit range: >=X.Y.Z <1.0.0
   - Examples: ^0.85.0 → >=0.85.0 <1.0.0, 0.85.0 → >=0.85.0 <1.0.0

2. Stable versions with caret (^3.3.1) → keep as-is

3. Stable versions exact (3.16.0) → add caret: ^3.16.0

This ensures pre-release version constraints are properly loosened for plugins
while maintaining stability guarantees.

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

* treat all modifiers the same

* docs: add backoffice npm package structure documentation

Add comprehensive section to CLAUDE.md explaining:
- Backoffice npm package architecture and plugin model
- Dependency hoisting strategy and version range logic
- How pre-release versions are handled vs stable versions
- Importmap as single source of truth for runtime
- Plugin development implications and expectations

Clarifies that while npm versions constrain types, the actual runtime comes
from importmap, and plugin developers should declare explicit dependencies
rather than relying on transitive deps.

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

* docs: add npm package publishing guide to backoffice CLAUDE.md

Add comprehensive section explaining:
- Why backoffice uses peerDependencies (importmap provides runtime)
- Dependency hoisting strategy and version range conversion logic
- How pre-release versions are handled differently from stable versions
- Example published peerDependencies showing final output
- Plugin developer guide with dos and don'ts
- Key files involved in the publishing process

Provides clear guidance for plugin developers on version compatibility
and explains the importmap-as-single-source-of-truth architecture.

https://claude.ai/code/session_01CBpcwXYZjzexKkM9Cf57Kb

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-05 11:25:30 +00:00
Niels LyngsøandGitHub 004569146e Block Catalogue: adapt size to the amount of BlockTypes (#21619)
adaptive Block Catalogue size based on amount of BlockType
2026-02-05 11:48:26 +01:00
Niels Lyngsø dfb46be645 remove not existing script from Claude settings 2026-02-05 11:11:40 +01:00
dfd915c6e3 Block List/Grid: Add "Clear" property action. (#21436)
* Enable Clear action for BlockList and BlockGrid editors.

* Add has-value condition to property action manifests.

* Clear manager state when value is undefined.

* Add clear kind property action.

* Register clear property action in block list/grid.

* Remove has value condition.

* Remove unused import.

* Fix missing export.

* move clear controller into kinds/clear folder

* Update manifests.ts

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-02-05 10:10:04 +00:00
Andy Butland ccc815d711 Merge branch 'release/17.2' 2026-02-05 08:23:39 +01:00
Andy ButlandandLan Nguyen Thuy 13fb4a4e09 OEmbed providers: Tighten up resource URL matching for providers (#21583)
* edit regex for oembed flickr

* Apply stricter matching with domain to all embed providers, and validate with unit tests.

* Resolved warnings and added further unit tests.

* Further tightened the URL matching regex for two providers.

* Add regex caching to OEmbedService and unit tests to verify behaviour.

* Restore flickr short URL domain.

* Use https in requests to oembed providers.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-05 07:14:43 +01:00
cd5ef5ff4d OEmbed providers: Tighten up resource URL matching for providers (#21583)
* edit regex for oembed flickr

* Apply stricter matching with domain to all embed providers, and validate with unit tests.

* Resolved warnings and added further unit tests.

* Further tightened the URL matching regex for two providers.

* Add regex caching to OEmbedService and unit tests to verify behaviour.

* Restore flickr short URL domain.

* Use https in requests to oembed providers.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-05 06:00:49 +00:00
35944c6cca External Login Providers: Fixes deleting a member with more than 1 external login provider causes an error. (#21625)
* Correcting flawed sql statement.

* Integration tests for the fix

* Resolve warnings in MemberServiceTests.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-04 20:57:23 +00:00
3a965677e0 Tests: Fix permission controller tests to use correct entity keys (#21613)
* Tests: Fix permission controller tests to use correct entity keys

The GetDocumentPermissionsCurrentUserController, GetMediaPermissionsCurrentUserController,
and GetPermissionsCurrentUserController tests were incorrectly creating user data and
passing user keys to the GetPermissions method. These controllers expect document/media
keys, not user keys.

Updated the tests to create the appropriate content/media types and entities, then pass
the correct keys to properly test the permission endpoints.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 18:23:35 +00:00
9b99d36cbf Picker: Add pagination to search results (#21593)
* add paging UI to picker search results

* implement paging in collection picker data source example

* Hide pagination when all items loaded

* Use paging object for search requests

* Forward paging params in server search queries

* Set default page size in PickerSearchManager

* Use args.paging for skip/take in search

* Update src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Reset pagination page when updating query

* dim the box while searching

* Delay loader appearance with fade-in

* Track executed search query and use in results to prevent UI flickering when entering in the search field

* Skip update when dataType is undefined

* Cancel tree loads on context destroy

* fix pagination labels

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-04 10:53:44 +01:00
af359e9f40 Code Documentation: Add XML documentation to all public members in Umbraco.Core (#21471)
* Umbraco.Core: add XML documentation to all public members

Add comprehensive XML documentation comments to all public classes,
interfaces, methods, properties, constructors, and enums in Umbraco.Core
to resolve SA1600 StyleCop warnings.

- Document ~2,500+ files across all folders (Services, Models,
  Notifications, Configuration, Cache, etc.)
- Use <summary>, <param>, <returns>, <remarks> tags as appropriate
- Apply <inheritdoc/> for interface implementations
- Use <see cref="..."/> for type references
- Preserve all existing comments

This eliminates approximately 15,500 SA1600 warnings from the project.

* Revert any code changes in the PR.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-04 09:35:20 +00:00
2917e5be97 Routing: Fix URL aliases not stored for variant content with shared alias property in DocumentUrlAliasService (#21571)
* Fix issue where URL aliases on variant content with a shared property were not being recorded.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 09:54:20 +01:00
Niels LyngsøandGitHub 1ab1311a90 Duplicate to: switch to split icon (#21616)
switch to split icon, from 'enter', to make distinguishable from move
2026-02-04 09:02:29 +01:00
bd3d2e1a3f Tiptap RTE: Add delete action support for RTE blocks (#21615)
Block RTE: Add delete action with undo support

Adds a delete button to RTE block entries that removes blocks from
both the editor HTML and the block manager data. Implements an
HTML-first deletion approach that enables Ctrl+Z undo support by
leveraging the existing _filterUnusedBlocks mechanism.

- Add delete button to block-rte-entry action bar
- Add pendingDeletions state to manager for HTML-first deletion flow
- Modify entries context to use pending deletion mechanism
- Add Tiptap API observer to process pending deletions
- Remove blocks from editor via ProseMirror transactions

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 09:00:21 +01:00
3a1f308e9c Updates Umbraco templates, removes framework choice, makes LTS version a wildcard (#21430)
* Updates UmbracoProject template

Removes the framework choice from the template configuration as it's not used and was out of date.

Updates the LTS version to a wildcard to allow minor version updates and mean this doesn't need to be updated all the time!

Updates the description in the dotnet version generated property

* Removes unnecessary build flag

* Remove Custom Version symbol

It doesn't show up in the template anyway and has been marked as obsolete

* Remove framework from Extension template also as not used

* fix: update dotnet new syntax in pipeline

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com>
2026-02-04 12:56:57 +07:00
f9abf0ad18 Management API: Add endpoints, service and repository methods for retrieving the allowed parents for content types (#21586)
* Document Types returns a list of allowed parent keys

* Media types included

* Minor fixes to namespace etc.

* Tests

* Fixing breaking change

* Correcting requested changes

* Corrected requested changes

* Removed unnecessary usings, aligned naming between service, repository and tests.
Add a new status for the default implementation (NotImplemented felt more correct than NotFound).
Updated inheritance in service layer so we maintain the NotFound behaviour for member types.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-03 15:36:08 +01:00
b99547db50 Documents: Remove deprecated entityType from property values (closes #21567) (#21609)
Documents: Remove deprecated entityType from property values

The entityType property on property values was causing "Unsaved Changes"
modal to appear after saving documents with RTE blocks. This occurred
because the server data source added entityType when reading, but
setPropertyValue did not preserve it when updating values.

Since entityType on UmbElementValueModel is deprecated and marked for
removal in v18, the cleanest fix is to stop adding it in the server
data source mapping.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:18:10 +01:00
3ab7a03254 Content Editor: Fix display of validation hint badge on tabs (#21595)
* Fix display of validation hint related to a tab.

* Update position of the badge.

* Change position for last tab.

---------

Co-authored-by: engjlr <enl@umbraco.dk>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-02-03 10:13:50 +00:00
Niels LyngsøandGitHub aa334ad3bc MCP: Basic setup for MCP in sourcecode (#21061)
Basic setup for MCP
2026-02-03 10:59:04 +01:00
dependabot[bot]andJacob Overgaard 21ece43091 Bump lodash
Bumps the npm_and_yarn group with 1 update in the /tests/Umbraco.Tests.AcceptanceTest directory: [lodash](https://github.com/lodash/lodash).


Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 09:26:03 +01:00
Andy ButlandandGitHub 9915fd3cc5 Media Types: Add public constant for Folder media type GUID (#21597)
* Add and use a constant for the folder media type GUID identifier.

* Use defined constant and avoid lookup for folder media type when searching for media items.
2026-02-03 09:16:11 +01:00
32e7b13944 Performance: Implement key-based caching for data type and template repositories (#21280)
* Add failing tests illustrating the lack of data type caching by key.

* Implement cache by key in data type repository.

* Apply same for template repository look-ups by key.

* Add tests verifying that content types are already cached by Id and key.

* Use correct default for creator Id in data type builder for tests.

* Use non-obsolete constructor in test.

* Ensured a deleted data type or template is cleared from the by key cache.

* Add IReadRepository implementations

* Utilize by-key repo access in service layers

* Fix test

* Safeguard against potential null reference exception

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-02-03 08:25:52 +01:00
50d089c2f9 Repositories: Quote table, column and alias names (closes #21451) (#21577)
* quote table, column and alias names with SqlSyntaxProvider methods in raw sql

* refactoring private methods into new file as internal methods,
refactor new extensions into another file

* refactor GetAlias method

* Double check the change

* improve code health

* change new static classes into public static partial class NPocoSqlExtensions

* resolve some Copilot review suggestions

* revert Copilot suggestion because it decreases code health

* revert test

* revert refactoring for CodeScene

* delete obsolete Test

* remove new methods and updates, which are not relevat for this PR

* prepare for additional states in the future

* don't mix string building methods

* fix SQL injection danger

* fix test for reverted methods

* another SqlSyntax issue

* Add additional unit and integration tests verifying the refactorings made in the PR.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-03 07:36:57 +01:00
Nhu DinhandGitHub df37ae9839 E2E: QA Bumped version of test helper to fix the failing tests (#21596) 2026-02-02 21:07:20 +07:00
769cd808f1 TipTap: Avoid empty target attribute on links (#21572)
* Remove the default empty target tag for links, so the target attribute is only output when it has a value.

* Tiptap Link extensions: defaults `target` value to `null`

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-02-02 12:42:02 +00:00
Jacob OvergaardandGitHub 6aeffe7a6e build(deps): bumps @umbraco-ui/uui to 1.17.0-rc.5 (#21569) 2026-02-02 12:40:30 +00:00
Andy ButlandandGitHub 574c9bed6c Content Types: Fix deletion of properties without containers (closes #21566) (#21585)
Fix issue with deleting properties that are not in containers.
2026-01-30 14:41:09 +01:00
Niels LyngsøandGitHub 8f0555182c Content Type Designer: transfer root properties when creating first tab. (#21582)
* transfer root properties when creating a new tab

* fix controller lifecycle
2026-01-30 13:28:57 +01:00
Niels Lyngsø 731b10b07a formatting 2026-01-30 11:13:10 +01:00
Niels Lyngsø f1d32e41e0 lint 2026-01-30 11:12:35 +01:00
dc218ac1bf Repositories: Use FirstOrDefault over ExecuteScalar for GUID and nullable types (closes #21448) (#21552)
* Squash merged  "v173/20453-21446-21448-FirstOrDefault-vs-ExecuteScalar" into "v173/21448-FirstOrDefault-vs-ExecuteScalar"

* resolce Copilot code review comments

* revert to ExecuteScalar<string>

* revert to Database.ExecuteScalar<string>

* revert .FirstOrDefault<long>(query) and its async variant to .ExecuteScalar<long>(query). It is fine for PostgreSql too.

* Remove the test added for verifying NPoco behaviour (it's not needed in the code base moving forward)

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-29 15:38:48 +00:00
Jacob Overgaard 79cce7b574 bumps lockfile 2026-01-29 12:39:59 +01:00
Jacob Overgaard feb2827d16 Merge branch 'release/17.2' 2026-01-29 12:39:27 +01:00
Jacob Overgaard ae10b6685a generates api types 2026-01-29 12:12:31 +01:00
Andy Butland 6122cfc201 Bump version to 17.3.0-rc. 2026-01-28 14:22:09 +01:00
Niels LyngsøandGitHub c1b0672ce0 Content Value Transformation: Clean out values when property-type variation transforms (#21557)
* cleanup values of property when property type variation changes

* implement handling segments in transformation
2026-01-28 14:16:13 +01:00
8efdfcd341 Backoffice: Exclude invariant options for culture-variant properties in preset builder (#21555)
* Exclude invariant options for culture-variant properties in preset builder

* Add unit test verifying the fix.

* added a few more unit tests

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-01-28 11:30:54 +00:00
3c7a1ad2de Entity Signs: Refactor Entity Sign Bundle to use Popover API. (#21490)
* Refactor entity sign tooltip to use popover API.

* Refactor popover positioning and styling logic.

* Add preview icons to entity sign bundle.

* Improve entity sign preview rendering and popover state.

* Refactor entity sign popover and sign container styles.

* Remove unused index parameter.

* Update menu item background color styles.

* Revert commented lines.

* Refactor entity sign popover rendering logic.

* Refactor popover sign creation into separate method.

* keep previews on hover

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-01-28 10:37:20 +00:00
5485a31f75 Tiptap RTE: Resolves inline blocks being set as dirty (closes #17749) (#21546)
* Resolves RTE inline blocks being flagged as dirty

Fixes #17749

* Deprecated `displayInline` field

* Update src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/block/block.tiptap-api.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 09:52:45 +00:00
Jacob OvergaardandGitHub 345a075706 Dependencies: Bumps @umbraco-ui/uui to 1.17.0-rc.4 (#21538)
* build(deps): bumps @umbraco-ui/uui to 1.17.0-rc.2

* build(deps-dev): bumps @umbraco-ui/uui to rc.3 to fix deps mess

* build(deps): bumps @umbraco-ui/uui to 1.17.0-rc.4
2026-01-28 09:52:08 +00:00
Niels LyngsøandGitHub 046c7d207d Content Type Designer: Property Layout updates (#21544)
* remove alias id

* adjust spacing and sizing for improved space in the layout
2026-01-28 09:26:37 +00:00
a5a67a4381 Translations: Missing translations in user permission (#21541)
* Fix issue localization of user permission

* add localize to create button

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2026-01-28 10:13:39 +01:00
Andy ButlandandGitHub e259cd0fd3 External Logins: Handle duplicate key race condition intermittently triggered in ExternalLoginRepository (#21551)
Handle duplicate key race condition in ExternalLoginRepository.
2026-01-28 06:27:27 +01:00
bohdansolovieandGitHub b5559eb9e8 View Engines: Make ProfilingViewEngine._inner private and modernize string formatting (#21550)
improvement(web): make ProfilingViewEngine._inner private and modernize string formatting

- Changed internal readonly Inner field to private readonly _inner field
- Replaced string.Format calls with string interpolation
- Removed TODO comment
2026-01-27 17:51:24 +01:00
f2c351df64 Skip leading whitespace in ufm parser (#21509)
* Skip leading whitespace in ufm parser

* UFM: Update start function to also skip leading whitespace

The tokenizer was updated to allow whitespace after opening braces,
but the start function still used a string pattern without whitespace
tolerance. This updates start to use a pre-compiled regex that matches
the tokenizer behavior, and adds an additional test case for the
documentation example format.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-01-27 16:02:27 +00:00
Andy Butland 4e6481abd9 Bumped test dependencies to latest minor or patch. 2026-01-27 15:30:43 +01:00
Andy Butland b40c17582a Bump dependency on SixLabors.ImageSharp for ImageSharp2. 2026-01-27 15:30:13 +01:00
Lee KelleherandGitHub af7a21723d Tiptap RTE: Upgraded to latest v3.x (#21493)
* Upgraded Tiptap to v3.13.0

* Remove eslint disable comments

* Update notes in externals

* `TextDirection` is now part of Tiptap core

* Upgraded Tiptap to v3.16.0

* The `addOptions()` typing error still persists in v3.16.0

* Resolved the export issue

* Removed unrequired `@ts-expect-error`

This came from an upstream merge.
2026-01-27 12:04:48 +00:00
Jacob OvergaardandGitHub fc5a8a0536 build(deps-dev): bumps login dependencies to latest (#21539) 2026-01-27 11:39:33 +01:00
7d813667c3 Content/Media: Fix deadlock when performing certain operations in parallel (closes #21125) (#21526)
* Move MediaTree write lock before MediaSavingNotification to prevent deadlock

Fixes a deadlock that could occur when saving multiple media items in parallel
when a MediaSavingNotification handler acquires a MediaTree read lock. The
previous ordering allowed two threads to each acquire read locks in their
notification handlers, then both attempt to upgrade to write locks, causing
a classic lock upgrade deadlock in SQL Server.

By acquiring the write lock before publishing the notification, the deadlock
scenario is avoided. Since the write lock is lazy, it only materializes at the
database level when actual queries are made, so notification handlers doing
in-memory work won't hold the lock.

* Apply same fix to MediaService.Delete method

* Apply same fix to DeleteVersions, DeleteVersion, and Sort methods

* Apply same fix to ContentService methods

Move WriteLock before notifications in:
- Save (single and batch)
- Delete
- DeleteVersions
- DeleteVersion
- Copy

* Apply the same pattern to MemberService.

* Add integration tests to verify the fix.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-27 11:08:35 +01:00
Andy ButlandandGitHub 138818acde Members: Fix misleading error message on change password with incorrect current password (#21504)
* Provide correct validation error message on member change password with incorrect current password.

* Rework to use MembersErrorDescriber.
2026-01-27 10:37:28 +01:00
Nhu DinhandGitHub 79753eaf82 E2E: QA Updated acceptance tests for collection view search and document type group (#21522) 2026-01-27 16:00:12 +07:00
Andy ButlandandGitHub 647aa08586 Dependencies: Bump to latest minor/patch versions (#21540)
Bump dependencies to the latest minor or patch.
2026-01-27 08:34:18 +00:00
Nhu DinhandGitHub 9deffead21 E2E: QA Added acceptance tests for multi url picker validation message (#21226)
* Added tests for multi url picker validation message

* Added more tests - not done

* Updated more tests for multi url picker validation message

* Removed unused file

* Bumped version

* Make tests run in the pipeline

* Reverted npm command
2026-01-27 08:31:41 +00:00
Andy ButlandandGitHub d5d93ff1e0 Templates: Allow underscore as first character in template alias (closes #21534) (#21536)
Use a custom regex for validating template aliases that allows underscores.
2026-01-27 08:32:37 +01:00
7ad3d2f68f Content picker: Fix bug where dynamic root children are not correctly available for selection (closes #21477 and #21537) (#21535)
Fix bug content picker dynamic root children not selected properly

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2026-01-27 08:27:52 +01:00
Andreas ZerbstandGitHub 2fb1221d68 Dotnet Template: Fix trailing comma in appsettings.json (#21529)
Fix trailing comma in global settings
2026-01-27 03:34:18 +00:00
efb8aaf87b Dark mode: Added color variable to code block in the system information dialog to make it readable (#21532)
* added color variable to code-block to make it readable in dark mode

* Update src/Umbraco.Web.UI.Client/src/packages/core/components/code-block/code-block.element.ts

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-26 18:07:17 +00:00
2e80b996cf Media: Prevent creation of media with GUID v7 keys when using incompatible path scheme (closes #21440) (#21457)
* Prevent creation of media items with GUID version 7 keys when a media scheme is registered that doesn't support this GUID version.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix log message formatting.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-01-26 16:29:37 +01:00
Andy ButlandandGitHub 883b6a4d5d Models Builder: Fix nested generic type handling in WriteClrType (#21429)
* Add support to models builder for nested generic types.

* Fixed existing warnings, added further tests, renamed tests for clarity.

* Add defensive validation for generic brackets passed to SplitGenericArguments.

* Fix failing unit tests.
2026-01-26 15:32:48 +01:00
6946783b74 Content types: Allow adding composition with clashing property alias when property is being removed (closes #21298) (#21527)
* Content types: Allow adding composition with clashing property alias when property is being removed

* Further assert on property coming from the composition.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-26 14:32:34 +00:00
ad759e9311 Block editors: Fix false pending changes indicator for invariant block editor with culture-variant blocks (closes #21223) (#21292)
* Block editors: Fix false pending changes indicator for invariant BlockList with culture-variant blocks (closes #21223)

When a document with a culture-variant content type has an invariant BlockList property containing culture-variant blocks, and you publish all languages for the first time, the content would incorrectly show as having unpublished changes.

The root cause was inconsistent JSON serialization order between EditedValue and PublishedValue. Two fixes were applied:

1. Sort block item values by culture before serialization in both `FromEditor` and `MergePartialPropertyValueForCulture` to ensure consistent ordering.

2. Add `[JsonIgnore]` to `BlockItemData.Udi` property since this computed property differs between save and publish paths.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Publishing.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed failing integration tests.

* Fix backwards compatibility for legacy UDI format in JSON deserializatio

* Tidy up, remove unused parameters.

* Fixed failing E2E test with copy blocks.

* Separate handling of udi and values in deserialization from current and legacy format, to correctly fix previously failing integeration and E2E tests.

* Fixed failing unit test.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-26 14:51:16 +01:00
00794f48c5 Tiptap RTE: Adds link (umbLink) support to styleMenu API (#21494)
* Adds link (`umbLink`) support to the Style Menu api

* Tiptap RTE: Fix toggleClassName to handle multi-class strings

The toggleClassName command now properly tokenizes the className parameter
to handle space-separated classes (e.g., "btn btn-primary"). Previously,
the entire string was treated as a single token, causing duplicates and
preventing class removal.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Tiptap RTE: Add ensureUmbLink command for idempotent link creation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 14:24:29 +01:00
Niels LyngsøandGitHub 2c5466755a Block List Editor: Describe that Single Block Mode is deprecated (#21512)
mark Single Block Mode as deprecated
2026-01-26 13:07:54 +00:00
DreamandGitHub 7438dd7c84 Backoffice: Redirect to list view after entity deletion (#21456)
Backoffice: Redirect to list view after entity deletion

When an entity is deleted from its detail workspace, the UI now redirects
to the parent list view and shows a success notification instead of staying
on the deleted entity's page showing a 404 error.

Changes:
- Dispatch UmbEntityDeletedEvent after successful deletion
- Show success notification toast on deletion
- Listen for delete event in workspace editor and navigate to backPath
2026-01-26 12:54:25 +00:00
Andy ButlandandGitHub 868ac50b79 Media: Only add deleted suffix to URLs for trashed media when recycle bin protection is enabled (#21412)
Fix protection for media URLs such that they only apply for trashed media.
2026-01-26 13:41:01 +01:00
Sven GeusensandGitHub 897b6ccac6 Load Balancing: Tracking difference CM and CD redirect and post-logout URIs in load-balanced environments (#21432)
* Integration tests for #21138

* Make OpenId redirect and postlogout uris support load balanced environments

* Applied review suggestions

* Fix unit test mocks
2026-01-26 12:08:42 +01:00
997df3d92b Performance: Optimize property retrieval and authorization checks in collection views (#21470)
* Introduce new method overloads and repository implentation, such that a collection view response only loads properties it needs.

* Use non-obsolete method overloads throughout.

* Add unit tests to verify property value retrieval.

* Don't load templates for collection view content retrieval.

* Optimize access checks by verifying the full collection rather than one at a time, and avoid the need to retrieve full content items.

* Added obsoletion messages and aligned behaviour of content and media permission service checks.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Return key in TreeEntityPath collection response, avoiding a later look-up of the key by Id.

* Resolve breaking changes to interfaces.

* Fix further breaking change.

* Additional assert for test verifying property loading for a non-existing property.

* Refactored repositories to avoid having method parameters related to templates on non-document and base content repositories.

* Remove check that verifies all provided keys are found when doing permission checks (although arguably correct, it's a behavioural change, and can also be argued it's corect as is).

* Introduce variable for permission set permissions.

* Provide functional default implementation on FilterAuthorizedAsync.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-26 11:53:53 +01:00
e4df87123c Testing: Ensure ordering for paged descendants tests (closes #21446) (#21447)
* Squash merge Squash merged v173/20453-fix-more-sql-syntax-issues int v173/20453-fix-more-sql-syntax-issues-squash (copy of main)

* fix 2 unit test

* Replace nameof(DTO.COLUMN_NAME) by constant, because it leads to casing issues for case sensitive databses

* fix Copilot review comments

* resolve review comments

* replace more hard coded strings

* fix test

* fix review comments

* fix database schema

* fix database schema

* fix database schema and ResultColumn reference names

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* add comment  from review

* fix two reference column names

* fix breaking change

* fix typo

* Remove unnecessary attributes

* mark 2 unsused DTO classes as obsolete

* reverted change of class UnionHelperDto adding [Column("...")] attributes again, because some integration tests for PostgreSQL provider fail without them. Again a case sensitivty issue.

* replace nameof reference names,
make all column name const consistent

* use NPoco dto instead of raw sql,
extend ISqlSyntaxProvider to handle some sql issues

* reduce complexity

* remove currently unsused extensions to ISqlSyntax

* add missing methods to ISqlSyntaxProvider and SqlSyntaxProviderBase

* add another missing methods to ISqlSyntaxProvider and SqlSyntaxProviderBase

* fix Copilot review comments and build errors

* update ISqlSyntaxProvider and SqlSyntaxProviderBase

* ensure GetPagedDescendants returns ordered by path entities as default

* resolve review comments

* fix review comments

* Update src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix Copilot comment

* fix wrong Copilot suggestion

* quote more column names

* resolve review

* Apply suggestions from code review

* synced interface and base class

* Revert "synced interface and base class". For an interface's default implementation, NotImplementedException makes more sense.

This reverts commit cf01cd01fc.

* Fixed remaining code warnings in DatabaseSchemaCreator.

* follow Cotpilot's review suggestion

* revert implementation and fix test

* use default

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-26 09:36:13 +00:00
c6370e39a7 Recycle Bin: Adds emptyRecycleBin collection action kind for Documents and Media (#21482)
* Adds reusable `emptyRecycleBin` `collectionAction` kind

* Adds `emptyRecycleBin` collection-action to documents

* Adds `emptyRecycleBin` collection-action to media

* Removes `api` export

since the condition is eagerly loaded.

* Fixes type annotations and JSDoc comments

- Uses correct generic type `UmbCollectionHasItemsConditionConfig` in `UmbCollectionHasItemsCondition`
- Corrects JSDoc `@augments` tag in `UmbEmptyRecycleBinCollectionAction`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fixed linting errors

* Refactors execute() to reduce cyclomatic complexity

Extracts tree refresh logic into private #reloadChildrenOfEntity() method.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/manifests.ts

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Removed code comment

as caused ambiguity.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-26 09:25:37 +00:00
08863332d2 Disabled the generation and upload off the docfx csharp api docs. (#21521)
* Disabled the generation and upload off the docfx csharp api docs.

* Add comment explaining why job is disabled

* Added comment on second job

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-26 09:07:00 +00:00
f483946c4d Code Quality: Added missing documentation to the Umbraco.Cms.Api.Common project (#21465)
* Added missing code documentation to the Umbraco.Cms.Api.Common project

* Remove duplicate XML summary for All constant

Removed duplicate XML summary documentation for the All constant.

* Removed inline comments no longer required now the information has been moved to XML header remarks

* Fix indentation on refactored path segment extraction in SubTypesSelector

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-26 07:28:56 +00:00
e7624364ed Database Providers: Add support for providers offering sequence and null casting support (closes #21418) (#21419)
* Squash merge Squash merged v173/20453-fix-more-sql-syntax-issues int v173/20453-fix-more-sql-syntax-issues-squash (copy of main)

* fix 2 unit test

* Replace nameof(DTO.COLUMN_NAME) by constant, because it leads to casing issues for case sensitive databses

* fix Copilot review comments

* resolve review comments

* replace more hard coded strings

* fix test

* fix review comments

* fix database schema

* fix database schema

* fix database schema and ResultColumn reference names

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* add comment  from review

* fix two reference column names

* fix breaking change

* fix typo

* Remove unnecessary attributes

* mark 2 unsused DTO classes as obsolete

* reverted change of class UnionHelperDto adding [Column("...")] attributes again, because some integration tests for PostgreSQL provider fail without them. Again a case sensitivty issue.

* replace nameof reference names,
make all column name const consistent

* use NPoco dto instead of raw sql,
extend ISqlSyntaxProvider to handle some sql issues

* reduce complexity

* remove currently unsused extensions to ISqlSyntax

* add missing methods to ISqlSyntaxProvider and SqlSyntaxProviderBase

* add another missing methods to ISqlSyntaxProvider and SqlSyntaxProviderBase

* fix Copilot review comments and build errors

* update ISqlSyntaxProvider and SqlSyntaxProviderBase

* resolve review comments

* fix review comments

* Update src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix Copilot comment

* fix wrong Copilot suggestion

* quote more column names

* resolve review

* Apply suggestions from code review

* synced interface and base class

* Revert "synced interface and base class". For an interface's default implementation, NotImplementedException makes more sense.

This reverts commit cf01cd01fc.

* Fixed remaining code warnings in DatabaseSchemaCreator.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-24 09:16:15 +00:00
83300221fe Log Viewer: Fix polling interval reset when changing intervals (closes #21507) (#21508)
* fix(log-viewer): prevent polling toggle reset when changing interval

Fixes issue where changing polling interval would reset the button to 'Polling' state instead of applying the new interval immediately.

- Remove togglePolling() call from closePoolingPopover() method
- Update setPollingInterval() to restart polling with new interval if already enabled

Fixes #21507

* refactor(log-viewer): extract polling start logic and fix regression

- Extract polling start logic into #startPolling() helper method
- Fix regression: enable and start polling when interval is selected while polling is off
- Update togglePolling() to use the helper method for consistency

Addresses feedback on PR #21508

---------

Co-authored-by: Gittensor Miner <miner@gittensor.io>
2026-01-23 17:17:02 +01:00
af81e258b4 Fix for the client side circular dependency. (#21464)
* Fix for the client side circular dependency.

This should fix the circular dependency without causing any breaking changes to the public APIs.

This issue is detailed here:
https://github.com/umbraco/Umbraco-CMS/issues/21463

* refactor UMB_MODAL_MANAGER_CONTEXT to avoid circular dependency

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2026-01-23 15:53:42 +00:00
aa4a33251a Content Types: Root properties (#21500)
implement root properties

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2026-01-23 12:47:17 +00:00
Mads RasmussenandGitHub e7c63b19b9 Modal: Add 'in modal' condition to modal package (#21503)
* Add 'is modal' condition to modal package

Introduces a new 'is modal' condition for extension manifests, allowing actions to be conditionally permitted based on modal context. Updates user collection action manifests to use this condition, preventing certain actions when inside a modal. Includes implementation, configuration, manifest registration, and tests for the new condition.

* rename from is modal to in modal
2026-01-23 12:27:36 +00:00
Niels LyngsøandGitHub 16f9bc7cd7 Block List & Block Single: Use property value in validation check (Fixes #21313) (#21491)
use this.value as source for the validation
2026-01-23 12:12:20 +00:00
Niels LyngsøandGitHub 3b5e938492 Property Value Preset Builder: accept variant options (#21382)
property value preset builder to use variant options
2026-01-23 10:04:37 +00:00
ea76efafc1 Dictionary: Add configurable value search functionality (#21200)
* added dicationary value search active only with config param set

* Removed code smell, by reducing nesting

* Renamed configuration value to EnableValueSearch.
Added integration tests to verify search results.

* update query to return correct values for each language in the overview

* Use OptionsMonitor and add additional assert to verify fix to indication of which languages have translations.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-23 09:47:39 +01:00
1a68d39272 Variants Sorting: Sort by language name (fix #21408) (#21435)
* Sort at last by language name

* ensure document language picker is sorted as variant selector

* Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/shared/document-variant-language-picker.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/utils.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor to avoid inline methods

* transform into a function

* revert config file commit

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-23 09:47:14 +01:00
Andy ButlandandGitHub fee3cc7352 Redirect Tracking: Handle empty string in redirect tracker when restoring from recycle bin (#21488)
Handle empty string in redirect tracker when restoring from recycle bin.
2026-01-23 14:25:51 +09:00
Nhu DinhandGitHub 3f3362e0f3 E2E: QA Updated tests for granular permission in content to match the changes (#21478) 2026-01-23 04:07:50 +00:00
Nicklas KramerandGitHub 8f55885829 Data Types: Allows transparency for the approved colors in the color picker. (#21495)
Fixing regex and adding test
2026-01-23 10:34:20 +09:00
8f6ebcdcf6 Picker: Support embedded Collections in the Collection Item Picker Modal (#21392)
* Add alias property to collection config interface

Introduced an 'alias' property to the UmbCollectionItemPickerModalCollectionConfig interface

* render collection element when modal is configured with an alias

* expose a picker modal route

* use collection in use picker

* adjust spacing

* add config option for selectOnly

* dynamic modal alias

* support selectable entity item ref

* wip entity data picker collection + ref and card views

* Add entity collection item card extension type + default elements

* implement user collection item card

* fix selection events

* map to prop

* add prop/attr for href

* add support for which detail properties to show

* update type import

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/item/entity-collection-item-card/entity-collection-item-card.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* import card in correct file

* Fix event listener binding for selection events

* implement disabled property for collection item cards

* init commit of collection item ref extension

* fix imports

* add element interface

* Implement UmbEntityCollectionItemElement interface in item cards

Added the UmbEntityCollectionItemElement interface to document and user collection item card elements for improved type safety and consistency. Updated type exports to include the new interface.

* Update collection item ref to use uui-ref-node

Replaces the placeholder div with a uui-ref-node component, passing relevant item properties and event handlers. Adds dynamic icon rendering using umb-icon.

* Refactor entity collection item elements to use shared base

Introduces a new abstract base class for entity collection item elements, consolidating shared logic for card and ref variants. Updates card and ref element implementations to extend the new base, and refactors extension manifest interfaces for consistency. This improves maintainability and reduces code duplication.

* use class instead of magic string

* Use entity collection item card in picker view

Replaces the placeholder card markup with the <umb-entity-collection-item-card> component, enabling selection and deselection functionality for items in the entity data picker card collection view.

* Update entity item ref to collection item ref

Replaces <umb-entity-item-ref> with <umb-entity-collection-item-ref> in the picker collection view. Adjusts event handlers and select-only logic to improve selection behavior and component consistency.

* utilise ref and card kind for picker views

* introduce ref and card collection view kinds

* Utilise card kind for user collection view

* Add item-specific href support to collection views

Introduces a requestItemHref method to collection contexts for retrieving item-specific hrefs. Updates card, ref, and user table collection views to use these hrefs, enabling dynamic linking for collection items. Refactors user table name column layout to accept href via value prop instead of constructing it internally.

* Update ManifestCollectionView import path

Changed the import of ManifestCollectionView from '../extensions/types.js' to '../view/types.js' to reflect its new location.

* remove unused

* use size medium for entity collection item picker

* use box

* render entity actions

* use edit path builder for user links

* rename method

* Revert "rename method"

This reverts commit 4df577688e.

* Update collection-default.context.ts

* make type lint ignore unused args with an underscore

* temp remove unused

* only make collection vie selectable if there are any registered bulk actions

* don't render name link if there is no href

* fix imports

* Render selection actions only if bulk actions exist

* use selectable state

* Update language-table-collection-view.element.ts

* Update language-table-collection-view.element.ts

* Update card-collection-view.element.ts

* clean up

* Refactor collection views to use shared base class

* refactor(collection): parallelize href fetching and make method private

* docs(examples): update collection example to use card and ref kinds

* docs(examples): add icon property to collection example data model

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/view/types.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update collection-bulk-action.manager.test.ts

* Removed duplicate and redundant '@typescript-eslint/no-unused-vars' rule definitions, consolidating the configuration to use only 'argsIgnorePattern'.

* Handle missing user href in name column layout

Replaces the user name link with a span when the href property is not provided, preventing broken links in the user table name column layout.

* Update user-table-name-column-layout.element.ts

* pass modal data and value to routable modal

* Update picker-input.context.ts

* support selectableFilter

* scaffolding of a collection text filter extension

* Refactor collection text filter to use API interface

* Fix incorrect tag

* Update types.ts

* Update collection-text-filter.extension.ts

* Add cancelation to debounced search on destroy

* clean up

* add js docs

* two way binding of filter value

* clean up

* Add collection text filter manifest example

Introduced a new filter manifest for the example collection and updated the main manifests file to include it. This enables a text filter extension for the example collection.

* Delete unused element and context

* Update src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.context-token.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update user-group-table-collection-view.element.ts

* support search for tree item and collection item pickers

* add spacing between collection ref items

* add margin between picker search result items

* remove spacing after last item

* remove padding in search results

* Update collection-item-picker-modal.element.ts

* move select only logic to collection selection manager

* add tests for collection selection manager

* change to filter label instead of search

* delete unused user grid collection view

* Select-only mode is now only disabled when all items are deselected, rather than on every deselection.

* prepare umb table for pickers

* utilize UmbCollectionViewElementBase in user table collection view

* remove console log

* handle select all and select item from same event

* bulk actions workaround

* add bulk action in collections feature toggle

* remove unused method

* make fields optional to avoid a breaking change

* remove unused import

* fix typescript errors

* adjust search styling

* hide with css

* fix ts errors

* Add modal data support to picker input context

Introduces methods to set and get modal data in UmbPickerInputContext, allowing base configuration for picker modals. Updates modal data handling to merge stored modal data with provided data for both direct picker opening and modal route setup.

* Fix bulk action manager test initialization

Added calls to setConfig in tests to properly initialize the observer before subscribing to hasBulkActions. Simplified the test logic for checking emissions when actions are present.

* Update tree-picker-modal.element.ts

* Update picker-search-result.element.ts

* Update src/Umbraco.Web.UI.Client/src/packages/core/collection/view/umb-collection-view-element-base.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Use ifDefined for modal route in user input button

* Use ifDefined for href binding in entity data picker

* Fix collection alias binding in item picker modal

* wire up user table collection view with selectableFilter

* clean up controller aliases

* Update collection-item-picker-modal.element.ts

* Update collection-item-picker-modal.element.ts

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-22 15:24:09 +00:00
Andy ButlandandGitHub eca3e91af0 Management API: Fix document URLs returning all languages for invariant content (closes #21459) (#21473)
Fix display of all languages for URLs for invariant documents.
2026-01-22 11:41:01 +01:00
Niels LyngsøandGitHub 718e35f483 Media: Picker Modal types export (Fixes #21265) (#21329)
fix media modal exports
2026-01-22 10:03:18 +00:00
Niels LyngsøandGitHub ca8f6f59bd Entity Signs: Embed Api & Element for performance (#21480)
embed api & element
2026-01-22 09:18:31 +00:00
338f650274 Content-Type Designer: Transfer tab when moving property to inhertied tab (Fixes #20789) (#21234)
* enable async method

* ensure container is local to the owner content type

* no need to await anyhow

* handle moved groups

* Update src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-properties.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-22 07:09:45 +01:00
Lee KelleherandGitHub 995e3bde6f Task: De-duplicate TypeScript class names (#21474)
De-duplicate TypeScript class names
2026-01-21 20:57:16 +01:00
7f162201e6 Fixes #20665 - Password change error msg (#21257)
* Fixes #20665 - Password change error msg

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

---------

Co-authored-by: Emma L Garland <1649855+emmagarland@users.noreply.github.com>
2026-01-21 13:48:25 +00:00
d7dbe39dd3 Routing: Add DocumentUrlAliasService for optimized URL alias lookups (closes #21383) (#21396)
* Implement document alias cache and service to optimize content finder by alias.

* Renamed to DocumentUrlAlias. Fixed issues on start-up.

* Remove tracking of root ancestor.

* Optimize cache key, tidy up tests, move domain matching to content finder.

* Handle language and document deletes.

* Align further with document URL service.

* Code tidy.

* Fixed comment.

* Refactor scope handling to avoid nested scopes

Extract CreateOrUpdateAliasesInternalAsync to process documents without
creating their own scope. Both CreateOrUpdateAliasesAsync and
CreateOrUpdateAliasesWithDescendantsAsync now create a single scope
and call the internal method, avoiding unnecessary nested scope creation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Extract CreateOrUpdateAliasesInternalAsync to process documents without
creating their own scope.

* Only return a document for a match under a domain if the document is found under the domain of the current request.

* Fix failing integration tests.

* Apply suggestions from code review.

* Ensured language to culture code map is updated when a language isn't found in the cached map.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 13:52:45 +01:00
b2801e6555 Backoffice: Fix event listener memory leaks in auth, dropzone, actions, and router (#21458)
fix(backoffice): resolve event listener memory leaks in auth, dropzone, actions, and router

Fixes memory leaks in 4 components where event listeners registered with .bind(this) could not be properly removed because each .bind() call creates a new function reference.

Changes:
- auth.context.ts: Convert #onStorageEvent to arrow function property
- dropzone-media.element.ts: Convert 4 drag handlers to arrow function properties
- entity-actions-dropdown.element.ts: Convert handler and add disconnectedCallback
- router-slot.element.ts: Convert handler and add proper cleanup in disconnectedCallback

Solution: Arrow function properties maintain consistent references while preserving 'this' context, enabling proper listener removal.

Testing:
- Added unit tests for auth.context.ts
- All builds pass
- Linter passes
- No breaking changes

Documentation:
- Added "Event Listener Cleanup Pattern" section to clean-code.md
- Added "Event Handler Guidelines" section to style-guide.md

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-21 11:12:09 +00:00
594b64e2a9 Tiptap RTE: Optimize umb-input-tiptap initialization and rendering (#21070)
* refactor(rte): Replace misleading Promise.all with sequential awaits

The inner awaits in Promise.all([await ..., await ...]) made the operations
sequential anyway. Since #loadEditor() depends on _extensions being populated,
sequential execution is correct - this change makes the intent clearer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* perf(rte): Cache toolbar and statusbar emptiness checks

Instead of calling .flat() on every render to check if toolbar/statusbar
have items, compute the boolean once when values are set in #loadEditor().
This avoids unnecessary array operations during render cycles.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* perf(rte): Pre-compute extension styles during initialization

Instead of calling unsafeCSS() on each style during every render cycle,
collect and process styles once in #loadEditor() and store the result
in _extensionStyles. This avoids repeated CSS processing during renders.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-21 10:19:21 +01:00
acd0aae48e DevOps: Adds check:duplicate-class-names devops script (#21460)
* Adds `check:duplicate-class-names` devops script

* DevOps: Improve `check:duplicate-class-names` script

- Fix example path in JSDoc comment
- Add support for `export default class` declarations
- Add `--ignore-stories` flag to exclude story files from detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Added try/catch on reading file contents

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-21 10:18:52 +01:00
Jacob OvergaardandGitHub d4c3813410 Dotnet Template: Removes unused setting SanitizeTinyMce (#21467)
chore: removes unused setting `SanitizeTinyMce`
2026-01-21 08:53:59 +00:00
Nikolaj GeisleandGitHub 5d664538f0 Examine: Check for registered populators before emptying indexes (#21455)
* Check for registered populators before emptying indexes

* Update can rebuild to also use HasRegisteredPopulator
2026-01-21 08:33:03 +01:00
Andy ButlandandGitHub c8f897879c Performance: Fix thread safety and optimize cache updates in PublishStatusService after content changes (#21415)
* Resolved potential thread safety issues with PublishStatusService.

* Only update published status in content cache refresher if within a publish or unpublish operation.
2026-01-21 06:41:55 +00:00
cee47613a6 Move media-type guid strings into constants partial (#21461)
* move media-type guid strings into constants partial

* missed one.

* Update src/Umbraco.Core/Constants-MediaTypes.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add member type GUID constants too.

* Removed member type incorrectly recorded as a built-in data type.

* Reuse constant in obsolete GUID constant.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-21 06:25:45 +00:00
ac1a0a46df Document URL Cache: Ensure URLs are rebuilt after upgrade and prevent duplicate initialization (closes #21337) (#21379)
* Remove rebuild of document URLs during migration, instead ensuring they will run after migration is complete and Umbraco is running.

* Avoid unnecessary second rebuild of document URL cache after startup with migration that has already triggered a rebuild.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-21 06:54:56 +01:00
8f571fe51b Rollback: Add toggle for diff display (closes #18518) (#21426)
* Add a toggle, defaulted to off, for display of diffs on the rollback view.

* Used only label for checkbox.

* Align formatting across translations for diffHelp key.

* Changed the checkbox to a toggle

UI semantics, checkboxes imply selection, whereas toggles imply activation.

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-01-20 15:44:04 +00:00
Niels LyngsøandGitHub a3b4e922d9 Performance: Use import maps to save requests (#21363)
Use import maps to save requests
2026-01-20 14:27:11 +00:00
fd7a2c6a5a Content picker: Prevent selection of document/member type containers when configuring allowed types (closes #21356) (#21357)
* Prevent selection of document and member type folders when selecting allowed types for the content picker.

* Added fix for Media Types

* Set `documentTypesOnly` on `umb-input-document-type`

so to disallow selecting element-types.

* Linting

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2026-01-20 12:50:37 +00:00
fd09a24559 Media: Unable to see the "Access denied" view when deep-linking to restricted media nodes (#21442)
* fix: aligns media workspace with document workspace to handle "variants" when calculating routes, which fixes an issue where the "Access denied" view would not be shown

* fix: clear root access flag when selecting specific start nodes

When selecting specific document or media start nodes for a user, the UI now automatically sets hasDocumentRootAccess/hasMediaRootAccess to false.

Previously, if a user group had "Has access to all items" enabled, selecting specific start nodes on the individual user wouldn't clear the root access flag. This caused the backend to add -1 (root access) to the start node list, overriding the specific node selections.

This ensures user-specific start node permissions properly override group-level root access settings.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add length check to prevent rendering router with empty routes array

The render method now checks both that _routes exists AND has length > 0 before rendering the router-slot. An empty array is truthy, so without the length check, the router-slot could be rendered with an empty routes array, causing runtime errors.

This aligns with the original render logic and prevents the TypeError when media tests run.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix E2E test URL construction for media workspace deep-linking

The test was constructing an invalid URL by appending the workspace path
directly to the current URL, which included '/collection'. This resulted in:
/umbraco/section/media/collection/workspace/media/edit/ (invalid)

Instead of the correct:
/umbraco/section/media/workspace/media/edit/

The fix removes '/collection' before appending the workspace path, ensuring
the test actually navigates to the workspace editor where the 'Access denied'
view is properly displayed.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Make all tests for media start node run in the pipeline - remember to revert before merging

* Revert npm command before merging

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Nhu Dinh <hnd@umbraco.dk>
2026-01-20 10:42:12 +00:00
2eb2e33f2f Document Tree: Filter tree items based on user browse permissions (closes #21141) (#21173)
* Document Tree: Filter tree items based on user browse permissions

- Add FilterTreeEntities virtual methods to EntityTreeControllerBase for filtering tree entities with total count adjustments
- Override FilterTreeEntities in DocumentTreeControllerBase to filter by ActionBrowse permission
- Extract filtering logic into IDocumentPermissionFilterService for testability
- Add unit tests for DocumentPermissionFilterService

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-20 10:57:13 +01:00
ddffd6ec1e HybridCache: Optimize content type change cache rebuild to resolve SQL timeouts (#21207)
* Complete the scope when no runnable job found. Without this I'm seeing timeouts and lock contention if a long-running document type save operation is running when the first distributed job is requested.

* Run serialization steps of rebuild of content cache in parallel for a small but not insignficant speed optimization.

* Add integration tests for database cache rebuild.

* Optimize rebuild of databaes and memory cache after content type update.

* Add debug log for running distributed job.

* Apply memory cache clear optimization to media.

* Optimize MediaCacheService.RebuildMemoryCacheByContentTypeAsync with lightweight query

Use GetMediaKeysByContentTypeKeys to fetch only media keys instead of loading full ContentCacheNode objects. This matches the same optimization applied to DocumentCacheService.

Also refactors Rebuild() to reuse RebuildMemoryCacheByContentTypeAsync for the memory cache clearing step.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Further updates from code review.

* Further tests for variant documents, composed documents and message pack serialization.

* Fixed failing integration tests.

* Clear the cacje level published content cache on content type change.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-20 09:15:40 +01:00
e206f26e5e Content Picker: Provide "content root" origin for dynamic root (closes #21134) (#21161)
* Provide "content root" origin for dynamic root.

* Update src/Umbraco.Core/DynamicRoot/Origin/ContentRootDynamicRootOriginFinder.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Code tidy.

* Add integration test

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sven Geusens <geusens@gmail.com>
Co-authored-by: Sven Geusens <sge@umbraco.dk>
2026-01-20 06:56:23 +01:00
Chris HoustonandGitHub cbaab6a6f3 Code Quality: Adding XML documentation to Umbraco.Cms.Persistence.EFCore.SQLServer & Umbraco.Cms.Persistence.EFCore.SQLite (#21439)
Adding XML documentation to these two projects.

- Umbraco.Cms.Persistence.EFCore.SQLServer
- Umbraco.Cms.Persistence.EFCore.SQLite
2026-01-20 06:50:24 +01:00
Chris HoustonandGitHub 4f62771135 Code Quality: Resolve 128 SA1600 documentation warnings in Umbraco.Cms.Persistence.Sqlite (#21438)
* fix: Resolve 128 SA1600 documentation warnings in Umbraco.Cms.Persistence.Sqlite

- Added XML documentation comments to interceptors, mappers, and services
- Added TODO (V18) comments to SqliteSyntaxProvider.Format methods (CS0114)
- Updated .csproj TODO comment to follow V18 convention
- CS0114 warnings remain suppressed as fix would be binary breaking

* Fixed the issues Copilot complained about with the documentation and..

Fixed two IDE0270 warnings (null check simplification).
2026-01-20 06:48:32 +01:00
Chris HoustonandGitHub a4e72cbdb1 Code Quality: Adding all missing XML documentation for the Umbraco.Cms.Persistence.EFCore project (#21437)
Adding all missing XML documentation for the Persistence.EFCore project
2026-01-20 06:42:00 +01:00
Chris HoustonandGitHub 8d75277297 Code Quality: Fix CS0659 and CS0661 build warnings in Item test class by removing legacy test and setup of little value (#21399)
* Code Quality: Fix CS0659 and CS0661 build warnings in Item test class

The Item class in test project defined Equals override and equality operators without implementing GetHashCode, causing CS0659 and CS0661 compiler warnings.

Added GetHashCode implementation using RuntimeHelpers.GetHashCode(this) for consistent reference-based equality matching the existing operators behavior.

Removed CS0659/CS0661 from WarningsNotAsErrors in test project as they are no longer needed.

* Code Quality: Remove unused test infrastructure classes

Remove Item, OrderItem, and SimpleOrder classes along with the SimpleOrder_Returns_Null_On_FirstOrDefault_When_Empty test.

These ~370 lines of test infrastructure existed only for a single trivial test that verified FirstOrDefault() returns null on an empty collection - behavior already tested on actual Umbraco collections in the same file.
2026-01-20 06:32:51 +01:00
Andy ButlandandGitHub d4fe1b3783 StringExtensions: Refactor into partial classes and optimize methods (#21370)
* Refactor StringExtensions into multiple files using partial classes.

* Tidy/complete XML header comments.

* Fixed warnings in string extension methods.

* Add unit tests for IsLowerCase and IsUpperCase and optimize the methods.

* Add unit tests for ReplaceNonAlphanumericChars and optimize the method.

* Add unit tests for StringWhitespace and optimize the method.

* Add unit tests for StripHtml and DecodeFromHex and optimize the methods.
Fix too aggressive regex for StripHTML to ensure works only on HTML tags.

* Add unit tests for EnsureStartsWith and EnsureENdsWith and optimize the methods.

* Add unit tests for ToSingleLine and StripNewLines and optimize the methods.

* Fix issues raised in code review.
2026-01-19 19:18:39 +01:00
Andreas ZerbstandGitHub e767914bd0 E2E: QA Added separate emails for the login tests (#21443) 2026-01-19 14:33:56 +00:00
7acdc6ec0b Router: Destroy route component when disconnected (Fixes #21272) (#21318)
destroy route component when disconnected

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-01-19 11:11:26 +01:00
Nhu DinhandGitHub 00971bb55e E2E: QA Fixed failing tests for setting up content notifications (#21441) 2026-01-19 08:39:27 +00:00
Chris HoustonandGitHub 9832603525 Code Quality: Resolve SA1649 warnings (#21401)
* Added the SA1649 to the "No Warnings" section.

Stylecop is trying to enforce filenames that are like:

CancellableObjectEventArgs{TEventObject}.cs

However Umbraco uses CancellableObjectEventArgs.cs

Unless a policy decision is make to follow this stylecop rule, I think it is better to add this rule to the " NoWarn " section, so we don't see it appear at all.

* Revert accidental package-lock.json change

* Renaming files to match the StyleCop patterns.

Except two which would end up having the same names as other file, so these have been renamed as LegacyIScope & LegacyIScopeProvider, with local Pragma warnings disabled for this Style Cop rule.

* Removing the SA1649 from Warnings NOT as Errors.

In other words, if you turn on show warnings as errors, these will show as errors, rather than being suppressed.
2026-01-17 14:35:01 +01:00
Jacob OvergaardandGitHub e485056b8d Bulk Publish: Filter variant options to applicable cultures only (closes #19147) (#21163) 2026-01-16 19:25:22 +01:00
Andy ButlandandGitHub 28a3884adb Revert binary breaking changes from PR #21236 (#21434)
* Revert binary breaking changes from #21236 and comment with a TODO for the next major.

* Further TODO.
2026-01-16 17:46:59 +01:00
Mads RasmussenandGitHub 0eaca92615 Backoffice Performance: Inline entry point modules to reduce JS chunk count (#21380)
* change to static import

* add support for passing modules to manifest js property

* Replaces dynamic imports of entry-point.js with static imports across all manifests

* Support statically imported modules in loader functions

Extended loadManifestApi and loadManifestElement to handle already resolved module objects (statically imported modules) in addition to dynamic imports. Updated type definitions in utils.ts to include module export types for loader properties.

* Add tests for loadManifest* functions in extension-api

Introduces unit tests for loadManifestApi, loadManifestElement, and loadManifestPlainJs functions. These tests cover various scenarios including direct class constructors, dynamic and static imports, export prioritization, and edge cases for null and undefined inputs.
2026-01-16 15:18:59 +00:00
Engiber LozadaandGitHub 9ed3186cc2 Content Workspace: Add condition to detect when a content workspace has finished loading. (#21290)
* Added folder and files for the new condition.

* Registered the condition.

* Added an example to test the condition.

* Added the condition in one of examples.

* Renamed condition.

* Fixed linting error.
2026-01-16 15:59:01 +01:00
10c5df892f Notification Container: Make toast notifications announced by screen readers in Chrome. (#21028)
* fix(a11y): Toast notifications not announced by screen readers in Chrome

- Move screen reader live region from Shadow DOM to Light DOM (document.body)
  Chrome doesn't reliably detect ARIA live regions inside Shadow DOM
- Use role="alert" with fresh elements for each announcement instead of
  updating text content of an existing live region
- Fix invalid aria-role="true" attribute (was invalid HTML)
- Fix missing backslash in unicode escape '\u00A0'

The previous implementation had the live region nested 3 levels deep in
Shadow DOM, which Safari handled but Chrome ignored. Creating a new
alert element in Light DOM for each announcement is the most reliable
method across browsers.

Closes #14521

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Removed comment.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-16 15:57:46 +01:00
Niels Lyngsø 90be5d9958 lint fixes 2026-01-16 14:51:20 +01:00
Bjarne FyrstenborgandGitHub 4d5f9ec7ce Focal point: Utility functions (#21264)
Focal point utils
2026-01-16 13:20:19 +00:00
Andy ButlandandGitHub 07e25d681b Media Picker: Respect start node when drag+dropping files directly onto picker (closes #21422) (#21423)
Pass parent unique to the media to the media picker dropzone.
2026-01-16 11:51:45 +01:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>iOvergaardEngiber Lozada
ba7f84c96f Thumbnails: Fix image thumbnails cropping to allow the entire image to be shown as a thumbnail (closes #20347) (#21288)
* Initial plan

* Change object-fit from cover to contain for image thumbnails

Co-authored-by: iOvergaard <752371+iOvergaard@users.noreply.github.com>

* Add visual demonstration of the fix

Co-authored-by: iOvergaard <752371+iOvergaard@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: iOvergaard <752371+iOvergaard@users.noreply.github.com>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2026-01-16 11:50:26 +01:00
Dirk SeefeldandGitHub f36947a947 Replace nameof() by constants of DTO (closes #21303) (#21344) 2026-01-16 10:59:03 +01:00
JeavonandGitHub f6230cd122 Update Umbraco and Starter Kit versions in templates (#21395)
* Update Umbraco version in starterkits template

LTS and Latest should both install 17.0.0, at the moment latest uses Umbraco v17.1.0 but a starter kit version 17.0.0-rc1 which is not a good combo

* Update LTS in template to 17.1.0
2026-01-16 10:04:48 +01:00
a2c743ca43 Persistence Model: Replace some hard coded strings in DTOs (#21327)
* Squash merge Squash merged v173/20453-fix-more-sql-syntax-issues int v173/20453-fix-more-sql-syntax-issues-squash (copy of main)

* fix 2 unit test

* fix Copilot review comments

* resolve review comments

* replace more hard coded strings

* fix test

* fix review comments

* fix database schema

* fix database schema and ResultColumn reference names

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* add comment  from review

* fix two reference column names

* fix breaking change

* fix typo

* Remove unnecessary attributes

* mark 2 unsused DTO classes as obsolete

* reverted change of class UnionHelperDto adding [Column("...")] attributes again, because some integration tests for PostgreSQL provider fail without them. Again a case sensitivty issue.

* replace nameof reference names,
make all column name const consistent

* Update obsoletion messages

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-16 06:18:53 +00:00
Niels Lyngsø d72d66edfd no delay on forbidden-text animation 2026-01-15 16:35:00 +01:00
9c50476b17 Tree Navigation: Add visual indicators for items with restricted access (#21365)
* Tree pickers: Implement noAccess property UI handling for user start nodes

- Add noAccess observable to document and media tree item contexts
- Add visual styling (grayed out, italic) for noAccess items in tree views
- Update document and media picker input contexts to prevent selection of noAccess items
- Items with noAccess are shown for navigation but cannot be selected in pickers

This implements the UI handling for Feature 63060 "Handle Start Nodes"

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix noAccess implementation and add E2E tests

This commit combines all improvements made to the noAccess property feature:

1. Refactored to use Lit lifecycle methods (updated()) instead of property watchers
2. Added click and keyboard event handlers to prevent navigation
3. Removed disabled attribute that was blocking tree expansion
4. Added comprehensive E2E tests for document and media trees

Critical bug fix: Removed disabled attribute that prevented expansion
- The disabled attribute was blocking ALL interactions including expanding
  tree items to show accessible children underneath noAccess ancestors
- Now only sets aria-disabled="true" for screen readers and removes href
- Click and keyboard event handlers still prevent navigation as intended
- Users can now properly navigate through noAccess ancestors to reach
  their accessible child nodes

E2E test coverage:
- Display noAccess styling (opacity, italic)
- Prevent navigation when clicking noAccess nodes
- Allow expansion of noAccess nodes to show children
- Picker tests skipped pending infrastructure improvements

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Remove aria-disabled manipulation that interferes with tree expansion

The previous implementation set aria-disabled="true" and removed href
from the menu-item in #updateMenuItemAccessibility(). This approach
caused issues with tree expansion functionality.

Removed:
- #updateMenuItemAccessibility() method
- updated() lifecycle hook that called it
- UUIMenuItemElement import (no longer needed)

The click and keyboard event handlers already prevent navigation to
noAccess nodes, so additional DOM manipulation is not necessary.

Test results:
 4 passing: Display styling and prevent navigation work correctly
 2 failing: These appear to be backend issues:
   1. Document expansion: Caret button disabled (backend marking noAccess
      items as not selectable, which disables entire menu-item)
   2. Media expansion: Child media folder incorrectly has noAccess attribute
      (backend data issue - child should be accessible as it's the start node)

The UI implementation is sound. The remaining test failures indicate
backend API issues that need investigation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix path comparison bug in UserStartNodeEntitiesService (similar to #21162)

This fixes the same path comparison bug we fixed in PR #21162 but in C# string
comparisons instead of SQL queries.

## Root Cause
Path comparisons without trailing commas caused false matches:
- Path "-1,1001" incorrectly matched prefix "-1,100"
- This marked nodes as ancestors/descendants when they weren't related

## Examples of False Matches
- child.Path = "-1,1001", startNodePath = "-1,100"
  - OLD: "-1,1001".StartsWith("-1,100") = TRUE (bug!)
  - NEW: "-1,1001,".StartsWith("-1,100,") = FALSE (correct!)

- child.Path = "-1,100", startNodePath = "-1,1001"
  - OLD: "-1,1001".StartsWith("-1,100") = TRUE (bug!)
  - NEW: "-1,1001,".StartsWith("-1,100,") = FALSE (correct!)

## Fix Applied (Two Locations)
1. Line 146 (ancestor check): Added comma suffix to child.Path
2. Line 226 (IsDescendantOrSelf): Added comma suffix to both paths

This matches the pattern already used correctly in lines 92 and 191 of the
same file, and mirrors the SQL fix from PR #21162.

## Test Impact
This should fix the failing E2E test where child media folders were incorrectly
marked as noAccess when they were actually the user's start node.

Related: #21162

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix remaining merge conflict markers in media-tree-item.element.ts

* Remove E2E agent markdown file (moved to personal space)

* test: adds mock data for noAccess

* feat: moves noAccess subscriber to base class

* test: adds mock data for media

* feat: moves no-access styling to the base class

* fix: media tree items should inherit styling from the base class

* feat: observes noAccess from children and reports back to the base class

* test: spec file should use undefined instead of null

* docs: add comprehensive comments explaining noAccess opt-in pattern

- Document why noAccess is not in base interface (breaking change)
- Explain opt-in pattern with code examples
- Add JSDoc comments to property, event handlers, and CSS
- Reference accessibility considerations (keyboard users)
- Link child class implementations to base class documentation

* test: adds timeout for URL to settle

* fix: allow clicks on accessible children of noAccess tree items

When a tree item has noAccess, child tree items are rendered in its slot.
Previously, the parent's click handler blocked ALL clicks due to event bubbling,
preventing users from navigating to accessible descendants.

Now checks if click originated from a child tree item element using closest().
If it's a child, allow the click. Only block clicks on the noAccess item itself.

Applied to both mouse clicks and keyboard navigation (Enter/Space).

This enables users to navigate through noAccess ancestors to reach their
accessible start nodes (e.g., Root[noAccess] → Child[noAccess] → Grandchild[accessible]).

Fixes tests:
- should allow expansion of noAccess ancestor node to show children (documents)
- should allow expansion of noAccess ancestor media node to show children (media)

* compare with the closest element to see if we are clicking on the element that is blocked or a sub-element that is not

* fix: adds forbidden route in case of no variants

* test: corrects label locator

* test: adds test to check if you can click or deeplink to restricted media

* test: removes .only

* test: removes duplicated tests

* test: adds test for document no-access

* test: add unit tests for user start node path comparison logic

Adds comprehensive unit tests documenting the path comparison fix that prevents
false matches when node IDs are numeric prefixes of other IDs (e.g., 100 vs 1001).

The fix uses trailing commas on both paths to ensure accurate comparison:
- Without fix: "-1,100".StartsWith("-1,10") = true  (incorrect)
- With fix: "-1,100,".StartsWith("-1,10,") = false  (correct)

Tests cover:
- Numeric prefix edge cases (1 vs 10, 10 vs 100, 100 vs 1001)
- Self comparison (start node itself)
- Descendant relationships
- Deep path hierarchies
- Demonstrates the bug without the fix for documentation

19 test cases total, all passing.

* test: removes .only

* fix: do not overwrite forbidden route

* docs: fixes line number in comment

* test: fixes comment

* feat: uses isSelectableContext to disable and scrub 'href' from base element

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-15 16:29:21 +01:00
7dca122a80 NPM: Move Umbraco Package Schema and custom-elements to root level for IDE discoverability (closes #16667) (#17866)
* Adjust build scripts for custom elements and JSON schema generation to be placed at root level, add generation to build for npm and update .gitignore

* fix: updates umbraco package schema location

* git ignores

* fix: outputs the vscode custom elements file at root

* fix: adds generated files to output

---------

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-01-15 15:11:26 +00:00
e7de6a8afb Backoffice: Fix login logo popover to display Umbraco branding (closes #21078) (#21413)
* fix(backoffice): use hardcoded Umbraco logo in header popover

Fixes issue where the backoffice header logo popover incorrectly
displayed the LoginLogoImageAlternative setting instead of showing
the Umbraco branding.

Changes:
- Added hardcoded umbraco-logo.svg asset to client project
- Updated backoffice-header-logo component to reference static logo
- Wrapped logo in link to umbraco.com
- Removed dependency on BackOfficeLogo endpoint for popover

The small header logo button still uses <umb-app-logo> and remains
customizable via the BackOfficeLogo setting.

Closes #62866

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* chore: removes link to umbraco.com

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-15 15:27:12 +01:00
d6de4a611a Media Picker: Uploaded files should automatically be selected (closes #21115) (#21409)
* fix(media-picker): auto-select uploaded media items

When uploading media in the media picker modal, uploaded items are now
automatically selected. This works for both single and multiple selection
modes, and correctly handles paginated folders where uploaded items may
not be visible on the current page.

Closes #21115

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(media-picker): navigate to last page after upload

Uploaded media items get the highest SortOrder, placing them on the last
page. This change navigates to the last page after upload so users can
see their newly uploaded items, which are also auto-selected.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-15 13:06:00 +01:00
Nhu DinhandGitHub 65d7989c23 E2E: QA Add acceptance tests for user group description (#21404)
* Added tests to create a user group with description

* Clean up

* Moved tests for user group description to other class

* Bumped version

* Make tests run in the pipeline

* Reverted npm command
2026-01-15 10:54:37 +00:00
226162d8f2 Performance: Optimize refresh of hybrid cache for a document by retrieving draft and published in single query (#21407)
* Optimize retrieval of ContentCacheNode for draft and publish in when refreshing the hybrid cache.

* Fixed issue with XML header documentation tags.

* Use is null for consistency

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2026-01-15 10:52:21 +00:00
dependabot[bot]andJacob Overgaard b484403b1c Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /src/Umbraco.Web.UI.Client directory: [diff](https://github.com/kpdecker/jsdiff).
Bumps the npm_and_yarn group with 1 update in the /src/Umbraco.Web.UI.Client/src/packages/core directory: [diff](https://github.com/kpdecker/jsdiff).


Updates `diff` from 7.0.0 to 8.0.3
- [Changelog](https://github.com/kpdecker/jsdiff/blob/master/release-notes.md)
- [Commits](https://github.com/kpdecker/jsdiff/compare/7.0.0...v8.0.3)

Updates `diff` from 7.0.0 to 8.0.3
- [Changelog](https://github.com/kpdecker/jsdiff/blob/master/release-notes.md)
- [Commits](https://github.com/kpdecker/jsdiff/compare/7.0.0...v8.0.3)

---
updated-dependencies:
- dependency-name: diff
  dependency-version: 8.0.3
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: diff
  dependency-version: 8.0.3
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-15 11:22:54 +01:00
f000b00c65 Server Events: Add runtime state check and error handling to ServerEventRouter (#21406)
Add resilience to ServerEventRouter to prevent failures during unattended
install/upgrade when SignalR (especially Azure SignalR) is configured.

Changes:
- Skip server event routing when runtime level is not Run (Install/Upgrade)
- Add try-catch with warning logging for graceful degradation on SignalR failures
- Add backwards-compatible obsolete constructor using StaticServiceProvider pattern
- Add unit tests for runtime level checks

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 10:20:06 +00:00
Chris HoustonandGitHub 720774d219 Code Quality: Fix CS1574 and CS0419 XML documentation warnings (#21400)
Docs: Fix CS1574 and CS0419 XML documentation warnings

Fixed 17 build warnings related to XML documentation cref attributes:

CS1574 (cref attribute could not be resolved):
- IApiMediaQueryService: Changed see cref to paramref for path parameter
- Permission resources: Removed cross-assembly cref to handlers in Management API
- ContentService: Removed reference to non-existent SaveAndPublish method
- ModelsBuilderModeValidator: Fixed cref to Constants.ModelsBuilder.ModelsModes.Nothing
- ImageCropperPropertyValueEditor: Fixed cref to TemporaryFileUploadValueBase.Src
- NPocoSqlServerDatabaseExtensions: Removed cref to external NPoco method
- RegisteredReloadableLogger: Removed cref to non-existent RefreshingRazorViewEngine
- FriendlyPublishedContentExtensions: Removed cref to non-existent AncestorOrSelf methods
- ManagementApiControllerBase: Removed cref to inherited Forbid() method
- BackOfficeExternalLoginProviderErrorMiddleware: Fixed namespace in cref
- HasScheduleFlagProvider: Fixed typo HasScheduleSignProvider -> HasScheduleFlagProvider

CS0419 (ambiguous cref reference):
- ServiceCollectionExtensions: Specified exact overload ConfigureUmbracoDefaults(IHostBuilder)
- IUserStartNodeEntitiesService: Replaced ambiguous GetPagedChildren cref with plain text
2026-01-15 06:45:13 +01:00
Chris HoustonandGitHub e389e3c505 Removing a variable that is not being used - fixes warning CS0168 (#21398) 2026-01-15 06:30:52 +01:00
9bf54ca9cb UI: Refactor breadcrumb URLs to use Path Constants (#21179)
- Add UMB_WORKSPACE_EDIT_PATH_PATTERN and UMB_WORKSPACE_EDIT_VARIANT_PATH_PATTERN
  to core workspace paths for generic edit URL generation
- Fix UmbPathPattern to support multi-level chaining via toAbsolutePatternString()
- Refactor workspace-menu-breadcrumb to use new path patterns
- Refactor menu-variant-tree-structure-workspace-context-base to use new patterns
- Refactor tree-item-context-base to use UMB_WORKSPACE_EDIT_PATH_PATTERN
- Refactor user-grid-collection-view to use existing UMB_EDIT_USER_WORKSPACE_PATH_PATTERN
- Remove outdated TODO about encoding uniques (handled at data source)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
2026-01-14 11:08:49 +01:00
59675a083d Code Quality: Fix StyleCop warnings SA1116, SA1401, SA1649, SA1405, SA1121, SA1130, SA1306, SA1028, SA1400, SA1106 (#21377)
* fix(stylecop): resolve SA1106 - remove empty statement

* fix(stylecop): resolve SA1400 - add missing access modifiers

* fix(stylecop): resolve SA1028 - remove trailing whitespace

* fix(stylecop): resolve SA1306 - rename fields to lowercase

* fix(stylecop): resolve SA1130 - use lambda syntax

* fix(stylecop): resolve SA1121 - use built-in type aliases

* fix(stylecop): resolve SA1405 - add messages to Debug.Assert calls

* fix(stylecop): resolve SA1649 - rename files to match type names (partial)

* fix(stylecop): resolve SA1401 - convert fields to const/readonly (partial)

* fix(stylecop): resolve SA1116 - reformat multi-line parameters (partial)

* fix(stylecop): revert breaking changes, add V18 TODO comments

* fix: correct TODO comment for SA1306 - should rename to _completed

* Standardize API file names across modules - No code changes, file names.

- Extracts login model to a dedicated file and preserves binding behavior
- Renames multiple API files to align with updated conventions ( Just to match their names in the code, not changing the actual API names, i.e. no breaking changes )
- Updates DI extensions, mappings, and OpenAPI helpers to follow new naming
- Adjusts tests for consistent formatting and readability
- Preserves behavior; no logic changes, references kept intact

* fix(tests): refactor UserEmail to virtual property pattern

- Convert protected field _userEmail to virtual property UserEmail
- Remove dead code (_userEmail += "groupName" executed after request)
- Update derived test classes to use property instead of field
- Maintains original name to avoid breaking changes
- Follows best practice: virtual property allows derived class override

This was originally changed in my PR from UserEmail to _userEmail, so changing it back to ensure no breaking change, even though this is in a test class.

* Committing small fix to prevent a breaking change, adding commit for future removal.

* Renames helper class and removes BOM

Renames internal helper to follow naming conventions without the T prefix
Removes stray BOM from header to ensure clean compilation
No runtime behavior changes

* Split Physical FileSystem interface into it's own file.

* Split the IContentQueryService into it's own file

Also updated XML docs.

* Reverting the package-lock.json

* Updated the typo for Permision -> Permission

Updated the file name and class to: AddUserGroup2PermissionTable

This should be safe to do so as migrations are logged with their GUID's not the class names.

* Update src/Umbraco.Core/Scoping/CoreScope.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Reverting a binary change.

* Reverted rename of public migration class.

* Revert name in migration plan.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-14 09:17:47 +00:00
Sebastiaan JanssenandGitHub a4a6c37c8b Only run this scheduled job on the original repo, not on forks 2026-01-14 10:01:23 +01:00
Andreas ZerbstandGitHub 396a8edd91 E2E: QA updated test helpers to fix flaky acceptance tests (#21373)
* Bumped helpers

* Updated tests

* Bumped version

* Bumped version again to fix flaky renaming test
2026-01-14 08:58:05 +01:00
Andy ButlandandGitHub de87a71bc8 Migrations: Ensure description column is added before earlier User Group migration runs (#21378)
* Ensure the description field added in a later migration for user groups is available when the earlier migration on this table runs.

* Update implementation of fix to store and use the state of UserGroupDto at the time of migrations.
2026-01-14 07:59:39 +01:00
abe772b963 Collection: Introduce Collection Text Filter Extension (#21172)
* scaffolding of a collection text filter extension

* Refactor collection text filter to use API interface

* Fix incorrect tag

* Update types.ts

* Update collection-text-filter.extension.ts

* Add cancelation to debounced search on destroy

* clean up

* add js docs

* two way binding of filter value

* clean up

* Add collection text filter manifest example

Introduced a new filter manifest for the example collection and updated the main manifests file to include it. This enables a text filter extension for the example collection.

* Delete unused element and context

* Update src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.context-token.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update user-group-table-collection-view.element.ts

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-13 20:56:27 +01:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>nielslyngsoeNiels Lyngsø
3342d31270 Add loading indicator and error handling to Member Public Access Modal (#21087)
* Initial plan

* Add loading indicator and error handling to public access modal

Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>

* Fix test for public access modal element

Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-01-13 14:24:09 +00:00
Niels LyngsøandGitHub 7557f7bfa5 Content Type Designer: make inherited property appear more like the local, to take less focus (#21229)
make inherited property appear more like the local, otherwise it takes too much attention.
2026-01-13 13:40:54 +00:00
6271 changed files with 208064 additions and 28493 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
+5
View File
@@ -0,0 +1,5 @@
UMBRACO_CLIENT_ID=umbraco-back-office-mcp
UMBRACO_CLIENT_SECRET=1234567890
UMBRACO_BASE_URL=https://localhost:44339
NODE_TLS_REJECT_UNAUTHORIZED=0
UMBRACO_INCLUDE_TOOL_COLLECTIONS=data-type,document-type,document,media-type,media
+5
View File
@@ -55,3 +55,8 @@
*.sln text=auto eol=crlf merge=union
*.gitattributes text=auto
# Generated files - hidden by default in GitHub diffs
src/Umbraco.Web.UI.Client/src/packages/core/backend-api/** linguist-generated
src/Umbraco.Web.UI.Login/src/api/** linguist-generated
src/Umbraco.Cms.Api.Management/OpenApi.json linguist-generated
+1 -218
View File
@@ -1,218 +1 @@
# Umbraco CMS Development Guide
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
## Working Effectively
Bootstrap, build, and test the repository:
- Install .NET SDK (version specified in global.json):
- `curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version $(jq -r '.sdk.version' global.json)`
- `export PATH="/home/runner/.dotnet:$PATH"`
- Install Node.js (version specified in src/Umbraco.Web.UI.Client/.nvmrc):
- `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash`
- `export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`
- `nvm install $(cat src/Umbraco.Web.UI.Client/.nvmrc) && nvm use $(cat src/Umbraco.Web.UI.Client/.nvmrc)`
- Fix shallow clone issue (required for GitVersioning):
- `git fetch --unshallow`
- Restore packages:
- `dotnet restore` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
- Build the solution:
- `dotnet build` -- takes 4.5 minutes. NEVER CANCEL. Set timeout to 10+ minutes.
- Install and build frontend:
- `cd src/Umbraco.Web.UI.Client`
- `npm ci --no-fund --no-audit --prefer-offline` -- takes 11 seconds.
- `npm run build:for:cms` -- takes 1.25 minutes. NEVER CANCEL. Set timeout to 5+ minutes.
- Install and build Login
- `cd src/Umbraco.Web.UI.Login`
- `npm ci --no-fund --no-audit --prefer-offline`
- `npm run build`
- Run the application:
- `cd src/Umbraco.Web.UI`
- `dotnet run --no-build` -- Application runs on https://localhost:44339 and http://localhost:11000
Check out [BUILD.md](./BUILD.md) for more detailed instructions.
## Validation
- ALWAYS run through at least one complete end-to-end scenario after making changes.
- Build and unit tests must pass before committing changes.
- Frontend build produces output in src/Umbraco.Web.UI.Client/dist-cms/ which gets copied to src/Umbraco.Web.UI/wwwroot/umbraco/backoffice/
- Always run `dotnet build` and `npm run build:for:cms` before running the application to see your changes.
- For login-only changes, you can run `npm run build` from src/Umbraco.Web.UI.Login and then `dotnet run --no-build` from src/Umbraco.Web.UI.
- For frontend-only changes, you can run `npm run dev:server` from src/Umbraco.Web.UI.Client for hot reloading.
- Frontend changes should be linted using `npm run lint:fix` which uses Eslint.
## Testing
### Unit Tests (.NET)
- Location: tests/Umbraco.Tests.UnitTests/
- Run: `dotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj --configuration Release --verbosity minimal`
- Duration: ~1 minute with 3,343 tests
- NEVER CANCEL: Set timeout to 5+ minutes
### Integration Tests (.NET)
- Location: tests/Umbraco.Tests.Integration/
- Run: `dotnet test tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj --configuration Release --verbosity minimal`
- NEVER CANCEL: Set timeout to 10+ minutes
### Frontend Tests
- Location: src/Umbraco.Web.UI.Client/
- Run: `npm test` (requires `npx playwright install` first)
- Frontend tests use Web Test Runner with Playwright
### Acceptance Tests (E2E)
- Location: tests/Umbraco.Tests.AcceptanceTest/
- Requires running Umbraco application and configuration
- See tests/Umbraco.Tests.AcceptanceTest/README.md for detailed setup (requires `npx playwright install` first)
## Project Structure
The solution contains 30 C# projects organized as follows:
### Main Application Projects
- **Umbraco.Web.UI**: Main web application project (startup project)
- **Umbraco.Web.UI.Client**: TypeScript frontend (backoffice)
- **Umbraco.Web.UI.Login**: Separate login screen frontend
- **Umbraco.Core**: Core domain models and interfaces
- **Umbraco.Infrastructure**: Data access and infrastructure
- **Umbraco.Cms**: Main CMS package
### API Projects
- **Umbraco.Cms.Api.Management**: Management API
- **Umbraco.Cms.Api.Delivery**: Content Delivery API
- **Umbraco.Cms.Api.Common**: Shared API components
### Persistence Projects
- **Umbraco.Cms.Persistence.SqlServer**: SQL Server support
- **Umbraco.Cms.Persistence.Sqlite**: SQLite support
- **Umbraco.Cms.Persistence.EFCore**: Entity Framework Core abstractions
### Test Projects
- **Umbraco.Tests.UnitTests**: Unit tests
- **Umbraco.Tests.Integration**: Integration tests
- **Umbraco.Tests.AcceptanceTest**: End-to-end tests with Playwright
- **Umbraco.Tests.Common**: Shared test utilities
## Common Tasks
### Running Umbraco in Different Modes
**Production Mode (Standard Development)**
Use this for backend development, testing full builds, or when you don't need hot reloading:
1. Build frontend assets: `cd src/Umbraco.Web.UI.Client && npm run build:for:cms`
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
3. Access backoffice: `https://localhost:44339/umbraco`
4. Application uses compiled frontend from `wwwroot/umbraco/backoffice/`
**Vite Dev Server Mode (Frontend Development with Hot Reload)**
Use this for frontend-only development with hot module reloading:
1. Configure backend for frontend development - Add to `src/Umbraco.Web.UI/appsettings.json` under `Umbraco:CMS:Security`:
```json
"BackOfficeHost": "http://localhost:5173",
"AuthorizeCallbackPathName": "/oauth_complete",
"AuthorizeCallbackLogoutPathName": "/logout",
"AuthorizeCallbackErrorPathName": "/error",
"BackOfficeTokenCookie": {
"SameSite": "None"
}
```
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
3. Run frontend dev server: `cd src/Umbraco.Web.UI.Client && npm run dev:server`
4. Access backoffice: `http://localhost:5173/` (no `/umbraco` prefix)
5. Changes to TypeScript/Lit files hot reload automatically
**Important:** Remove the `BackOfficeHost` configuration before committing or switching back to production mode.
### Backend-Only Development
For backend-only changes, disable frontend builds:
- Comment out the target named "BuildStaticAssetsPreconditions" in src/Umbraco.Cms.StaticAssets.csproj:
```
<!--<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
[...]
</Target>-->
```
- Remember to uncomment before committing
### Building NuGet Packages
To build custom NuGet packages for testing:
```bash
dotnet pack -c Release -o Build.Out
dotnet nuget add source [Path to Build.Out folder] -n MyLocalFeed
```
### Regenerating Frontend API Types
When changing Management API:
```bash
cd src/Umbraco.Web.UI.Client
npm run generate:server-api-dev
```
Also update OpenApi.json from /umbraco/swagger/management/swagger.json
## Database Setup
Default configuration supports SQLite for development. For production-like testing:
- Use SQL Server/LocalDb for better performance
- Configure connection string in src/Umbraco.Web.UI/appsettings.json
## Clean Up / Reset
To reset development environment:
```bash
# Remove configuration and database
rm src/Umbraco.Web.UI/appsettings.json
rm -rf src/Umbraco.Web.UI/umbraco/Data
# Full clean (removes all untracked files)
git clean -xdf .
```
## Version Information
- Target Framework: .NET (version specified in global.json)
- Current Version: (specified in version.json)
- Node.js Requirement: (specified in src/Umbraco.Web.UI.Client/.nvmrc)
- npm Requirement: Latest compatible version
## Known Issues
- Build requires full git history (not shallow clone) due to GitVersioning
- Some NuGet package security warnings are expected (SixLabors.ImageSharp vulnerabilities)
- Frontend tests require Playwright browser installation: `npx playwright install`
- Older Node.js versions may show engine compatibility warnings (check .nvmrc for current requirement)
## Timing Expectations
**NEVER CANCEL** these operations - they are expected to take time:
| Operation | Expected Time | Timeout Setting |
| ----------------------- | ------------- | --------------- |
| `dotnet restore` | 50 seconds | 90+ seconds |
| `dotnet build` | 4.5 minutes | 10+ minutes |
| `npm ci` | 11 seconds | 30+ seconds |
| `npm run build:for:cms` | 1.25 minutes | 5+ minutes |
| `npm test` | 2 minutes | 5+ minutes |
| `npm run lint` | 1 minute | 5+ minutes |
| Unit tests | 1 minute | 5+ minutes |
| Integration tests | Variable | 10+ minutes |
Always wait for commands to complete rather than canceling and retrying.
The full development guide for this repository lives in [CLAUDE.md](../CLAUDE.md). Please read that file for complete instructions on architecture, build steps, testing, branching conventions, and coding patterns.
-4
View File
@@ -4,9 +4,7 @@ on:
push:
branches:
- main
- release/*
- v*/dev
- v*/main
paths:
- src/Umbraco.Web.UI.Client/package.json
- src/Umbraco.Web.UI.Client/package-lock.json
@@ -16,9 +14,7 @@ on:
types: [opened, synchronize, reopened, closed]
branches:
- main
- release/*
- v*/dev
- v*/main
workflow_dispatch:
jobs:
-2
View File
@@ -5,7 +5,6 @@ on:
branches:
- main
- v*/dev
- v*/main
paths:
- src/Umbraco.Web.UI.Client/package.json
- src/Umbraco.Web.UI.Client/package-lock.json
@@ -16,7 +15,6 @@ on:
branches:
- main
- v*/dev
- v*/main
workflow_dispatch:
env:
@@ -12,6 +12,7 @@ permissions:
jobs:
reconcile:
if: github.repository == 'umbraco/Umbraco-CMS'
runs-on: ubuntu-latest
steps:
- name: Reconcile release/* labels → discussions
+6
View File
@@ -51,6 +51,10 @@ tools/docfx/
/build/csharp-docs/api/
/build/csharp-docs/_site/
# Local config
.claude/settings.local.json
.env.local
# Build
/build.out/
/build.tmp/
@@ -99,6 +103,7 @@ tools/docfx/
playwright-report
trace.zip
/tests/Umbraco.Tests.AcceptanceTest/results
/tests/Umbraco.Tests.AcceptanceTest/dist
# Ignore auto-generated schema
/src/Umbraco.Cms.Targets/tasks/
@@ -107,6 +112,7 @@ trace.zip
/src/Umbraco.Web.UI/appsettings-schema.json
/src/Umbraco.Web.UI/appsettings-schema.*.json
/src/Umbraco.Web.UI/umbraco-package-schema.json
/src/Umbraco.Web.UI.Client/umbraco-package-schema.json
/tests/Umbraco.Tests.Integration/appsettings-schema.json
/tests/Umbraco.Tests.Integration/appsettings-schema.*.json
/tests/Umbraco.Tests.Integration/umbraco-package-schema.json
+14
View File
@@ -0,0 +1,14 @@
{
"mcpServers": {
"umbraco-cms": {
"command": "npx",
"args": ["@umbraco-cms/mcp-dev@17"]
},
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest"
]
}
}
}
+133 -2
View File
@@ -259,7 +259,108 @@ Project ownership is distributed across teams. Check individual project director
---
## 5. Project-Specific Notes
## 5. Avoiding Breaking Changes
No binary breaking changes are allowed within a major version. Three patterns are used:
### 5.1 Obsolete Constructor + StaticServiceProvider
When a public class needs new dependencies, obsolete the existing constructor and add a new one. The old constructor delegates to the new one, resolving missing deps via `StaticServiceProvider`.
```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;
}
```
**Examples**:
- `ContentCollectionPresentationFactory` - added `FlagProviderCollection`
- `CacheInstructionService` - added `ILastSyncedManager`, `IRepositoryCacheVersionService`
- `DocumentPresentationFactory` - added `FlagProviderCollection`
**Rules**:
- Old constructor marked `[Obsolete("... Scheduled for removal in Umbraco {current-major+2}.")]`
- Old constructor calls new constructor via `: this(...)`
- Uses `StaticServiceProvider.Instance.GetRequiredService<T>()` for new params only
- DI registration must use the NEW constructor (old is for external consumers only)
### 5.2 Obsolete Method + New Overload
When a public method signature needs to change, add the new method/overload and obsolete the old. The obsolete method should call the new one with suitable defaults.
```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
}
```
**Rules**:
- Old method marked `[Obsolete]` with removal schedule
- DRY: old method calls new method, providing defaults for new parameters
- All internal callers must be updated to use the new method
- No callers should remain on the obsolete method within the codebase
### 5.3 Default Interface Implementation
When adding methods to a public interface, provide a default implementation so existing external implementations don't break.
```csharp
public interface IMyService
{
// Existing method
void ExistingMethod();
// New method with default implementation
void NewMethod(string param)
=> ExistingMethod(); // delegate to existing if possible
}
```
**Strategies for the default** (in order of preference):
1. **Use existing interface methods** to satisfy the contract (even if not optimal)
2. **Return a sensible default** like empty collection, null, etc.
3. **Throw `NotImplementedException`** if no reasonable default exists
**Example**: `IContentService.SaveBlueprint` - new overload with `IContent? createdFromContent` has a default impl that calls the old method (ignoring the new param).
**Example**: `IDocumentPresentationFactory.CreateCulturePublishScheduleModels` - full default implementation with logic, uses `StaticServiceProvider` for dependency resolution within the interface.
**Rules**:
- Add `// TODO (V{next-major}): Remove the default implementation when {obsolete method} is removed.` comment
- Default impl should be functionally correct even if not optimal
- If using `StaticServiceProvider` in a default impl, note this is temporary
### 5.4 General Rules
- **Removal policy**: Obsoleted members must remain for at least one full major version before removal. If obsoleted in version N, the earliest removal is version N+2. For example, something obsoleted in v17 is scheduled for removal in v19 (giving the whole of v18 as a deprecation period).
- All `[Obsolete]` attributes must include **"Scheduled for removal in Umbraco {current+2}"**
- Read `version.json` to determine the current major version
- Suppress `CS0618` warnings where obsolete members must call each other:
```csharp
#pragma warning disable CS0618 // Type or member is obsolete
=> OldMethod(param);
#pragma warning restore CS0618 // Type or member is obsolete
```
- Update ALL internal callers to use the new API - no internal code should use obsolete members
---
## 6. Project-Specific Notes
### Centralized Package Management
@@ -292,13 +393,18 @@ The repository contains BOTH (actively supported):
All APIs use **OpenIddict** (OAuth 2.0/OpenID Connect):
- Reference tokens (not JWT) for better security
- **Secure cookie-based token storage** (v17+) - tokens stored in HTTP-only cookies with `__Host-` prefix
- Tokens are redacted from client-side responses and passed via secure cookies only
- Tokens are redacted from client-side responses and passed via secure cookies only (`[redacted]` placeholder)
- ASP.NET Core Data Protection for token encryption
- Configured in `Umbraco.Cms.Api.Common`
- API requests must include credentials (`credentials: include` for fetch)
**Load Balancing Requirement**: All servers must share the same Data Protection key ring.
**Frontend auth pitfalls** — see `src/Umbraco.Web.UI.Client/docs/edge-cases.md` (Auth & Cross-tab section) and `docs/security.md`. Key points:
- Never call `validateToken()` per API request — it revokes the previous reference token (ID2019 errors)
- `window.opener` is set for ANY `window.open()` target, not only OAuth popups — scope guards to the pathname too
- BroadcastChannel does not deliver messages to the sender's own tab
### Content Caching Strategy
**HybridCache** (`Umbraco.PublishedCache.HybridCache`):
@@ -313,6 +419,20 @@ APIs use `Asp.Versioning.Mvc`:
- Delivery API: `/umbraco/delivery/api/v{version}/*`
- OpenAPI/Swagger docs per version
### Updating `OpenApi.json` (Management API)
When a PR changes Management API controllers or models, the `OpenApi.json` file in the Management API project must be updated:
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`
**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.
### Backoffice npm Package
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
1. **Circular Dependencies**: Avoided via `Lazy<T>` or event notifications
@@ -342,6 +462,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 |
@@ -367,6 +497,7 @@ dotnet pack -c Release
For detailed information about individual projects, see their CLAUDE.md files:
- **Core Architecture**: `/src/Umbraco.Core/CLAUDE.md` - Service contracts, notification patterns
- **API Infrastructure**: `/src/Umbraco.Cms.Api.Common/CLAUDE.md` - OpenAPI, authentication, serialization
- **Backoffice Frontend**: `/src/Umbraco.Web.UI.Client/CLAUDE.md` - Lit web components, extension system, auth client
### Getting Help
+30 -29
View File
@@ -13,27 +13,27 @@
</ItemGroup>
<!-- Microsoft packages -->
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.1.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.4.0" />
<PackageVersion Include="System.Linq.Async" Version="7.0.0" />
</ItemGroup>
<!-- Umbraco packages -->
@@ -42,27 +42,27 @@
</ItemGroup>
<!-- Third-party packages -->
<ItemGroup>
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.1" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.1" />
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
<PackageVersion Include="Examine" Version="3.7.1" />
<PackageVersion Include="Examine.Core" Version="3.7.1" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="MailKit" Version="4.14.1" />
<PackageVersion Include="Markdig" Version="0.44.0" />
<PackageVersion Include="MailKit" Version="4.15.1" />
<PackageVersion Include="Markdig" Version="0.45.0" />
<PackageVersion Include="Markdown" Version="2.2.1" />
<PackageVersion Include="MessagePack" Version="3.1.4" />
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
<PackageVersion Include="MiniProfiler.Shared" Version="4.5.4" />
<PackageVersion Include="ncrontab" Version="3.4.0" />
<PackageVersion Include="NPoco" Version="6.1.0" />
<PackageVersion Include="NPoco.SqlServer" Version="6.1.0" />
<PackageVersion Include="NPoco" Version="6.2.0" />
<PackageVersion Include="NPoco.SqlServer" Version="6.2.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="7.2.0" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="7.2.0" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="7.2.0" />
<PackageVersion Include="Serilog" Version="4.3.0" />
<PackageVersion Include="Serilog" Version="4.3.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
@@ -76,7 +76,8 @@
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.0.1" />
<!-- When updating this version, also update templates/UmbracoExtension/Umbraco.Extension.csproj -->
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.4" />
</ItemGroup>
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
<ItemGroup>
@@ -88,4 +89,4 @@
<!-- TODO (V19): Remove these pinned dependencies when the Markdown dependency is removed. -->
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
</Project>
</Project>
+144
View File
@@ -0,0 +1,144 @@
# MCP (Model Context Protocol) Setup
This repository includes configuration for [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers, enabling AI tooling integration for Umbraco CMS development workflows.
## Overview
MCP allows AI assistants (like Claude) to interact with external tools and services. This repository configures two MCP servers:
| Server | Purpose | Package |
|--------|---------|---------|
| **umbraco-cms** | Manage Umbraco content types, documents, and media | `@umbraco-cms/mcp-dev@17` |
| **playwright** | Browser automation for testing and debugging | `@playwright/mcp@latest` |
## Quick Start
### 1. Start Umbraco Locally
Ensure your local Umbraco instance is running at `https://localhost:44339` (or update the URL in your `.env.local`).
### 2. Configure Environment Variables
Copy the example environment file and customize it:
```bash
cp .env.example .env.local
```
Edit `.env.local` with your local settings:
```env
UMBRACO_CLIENT_ID=umbraco-back-office-mcp
UMBRACO_CLIENT_SECRET=<your-client-secret>
UMBRACO_BASE_URL=https://localhost:44339
NODE_TLS_REJECT_UNAUTHORIZED=0
UMBRACO_INCLUDE_TOOL_COLLECTIONS=data-type,document-type,document,media-type,media
```
### 3. Configure the OAuth Client in Umbraco
Create an OAuth client in your Umbraco instance with:
- **Client ID**: `umbraco-back-office-mcp`
- **Client Secret**: The value you set in `.env.local`
- **Grant Type**: Client Credentials
## Environment Variables Reference
| Variable | Description | Example |
|----------|-------------|---------|
| `UMBRACO_CLIENT_ID` | OAuth client ID configured in Umbraco | `umbraco-back-office-mcp` |
| `UMBRACO_CLIENT_SECRET` | OAuth client secret (keep secure!) | `your-secure-secret` |
| `UMBRACO_BASE_URL` | URL of your local Umbraco instance | `https://localhost:44339` |
| `NODE_TLS_REJECT_UNAUTHORIZED` | Set to `0` for self-signed certificates (local dev only) | `0` |
| `UMBRACO_INCLUDE_TOOL_COLLECTIONS` | Comma-separated list of tool collections to enable | `data-type,document-type,document` |
### Tool Collections
The `UMBRACO_INCLUDE_TOOL_COLLECTIONS` variable controls which Umbraco MCP tools are available:
- `data-type` - Manage data types (property editors)
- `document-type` - Manage document types (content types)
- `document` - Manage content/documents
- `media-type` - Manage media types
- `media` - Manage media items
## Security Considerations
> **Warning**: This configuration is for **local development only**.
### Self-Signed Certificates
`NODE_TLS_REJECT_UNAUTHORIZED=0` disables SSL certificate validation. This is necessary for self-signed certificates in local development but:
- **Never use in production**
- Affects all HTTPS connections made by Node.js processes
- Consider trusting your local development certificate instead
### Client Secrets
- Never commit real secrets to source control
- The `.env.local` file is gitignored for this reason
- Use strong, unique secrets even in development
- The example value `1234567890` in `.env.example` is a placeholder only
## File Structure
```
Umbraco-CMS/
├── .mcp.json # MCP server configuration
├── .env.example # Example environment variables (committed)
├── .env.local # Your local environment variables (gitignored)
├── .claude/
│ ├── settings.json # Shared Claude AI permissions (committed)
│ └── settings.local.json # Local Claude overrides (gitignored)
├── .gitignore # Ignores .env.local and settings.local.json
└── MCP.md # This documentation (you are here)
```
## Claude AI Permissions
The `.claude/settings.json` file configures which MCP tools Claude can use automatically without prompting. This is shared across the team for consistent developer experience.
### Customizing Permissions Locally
Create `.claude/settings.local.json` to override permissions for your environment:
```json
{
"permissions": {
"allow": [
"mcp__umbraco__get-all-document-types"
]
}
}
```
## Troubleshooting
### "Connection refused" errors
- Ensure Umbraco is running at the configured `UMBRACO_BASE_URL`
- Check that the port matches your local setup
### "Unauthorized" errors
- Verify the OAuth client is configured in Umbraco
- Check that `UMBRACO_CLIENT_ID` and `UMBRACO_CLIENT_SECRET` match
- Ensure the client has appropriate permissions
### "Certificate" errors
- For local development, set `NODE_TLS_REJECT_UNAUTHORIZED=0` in `.env.local`
- Alternatively, trust your local development certificate
### MCP server not starting
- Ensure Node.js is installed (v22+ recommended, matching .nvmrc)
- Run `npx @umbraco-cms/mcp-dev@17 --help` to verify the package works
## Further Reading
- [Model Context Protocol Documentation](https://modelcontextprotocol.io/)
- [Umbraco MCP Package](https://www.npmjs.com/package/@umbraco-cms/mcp-dev)
- [Playwright MCP](https://www.npmjs.com/package/@playwright/mcp)
- [Claude Code Documentation](https://docs.anthropic.com/claude-code)
+115 -7
View File
@@ -107,9 +107,17 @@ stages:
command: build
projects: $(solution)
arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
# Publish compiled DLLs for C# API documentation generation
# Separate artifact to avoid increasing build_output size for all builds
- task: PublishPipelineArtifact@1
displayName: Publish DocFX DLLs
condition: and(succeeded(), or(eq(variables['build.NBGV_PublicRelease'], 'True'), eq('${{ parameters.buildApiDocs }}', 'True')))
inputs:
targetPath: $(Build.SourcesDirectory)/src/Umbraco.Cms/bin/Release
artifactName: csharp-docs-dlls
- powershell: |
dotnet tool install --global CycloneDX
dotnet-CycloneDX $(solution) --output $(Build.ArtifactStagingDirectory)/bom --filename bom-dotnet.xml
dotnet-CycloneDX $(solution) --spec-version 1.5 --output $(Build.ArtifactStagingDirectory)/bom --filename bom-dotnet.xml
displayName: 'Generate Backend BOM'
- powershell: |
npm install --global @cyclonedx/cyclonedx-npm
@@ -167,6 +175,41 @@ stages:
artifact: bom-frontend
displayName: 'Publish Frontend BOM'
- job: C
displayName: Build Test Helpers Package
pool:
vmImage: "ubuntu-latest"
steps:
- checkout: self
submodules: false
lfs: false
fetchDepth: 500
- template: templates/e2e-install.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
npm_config_cache: ${{ variables.npm_config_cache }}
- bash: |
echo "##[command]Install nbgv"
dotnet tool install --tool-path . nbgv
echo "##[command]Running nbgv get-version"
PACKAGE_VERSION=$(nbgv get-version -v NpmPackageVersion)
echo "##[command]Running npm version"
echo "##[debug]Version: $PACKAGE_VERSION"
cd tests/Umbraco.Tests.AcceptanceTest
npm version $PACKAGE_VERSION --allow-same-version --no-git-tag-version
displayName: Set NPM Version
- bash: |
echo "##[command]Running npm pack"
mkdir $(Build.ArtifactStagingDirectory)/npm-testhelpers
npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm-testhelpers
displayName: Run npm pack
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
- task: PublishPipelineArtifact@1
displayName: Publish Test Helpers npm artifact
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/npm-testhelpers
artifactName: npm-testhelpers
- stage: E2E_BOM
displayName: E2E Tests BOM Generation
dependsOn: []
@@ -200,12 +243,22 @@ stages:
variables:
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
jobs:
# C# API Reference
# C# API Reference - uses pre-compiled DLLs for faster generation (csproj approach caused timeouts)
- job:
displayName: Build C# API Reference
pool:
vmImage: "windows-latest"
steps:
- checkout: self
submodules: false
lfs: false
fetchDepth: 1
fetchFilter: tree:0
- task: DownloadPipelineArtifact@2
displayName: Download DocFX DLLs
inputs:
artifact: csharp-docs-dlls
path: $(Build.SourcesDirectory)/src/Umbraco.Cms/bin/Release
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
@@ -215,7 +268,7 @@ stages:
inputs:
targetType: inline
script: |
dotnet tool install -g docfx
dotnet tool install -g docfx --version 2.78.4
if ($lastexitcode -ne 0){
throw ("Error installing DocFX")
}
@@ -561,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:
@@ -814,12 +866,44 @@ stages:
npm publish "${files[0]}"
displayName: Push to npm (MyGet)
workingDirectory: $(Pipeline.Workspace)/npm
- job: PublishTestHelpersNpm
displayName: Push TestHelpers to pre-release feed (npm)
steps:
- checkout: none
- download: current
artifact: npm-testhelpers
- bash: |
# Check if we are on a nightly build
if [ $isNightly = "False" ]; then
echo "##[debug]Prerelease build detected"
registry="https://www.myget.org/F/umbracoprereleases/npm/"
else
echo "##[debug]Nightly build detected"
registry="https://www.myget.org/F/umbraconightly/npm/"
fi
echo "@umbraco-cms:registry=$registry" >> .npmrc
env:
isNightly: ${{parameters.isNightly}}
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
displayName: Add scoped registry to .npmrc
- task: npmAuthenticate@0
displayName: Authenticate with npm (MyGet)
inputs:
workingFile: "$(Pipeline.Workspace)/npm-testhelpers/.npmrc"
customEndpoint: "MyGet (npm) - Umbracoprereleases, MyGet (npm) - Umbraconightly"
- bash: |
# Setup temp npm project to load in defaults from the local .npmrc
npm init -y
# Find the first .tgz file in the current directory and publish it
files=( ./*.tgz )
npm publish "${files[0]}"
displayName: Push test helpers to npm (MyGet)
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
- stage: Deploy_NuGet
displayName: NuGet release
dependsOn:
- Deploy_MyGet
- Build_Docs
dependsOn: Deploy_MyGet
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}}))
jobs:
- job:
@@ -870,6 +954,29 @@ stages:
npm publish "${files[0]}"
displayName: Push to npm
workingDirectory: $(Pipeline.Workspace)/npm
- job: PublishTestHelpers
displayName: Push Test Helpers to NPM
steps:
- checkout: none
- download: current
artifact: npm-testhelpers
- bash: echo "@umbraco-cms:registry=https://registry.npmjs.org" >> .npmrc
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
displayName: Add scoped registry to .npmrc
- task: npmAuthenticate@0
displayName: Authenticate with npm
inputs:
workingFile: $(Pipeline.Workspace)/npm-testhelpers/.npmrc
customEndpoint: "NPM - Umbraco Backoffice"
- script: |
# Setup temp npm project to load in defaults from the local .npmrc
npm init -y
# Find the first .tgz file in the current directory and publish it
files=( ./*.tgz )
npm publish "${files[0]}"
displayName: Push test helpers to npm
workingDirectory: $(Pipeline.Workspace)/npm-testhelpers
- stage: Upload_API_Docs
pool:
@@ -879,6 +986,7 @@ stages:
displayName: Upload API Documentation
dependsOn:
- Build
- Build_Docs
- Deploy_NuGet
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadApiDocs}}))
jobs:
+4 -8
View File
@@ -3,17 +3,13 @@
{
"src": [
{
"src": "../../src",
"src": "../../src/Umbraco.Cms/bin/Release",
"files": [
"**/*.csproj"
"**/Umbraco.*.dll"
],
"exclude": [
"**/obj/**",
"**/bin/**",
"**/Umbraco.Web.csproj",
"**/Umbraco.Web.UI.csproj",
"**/Umbraco.Cms.StaticAssets.csproj",
"**/JsonSchema.csproj"
"**/Umbraco.Cms.StaticAssets.dll",
"**/Umbraco.Cms.Targets.dll"
]
}
],
@@ -9,7 +9,7 @@
<meta name="generator" content="docfx {{_docfxVersion}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="icon" type="image/png" href="https://our.umbraco.com/assets/images/app-icons/favicon.png">
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.min.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<meta property="docfx:navrel" content="{{_navRel}}">
+9 -3
View File
@@ -54,11 +54,17 @@ steps:
- pwsh: |
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
$destinationPath = "UmbracoProject"
$csharpFiles = Get-ChildItem -Path $sourcePath -Filter "*.cs"
$csharpFiles = Get-ChildItem -Path $sourcePath -Filter "*.cs" -Recurse
if ($csharpFiles) {
$csharpFiles | ForEach-Object {
Write-Host "Copying: $($_.FullName)"
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
$relativePath = $_.FullName.Substring($sourcePath.Length + 1)
$targetPath = Join-Path -Path $destinationPath -ChildPath $relativePath
$targetDir = Split-Path -Path $targetPath -Parent
if (-not (Test-Path -Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
Write-Host "Copying: $($_.FullName) -> $targetPath"
Copy-Item -Path $_.FullName -Destination $targetPath -Force
}
} else {
Write-Host "No C# files found."
+2 -2
View File
@@ -44,7 +44,7 @@ steps:
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet new install Umbraco.Templates@$cmsVersion
dotnet new umbraco --name UmbracoProject --exclude-gitignore --no-restore --no-update-check
displayName: Install Template
workingDirectory: $(Agent.BuildDirectory)/app
+6 -7
View File
@@ -4,12 +4,10 @@ pr: none
trigger: none
schedules:
- cron: '0 0 * * *'
displayName: Daily midnight build
- cron: '0 3 * * *'
displayName: Daily 3AM build (main)
branches:
include:
- v15/dev
- v16/dev
- main
parameters:
@@ -321,7 +319,8 @@ stages:
- stage: DefaultConfigE2E
displayName: Default Config E2E Tests
dependsOn: Build
dependsOn: Integration
condition: always()
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
# Enable console logging in Release mode
@@ -340,7 +339,6 @@ stages:
UMBRACO__CMS__GLOBAL__VERSIONCHECKPERIOD: 0
UMBRACO__CMS__GLOBAL__USEHTTPS: true
UMBRACO__CMS__HEALTHCHECKS__NOTIFICATION__ENABLED: false
UMBRACO__CMS__KEEPALIVE__DISABLEKEEPALIVETASK: true
UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL: https://localhost:44331/
ASPNETCORE_URLS: https://localhost:44331
jobs:
@@ -502,7 +500,8 @@ stages:
- stage: AdditionalConfigE2E
displayName: Additional Config E2E Tests
dependsOn: Build
dependsOn: DefaultConfigE2E
condition: always()
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
ASPNETCORE_URLS: https://localhost:44331
+1
View File
@@ -10,6 +10,7 @@ schedules:
include:
- v13/dev
- v16/dev
- v18/dev
- main
steps:
+5 -1
View File
@@ -29,7 +29,7 @@ steps:
"UMBRACO_USER_LOGIN=${{ parameters.PlaywrightUserEmail }}
UMBRACO_USER_PASSWORD=${{ parameters.PlaywrightPassword }}
URL=${{ parameters.ASPNETCORE_URLS }}
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
STORAGE_STATE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
@@ -47,3 +47,7 @@ steps:
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
- script: npm run build
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Build test helpers
@@ -1,10 +1,17 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Common.Accessors;
/// <summary>
/// Provides access to the <see cref="IOutputExpansionStrategy"/> for the current HTTP request context.
/// </summary>
public sealed class RequestContextOutputExpansionStrategyAccessor : RequestContextServiceAccessorBase<IOutputExpansionStrategy>, IOutputExpansionStrategyAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="RequestContextOutputExpansionStrategyAccessor"/> class.
/// </summary>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
public RequestContextOutputExpansionStrategyAccessor(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
{
@@ -1,17 +1,30 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Umbraco.Cms.Api.Common.Accessors;
/// <summary>
/// Base class for accessing request-scoped services from the current HTTP context.
/// </summary>
/// <typeparam name="T">The type of service to access.</typeparam>
public abstract class RequestContextServiceAccessorBase<T>
where T : class
{
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="RequestContextServiceAccessorBase{T}"/> class.
/// </summary>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
protected RequestContextServiceAccessorBase(IHttpContextAccessor httpContextAccessor)
=> _httpContextAccessor = httpContextAccessor;
/// <summary>
/// Attempts to retrieve the service from the current HTTP context's request services.
/// </summary>
/// <param name="requestStartNodeService">When this method returns, contains the service instance if found; otherwise, <c>null</c>.</param>
/// <returns><c>true</c> if the service was found; otherwise, <c>false</c>.</returns>
public bool TryGetValue([NotNullWhen(true)] out T? requestStartNodeService)
{
requestStartNodeService = _httpContextAccessor.HttpContext?.RequestServices.GetService<T>();
@@ -1,9 +1,19 @@
namespace Umbraco.Cms.Api.Common.Attributes;
/// <summary>
/// Attribute used to map a class to a specific API for OpenAPI documentation generation.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MapToApiAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="MapToApiAttribute"/> class.
/// </summary>
/// <param name="apiName">The name of the API to map to.</param>
public MapToApiAttribute(string apiName) => ApiName = apiName;
/// <summary>
/// Gets the name of the API this class is mapped to.
/// </summary>
public string ApiName { get; }
}
@@ -1,9 +1,12 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.Builders;
/// <summary>
/// A fluent builder for creating RFC 7807 <see cref="ProblemDetails"/> responses.
/// </summary>
public class ProblemDetailsBuilder
{
private string? _title;
@@ -12,24 +15,45 @@ public class ProblemDetailsBuilder
private string? _operationStatus;
private IDictionary<string, object>? _extensions;
/// <summary>
/// Sets the title of the problem details.
/// </summary>
/// <param name="title">A short, human-readable summary of the problem type.</param>
/// <returns>The current builder instance for method chaining.</returns>
public ProblemDetailsBuilder WithTitle(string title)
{
_title = title;
return this;
}
/// <summary>
/// Sets the detail of the problem details.
/// </summary>
/// <param name="detail">A human-readable explanation specific to this occurrence of the problem.</param>
/// <returns>The current builder instance for method chaining.</returns>
public ProblemDetailsBuilder WithDetail(string detail)
{
_detail = detail;
return this;
}
/// <summary>
/// Sets the type of the problem details.
/// </summary>
/// <param name="type">A URI reference that identifies the problem type.</param>
/// <returns>The current builder instance for method chaining.</returns>
public ProblemDetailsBuilder WithType(string type)
{
_type = type;
return this;
}
/// <summary>
/// Sets the operation status from an enum value.
/// </summary>
/// <typeparam name="TEnum">The enum type representing operation statuses.</typeparam>
/// <param name="operationStatus">The operation status enum value.</param>
/// <returns>The current builder instance for method chaining.</returns>
public ProblemDetailsBuilder WithOperationStatus<TEnum>(TEnum operationStatus)
where TEnum : Enum
{
@@ -37,9 +61,20 @@ public class ProblemDetailsBuilder
return this;
}
/// <summary>
/// Adds request model validation errors to the problem details.
/// </summary>
/// <param name="errors">A dictionary of field names to error messages.</param>
/// <returns>The current builder instance for method chaining.</returns>
public ProblemDetailsBuilder WithRequestModelErrors(IDictionary<string, string[]> errors)
=> WithExtension(nameof(HttpValidationProblemDetails.Errors).ToFirstLowerInvariant(), errors);
/// <summary>
/// Adds a custom extension to the problem details.
/// </summary>
/// <param name="key">The extension key.</param>
/// <param name="value">The extension value.</param>
/// <returns>The current builder instance for method chaining.</returns>
public ProblemDetailsBuilder WithExtension(string key, object value)
{
_extensions ??= new Dictionary<string, object>();
@@ -47,6 +82,10 @@ public class ProblemDetailsBuilder
return this;
}
/// <summary>
/// Builds the <see cref="ProblemDetails"/> instance with all configured values.
/// </summary>
/// <returns>A new <see cref="ProblemDetails"/> instance.</returns>
public ProblemDetails Build()
{
var problemDetails = new ProblemDetails
@@ -1,10 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace Umbraco.Cms.Api.Common.Configuration;
/// <summary>
/// Configures <see cref="ApiBehaviorOptions"/> for Umbraco APIs.
/// </summary>
public class ConfigureApiBehaviorOptions : IConfigureOptions<ApiBehaviorOptions>
{
/// <inheritdoc/>
public void Configure(ApiBehaviorOptions options) =>
// disable ProblemDetails as default result type for every non-success response (i.e. 404)
// - see https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.apibehavioroptions.suppressmapclienterrors
@@ -5,12 +5,21 @@ using Umbraco.Cms.Api.Common.Json;
namespace Umbraco.Cms.Api.Common.Configuration;
/// <summary>
/// Configures <see cref="MvcOptions"/> with named JSON input and output formatters for Umbraco APIs.
/// </summary>
public class ConfigureMvcJsonOptions : IConfigureOptions<MvcOptions>
{
private readonly string _jsonOptionsName;
private readonly IOptionsMonitor<JsonOptions> _jsonOptions;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureMvcJsonOptions"/> class.
/// </summary>
/// <param name="jsonOptionsName">The name of the JSON options configuration to use.</param>
/// <param name="jsonOptions">The JSON options monitor.</param>
/// <param name="loggerFactory">The logger factory.</param>
public ConfigureMvcJsonOptions(
string jsonOptionsName,
IOptionsMonitor<JsonOptions> jsonOptions,
@@ -21,6 +30,7 @@ public class ConfigureMvcJsonOptions : IConfigureOptions<MvcOptions>
_loggerFactory = loggerFactory;
}
/// <inheritdoc/>
public void Configure(MvcOptions options)
{
JsonOptions jsonOptions = _jsonOptions.Get(_jsonOptionsName);
@@ -4,12 +4,24 @@ using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Api.Common.Configuration;
/// <summary>
/// Configures OpenIddict server options for Umbraco authentication.
/// </summary>
/// <remarks>
/// Disables transport security requirement when HTTPS is not configured in global settings.
/// Warning: This should only be used in development environments.
/// </remarks>
internal sealed class ConfigureOpenIddict : IConfigureOptions<OpenIddictServerAspNetCoreOptions>
{
private readonly IOptions<GlobalSettings> _globalSettings;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureOpenIddict"/> class.
/// </summary>
/// <param name="globalSettings">The global settings options.</param>
public ConfigureOpenIddict(IOptions<GlobalSettings> globalSettings) => _globalSettings = globalSettings;
/// <inheritdoc/>
public void Configure(OpenIddictServerAspNetCoreOptions options)
=> options.DisableTransportSecurityRequirement = _globalSettings.Value.UseHttps is false;
}
@@ -8,6 +8,9 @@ using Umbraco.Cms.Core.DependencyInjection;
namespace Umbraco.Cms.Api.Common.Configuration;
/// <summary>
/// Configures Swagger/OpenAPI generation options for Umbraco APIs.
/// </summary>
public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IOperationIdSelector _operationIdSelector;
@@ -15,6 +18,13 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
private readonly ISubTypesSelector _subTypesSelector;
private readonly IDocumentInclusionSelector _documentInclusionSelector;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureUmbracoSwaggerGenOptions"/> class.
/// </summary>
/// <param name="operationIdSelector">The operation ID selector.</param>
/// <param name="schemaIdSelector">The schema ID selector.</param>
/// <param name="subTypesSelector">The sub-types selector for polymorphism support.</param>
/// <param name="documentInclusionSelector">The document inclusion selector.</param>
public ConfigureUmbracoSwaggerGenOptions(
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector,
@@ -27,6 +37,12 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
_documentInclusionSelector = documentInclusionSelector;
}
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureUmbracoSwaggerGenOptions"/> class.
/// </summary>
/// <param name="operationIdSelector">The operation ID selector.</param>
/// <param name="schemaIdSelector">The schema ID selector.</param>
/// <param name="subTypesSelector">The sub-types selector for polymorphism support.</param>
[Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")]
public ConfigureUmbracoSwaggerGenOptions(
IOperationIdSelector operationIdSelector,
@@ -40,6 +56,7 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
{
}
/// <inheritdoc/>
public void Configure(SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SwaggerDoc(
@@ -64,7 +81,14 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
swaggerGenOptions.SupportNonNullableReferenceTypes();
}
// see https://github.com/domaindrivendev/Swashbuckle.AspNetCore#change-operation-sort-order-eg-for-ui-sorting
/// <summary>
/// Generates a sort key for API actions.
/// </summary>
/// <param name="apiDesc">The API description.</param>
/// <returns>A string used to sort API operations in the documentation.</returns>
/// <remarks>
/// See https://github.com/domaindrivendev/Swashbuckle.AspNetCore#change-operation-sort-order-eg-for-ui-sorting.
/// </remarks>
private static string ActionOrderBy(ApiDescription apiDesc)
=> $"{apiDesc.GroupName}_{apiDesc.ActionDescriptor.AttributeRouteInfo?.Template ?? apiDesc.ActionDescriptor.RouteValues["controller"]}_{(apiDesc.ActionDescriptor.RouteValues.TryGetValue("action", out var action) ? action : null)}_{apiDesc.HttpMethod}";
}
@@ -1,6 +1,12 @@
namespace Umbraco.Cms.Api.Common.Configuration;
/// <summary>
/// Contains default configuration values for the API.
/// </summary>
internal static class DefaultApiConfiguration
{
/// <summary>
/// The default API name used for endpoints not assigned to a specific API.
/// </summary>
public const string ApiName = "default";
}
@@ -1,7 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using OpenIddict.Validation;
using Umbraco.Cms.Core;
@@ -13,10 +16,19 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
/// <summary>
/// Handles secure storage of back-office authentication tokens in HTTP-only cookies.
/// </summary>
/// <remarks>
/// This handler intercepts OpenIddict token responses for the back-office client and stores
/// access tokens, refresh tokens, and PKCE codes in encrypted HTTP-only cookies. The tokens
/// are redacted from the response to prevent client-side JavaScript access.
/// </remarks>
internal sealed class HideBackOfficeTokensHandler
: IOpenIddictServerHandler<OpenIddictServerEvents.ApplyTokenResponseContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ApplyAuthorizationResponseContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ExtractTokenRequestContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ExtractRevocationRequestContext>,
IOpenIddictValidationHandler<OpenIddictValidationEvents.ProcessAuthenticationContext>,
INotificationHandler<UserLogoutSuccessNotification>
{
@@ -25,25 +37,44 @@ internal sealed class HideBackOfficeTokensHandler
// The __Host- prefix enforces secure cookies at browser level (requires Secure, Path=/, no Domain).
// For local development over HTTP, we use a simpler prefix to avoid browser rejection.
private const string SecureCookiePrefix = "__Host-";
private const string AccessTokenCookieName = "umbAccessToken";
private const string RefreshTokenCookieName = "umbRefreshToken";
private const string PkceCodeCookieName = "umbPkceCode";
private readonly string _accessTokenCookieName = "umbAccessToken";
private readonly string _refreshTokenCookieName = "umbRefreshToken";
private readonly string _pkceCodeCookieName = "umbPkceCode";
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly ILogger<HideBackOfficeTokensHandler> _logger;
#pragma warning disable CS0618 // Type or member is obsolete
private readonly BackOfficeTokenCookieSettings _backOfficeTokenCookieSettings;
#pragma warning restore CS0618 // Type or member is obsolete
private readonly GlobalSettings _globalSettings;
/// <summary>
/// Initializes a new instance of the <see cref="HideBackOfficeTokensHandler"/> class.
/// </summary>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
/// <param name="dataProtectionProvider">The data protection provider for encrypting cookie values.</param>
/// <param name="logger">The logger.</param>
/// <param name="backOfficeTokenCookieSettings">The back-office token cookie settings.</param>
/// <param name="globalSettings">The global settings.</param>
public HideBackOfficeTokensHandler(
IHttpContextAccessor httpContextAccessor,
IDataProtectionProvider dataProtectionProvider,
ILogger<HideBackOfficeTokensHandler> logger,
#pragma warning disable CS0618 // Type or member is obsolete
IOptions<BackOfficeTokenCookieSettings> backOfficeTokenCookieSettings,
#pragma warning restore CS0618 // Type or member is obsolete
IOptions<GlobalSettings> globalSettings)
{
_httpContextAccessor = httpContextAccessor;
_dataProtectionProvider = dataProtectionProvider;
_logger = logger;
_backOfficeTokenCookieSettings = backOfficeTokenCookieSettings.Value;
_globalSettings = globalSettings.Value;
_accessTokenCookieName += _backOfficeTokenCookieSettings.SiteName;
_refreshTokenCookieName += _backOfficeTokenCookieSettings.SiteName;
_pkceCodeCookieName += _backOfficeTokenCookieSettings.SiteName;
}
/// <summary>
@@ -63,13 +94,13 @@ internal sealed class HideBackOfficeTokensHandler
if (context.Response.AccessToken is not null)
{
SetCookie(httpContext, AccessTokenCookieName, context.Response.AccessToken);
SetCookie(httpContext, _accessTokenCookieName, context.Response.AccessToken);
context.Response.AccessToken = RedactedTokenValue;
}
if (context.Response.RefreshToken is not null)
{
SetCookie(httpContext, RefreshTokenCookieName, context.Response.RefreshToken);
SetCookie(httpContext, _refreshTokenCookieName, context.Response.RefreshToken);
context.Response.RefreshToken = RedactedTokenValue;
}
@@ -91,7 +122,7 @@ internal sealed class HideBackOfficeTokensHandler
if (context.Response.Code is not null)
{
SetCookie(GetHttpContext(), PkceCodeCookieName, context.Response.Code);
SetCookie(GetHttpContext(), _pkceCodeCookieName, context.Response.Code);
context.Response.Code = RedactedTokenValue;
}
@@ -113,12 +144,12 @@ internal sealed class HideBackOfficeTokensHandler
// Handle when the PKCE code is being exchanged for an access token.
if (context.Request.Code == RedactedTokenValue
&& TryGetCookie(httpContext, PkceCodeCookieName, out var code))
&& TryGetCookie(httpContext, _pkceCodeCookieName, out var code))
{
context.Request.Code = code;
// We won't need the PKCE cookie after this, let's remove it.
RemoveCookie(httpContext, PkceCodeCookieName);
RemoveCookie(httpContext, _pkceCodeCookieName);
}
else
{
@@ -129,7 +160,7 @@ internal sealed class HideBackOfficeTokensHandler
// Handle when a refresh token is being exchanged for a new access token.
if (context.Request.RefreshToken == RedactedTokenValue
&& TryGetCookie(httpContext, RefreshTokenCookieName, out var refreshToken))
&& TryGetCookie(httpContext, _refreshTokenCookieName, out var refreshToken))
{
context.Request.RefreshToken = refreshToken;
}
@@ -144,6 +175,40 @@ internal sealed class HideBackOfficeTokensHandler
return ValueTask.CompletedTask;
}
/// <summary>
/// This is invoked when a token revocation request is received.
/// </summary>
public ValueTask HandleAsync(OpenIddictServerEvents.ExtractRevocationRequestContext context)
{
if (context.Request?.ClientId != Constants.OAuthClientIds.BackOffice)
{
// Only ever handle the back-office client.
return ValueTask.CompletedTask;
}
HttpContext httpContext = GetHttpContext();
// Determine which cookie to read based on the token type hint.
var cookieName = context.Request.TokenTypeHint == OpenIddictConstants.TokenTypeHints.RefreshToken
? _refreshTokenCookieName
: _accessTokenCookieName;
if (context.Request.Token == RedactedTokenValue
&& TryGetCookie(httpContext, cookieName, out var token))
{
context.Request.Token = token;
}
else
{
// If we got here, either the token was not redacted, or nothing was found in the expected cookie.
// If OpenIddict found a token, it could be an old token that is potentially still valid. For security
// reasons, we cannot accept that; at this point, we expect the tokens to be explicitly redacted.
context.Request.Token = null;
}
return ValueTask.CompletedTask;
}
/// <summary>
/// This is invoked when extracting the auth context for a client request.
/// </summary>
@@ -155,7 +220,7 @@ internal sealed class HideBackOfficeTokensHandler
return ValueTask.CompletedTask;
}
if (TryGetCookie(GetHttpContext(), AccessTokenCookieName, out var accessToken))
if (TryGetCookie(GetHttpContext(), _accessTokenCookieName, out var accessToken))
{
context.AccessToken = accessToken;
}
@@ -163,6 +228,7 @@ internal sealed class HideBackOfficeTokensHandler
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public void Handle(UserLogoutSuccessNotification notification)
{
HttpContext? httpContext = _httpContextAccessor.HttpContext;
@@ -174,8 +240,8 @@ internal sealed class HideBackOfficeTokensHandler
return;
}
RemoveCookie(httpContext, AccessTokenCookieName);
RemoveCookie(httpContext, RefreshTokenCookieName);
RemoveCookie(httpContext, _accessTokenCookieName);
RemoveCookie(httpContext, _refreshTokenCookieName);
}
private HttpContext GetHttpContext()
@@ -231,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,4 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -6,8 +6,18 @@ using Umbraco.Cms.Api.Common.Configuration;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
/// <summary>
/// Extension methods for <see cref="IMvcBuilder"/>.
/// </summary>
public static class MvcBuilderExtensions
{
/// <summary>
/// Adds named JSON serialization options to the MVC builder.
/// </summary>
/// <param name="builder">The MVC builder.</param>
/// <param name="settingsName">The name for the JSON options configuration.</param>
/// <param name="configure">The action to configure the JSON options.</param>
/// <returns>The MVC builder for method chaining.</returns>
public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder, string settingsName, Action<JsonOptions> configure)
{
builder.Services.Configure(settingsName, configure);
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using OpenIddict.Server;
using OpenIddict.Validation;
using Umbraco.Cms.Core;
@@ -6,12 +6,23 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
/// <summary>
/// Handles OpenIddict request processing to skip handling for non-authentication requests.
/// </summary>
/// <remarks>
/// This handler prevents OpenIddict from processing every request to the server,
/// limiting its scope to back-office and well-known OpenID Connect endpoints.
/// </remarks>
public class ProcessRequestContextHandler
: IOpenIddictServerHandler<OpenIddictServerEvents.ProcessRequestContext>, IOpenIddictValidationHandler<OpenIddictValidationEvents.ProcessRequestContext>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly string[] _pathsToHandle;
/// <summary>
/// Initializes a new instance of the <see cref="ProcessRequestContextHandler"/> class.
/// </summary>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
public ProcessRequestContextHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
@@ -21,6 +32,11 @@ public class ProcessRequestContextHandler
_pathsToHandle = [backOfficePathSegment, "/.well-known/openid-configuration", "/.well-known/jwks"];
}
/// <summary>
/// Handles the server process request context event.
/// </summary>
/// <param name="context">The process request context.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
public ValueTask HandleAsync(OpenIddictServerEvents.ProcessRequestContext context)
{
if (SkipOpenIddictHandlingForRequest())
@@ -31,6 +47,11 @@ public class ProcessRequestContextHandler
return ValueTask.CompletedTask;
}
/// <summary>
/// Handles the validation process request context event.
/// </summary>
/// <param name="context">The process request context.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
public ValueTask HandleAsync(OpenIddictValidationEvents.ProcessRequestContext context)
{
if (SkipOpenIddictHandlingForRequest())
@@ -7,8 +7,16 @@ using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
/// <summary>
/// Extension methods for <see cref="IUmbracoBuilder"/> to configure API services.
/// </summary>
public static class UmbracoBuilderApiExtensions
{
/// <summary>
/// Adds Umbraco API OpenAPI/Swagger UI services to the builder.
/// </summary>
/// <param name="builder">The Umbraco builder.</param>
/// <returns>The Umbraco builder for method chaining.</returns>
public static IUmbracoBuilder AddUmbracoApiOpenApiUI(this IUmbracoBuilder builder)
{
if (builder.Services.Any(x => !x.IsKeyedService && x.ImplementationType == typeof(OperationIdSelector)))
@@ -9,14 +9,26 @@ using Umbraco.Cms.Api.Common.Security;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Infrastructure.BackgroundJobs;
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.DistributedJobs;
using Umbraco.Cms.Core.Notifications;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
/// <summary>
/// Extension methods for <see cref="IUmbracoBuilder"/> to configure authentication services.
/// </summary>
public static class UmbracoBuilderAuthExtensions
{
/// <summary>
/// Adds OpenIddict authentication services for Umbraco APIs.
/// </summary>
/// <param name="builder">The Umbraco builder.</param>
/// <returns>The Umbraco builder for method chaining.</returns>
/// <remarks>
/// Configures OpenIddict with authorization code flow (with PKCE), client credentials flow,
/// reference tokens, and ASP.NET Core Data Protection for token encryption.
/// </remarks>
public static IUmbracoBuilder AddUmbracoOpenIddict(this IUmbracoBuilder builder)
{
if (builder.Services.Any(x => !x.IsKeyedService && x.ImplementationType == typeof(OpenIddictCleanupJob)) is false)
@@ -133,6 +145,12 @@ public static class UmbracoBuilderAuthExtensions
.UseSingletonHandler<HideBackOfficeTokensHandler>()
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest<OpenIddictServerEvents.ExtractTokenRequestContext>.Descriptor.Order + 1);
});
options.AddEventHandler<OpenIddictServerEvents.ExtractRevocationRequestContext>(configuration =>
{
configuration
.UseSingletonHandler<HideBackOfficeTokensHandler>()
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest<OpenIddictServerEvents.ExtractRevocationRequestContext>.Descriptor.Order + 1);
});
})
// Register the OpenIddict validation components.
@@ -33,4 +33,4 @@ public static class ActionDescriptorApiCommonExtensions
return mapToApiAttributes.SingleOrDefault()?.ApiName;
}
}
}
@@ -5,9 +5,16 @@ using Umbraco.Cms.Api.Common.Configuration;
namespace Umbraco.Extensions;
/// <summary>
/// Extension methods for <see cref="MethodInfo"/> to work with API-related attributes.
/// </summary>
public static class MethodInfoApiCommonExtensions
{
/// <summary>
/// Gets the API version values from <see cref="MapToApiVersionAttribute"/> applied to the method.
/// </summary>
/// <param name="methodInfo">The method info to inspect.</param>
/// <returns>A pipe-separated string of API version values.</returns>
public static string GetMapToApiVersionAttributeValue(this MethodInfo methodInfo)
{
MapToApiVersionAttribute[] mapToApis = methodInfo.GetCustomAttributes(typeof(MapToApiVersionAttribute), inherit: true).Cast<MapToApiVersionAttribute>().ToArray();
@@ -15,6 +22,11 @@ public static class MethodInfoApiCommonExtensions
return string.Join("|", mapToApis.SelectMany(x => x.Versions));
}
/// <summary>
/// Gets the API name from <see cref="MapToApiAttribute"/> applied to the method's declaring type.
/// </summary>
/// <param name="methodInfo">The method info to inspect.</param>
/// <returns>The API name if the attribute is present; otherwise, <c>null</c>.</returns>
public static string? GetMapToApiAttributeValue(this MethodInfo methodInfo)
{
MapToApiAttribute[] mapToApis = (methodInfo.DeclaringType?.GetCustomAttributes(typeof(MapToApiAttribute), inherit: true) ?? Array.Empty<object>()).Cast<MapToApiAttribute>().ToArray();
@@ -22,6 +34,15 @@ public static class MethodInfoApiCommonExtensions
return mapToApis.SingleOrDefault()?.ApiName;
}
/// <summary>
/// Determines whether the method's declaring type has a <see cref="MapToApiAttribute"/> with the specified API name.
/// </summary>
/// <param name="methodInfo">The method info to inspect.</param>
/// <param name="apiName">The API name to check for.</param>
/// <returns>
/// <c>true</c> if the attribute is present and matches the specified API name,
/// or if the attribute is not present and the API name matches the default API name; otherwise, <c>false</c>.
/// </returns>
public static bool HasMapToApiAttribute(this MethodInfo methodInfo, string apiName)
{
var value = methodInfo.GetMapToApiAttributeValue();
@@ -1,9 +1,19 @@
namespace Umbraco.Cms.Api.Common.Filters;
namespace Umbraco.Cms.Api.Common.Filters;
/// <summary>
/// Attribute used to specify the named JSON serialization options for a controller.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class JsonOptionsNameAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="JsonOptionsNameAttribute"/> class.
/// </summary>
/// <param name="jsonOptionsName">The name of the JSON options configuration to use.</param>
public JsonOptionsNameAttribute(string jsonOptionsName) => JsonOptionsName = jsonOptionsName;
/// <summary>
/// Gets the name of the JSON options configuration.
/// </summary>
public string JsonOptionsName { get; }
}
@@ -1,10 +1,18 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Api.Common.Filters;
namespace Umbraco.Cms.Api.Common.Json;
/// <summary>
/// Extension methods for <see cref="HttpContext"/> related to JSON serialization.
/// </summary>
public static class HttpContextJsonExtensions
{
/// <summary>
/// Gets the named JSON options configuration for the current endpoint.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>The JSON options name if specified via <see cref="JsonOptionsNameAttribute"/>; otherwise, <c>null</c>.</returns>
public static string? CurrentJsonOptionsName(this HttpContext context)
=> context.GetEndpoint()?.Metadata.GetMetadata<JsonOptionsNameAttribute>()?.JsonOptionsName;
}
@@ -1,20 +1,31 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;
namespace Umbraco.Cms.Api.Common.Json;
/// <summary>
/// A JSON input formatter that only processes requests for endpoints with matching named JSON options.
/// </summary>
internal sealed class NamedSystemTextJsonInputFormatter : SystemTextJsonInputFormatter
{
private readonly string _jsonOptionsName;
/// <summary>
/// Initializes a new instance of the <see cref="NamedSystemTextJsonInputFormatter"/> class.
/// </summary>
/// <param name="jsonOptionsName">The name of the JSON options configuration this formatter handles.</param>
/// <param name="options">The JSON options.</param>
/// <param name="logger">The logger.</param>
public NamedSystemTextJsonInputFormatter(string jsonOptionsName, JsonOptions options, ILogger<NamedSystemTextJsonInputFormatter> logger)
: base(options, logger) =>
_jsonOptionsName = jsonOptionsName;
/// <inheritdoc/>
public override bool CanRead(InputFormatterContext context)
=> context.HttpContext.CurrentJsonOptionsName() == _jsonOptionsName && base.CanRead(context);
/// <inheritdoc/>
public override async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
try
@@ -1,17 +1,26 @@
using System.Text.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
namespace Umbraco.Cms.Api.Common.Json;
/// <summary>
/// A JSON output formatter that only processes responses for endpoints with matching named JSON options.
/// </summary>
internal sealed class NamedSystemTextJsonOutputFormatter : SystemTextJsonOutputFormatter
{
private readonly string _jsonOptionsName;
/// <summary>
/// Initializes a new instance of the <see cref="NamedSystemTextJsonOutputFormatter"/> class.
/// </summary>
/// <param name="jsonOptionsName">The name of the JSON options configuration this formatter handles.</param>
/// <param name="jsonSerializerOptions">The JSON serializer options.</param>
public NamedSystemTextJsonOutputFormatter(string jsonOptionsName, JsonSerializerOptions jsonSerializerOptions) : base(jsonSerializerOptions)
{
_jsonOptionsName = jsonOptionsName;
}
/// <inheritdoc/>
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
=> context.HttpContext.CurrentJsonOptionsName() == _jsonOptionsName && base.CanWriteResult(context);
}
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
@@ -16,6 +16,13 @@ public sealed class EmptyCreatedAtActionResult : ActionResult
private readonly object _routeValues;
private readonly string _resourceIdentifier;
/// <summary>
/// Initializes a new instance of the <see cref="EmptyCreatedAtActionResult"/> class.
/// </summary>
/// <param name="actionName">The name of the action to generate the URL for.</param>
/// <param name="controllerName">The name of the controller to generate the URL for.</param>
/// <param name="routeValues">The route values to use for URL generation.</param>
/// <param name="resourceIdentifier">The identifier of the created resource.</param>
public EmptyCreatedAtActionResult(string actionName, string controllerName, object routeValues, string resourceIdentifier)
{
_actionName = actionName;
@@ -24,6 +31,7 @@ public sealed class EmptyCreatedAtActionResult : ActionResult
_resourceIdentifier = resourceIdentifier;
}
/// <inheritdoc/>
public override void ExecuteResult(ActionContext context)
{
ArgumentNullException.ThrowIfNull(context);
@@ -1,4 +1,4 @@
using System.Reflection;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json.Nodes;
using Microsoft.OpenApi;
@@ -6,8 +6,16 @@ using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// A schema filter that converts enum schemas to string type with enum member names.
/// </summary>
/// <remarks>
/// This filter ensures enums are represented as strings in the OpenAPI schema,
/// using <see cref="EnumMemberAttribute"/> values when available.
/// </remarks>
public class EnumSchemaFilter : ISchemaFilter
{
/// <inheritdoc/>
public void Apply(IOpenApiSchema model, SchemaFilterContext context)
{
if (model is not OpenApiSchema schema || context.Type.IsEnum is false)
@@ -2,9 +2,22 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Defines a handler for generating OpenAPI operation IDs.
/// </summary>
public interface IOperationIdHandler
{
bool CanHandle(ApiDescription apiDescription);
/// <summary>
/// Determines whether this handler can generate an operation ID for the specified API description.
/// </summary>
/// <param name="apiDescription">The API description to check.</param>
/// <returns><c>true</c> if this handler can handle the API description; otherwise, <c>false</c>.</returns>
bool CanHandle(ApiDescription apiDescription);
string Handle(ApiDescription apiDescription);
/// <summary>
/// Generates an operation ID for the specified API description.
/// </summary>
/// <param name="apiDescription">The API description to generate an operation ID for.</param>
/// <returns>The generated operation ID.</returns>
string Handle(ApiDescription apiDescription);
}
@@ -3,7 +3,15 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Defines a selector for choosing operation IDs from registered handlers.
/// </summary>
public interface IOperationIdSelector
{
/// <summary>
/// Selects an operation ID for the specified API description.
/// </summary>
/// <param name="apiDescription">The API description to generate an operation ID for.</param>
/// <returns>The operation ID, or <c>null</c> if none could be determined.</returns>
string? OperationId(ApiDescription apiDescription);
}
@@ -1,8 +1,21 @@
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Defines a handler for generating OpenAPI schema IDs.
/// </summary>
public interface ISchemaIdHandler
{
/// <summary>
/// Determines whether this handler can generate a schema ID for the specified type.
/// </summary>
/// <param name="type">The type to check.</param>
/// <returns><c>true</c> if this handler can handle the type; otherwise, <c>false</c>.</returns>
bool CanHandle(Type type);
/// <summary>
/// Generates a schema ID for the specified type.
/// </summary>
/// <param name="type">The type to generate a schema ID for.</param>
/// <returns>The generated schema ID.</returns>
string Handle(Type type);
}
@@ -1,6 +1,14 @@
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Defines a selector for choosing schema IDs from registered handlers.
/// </summary>
public interface ISchemaIdSelector
{
/// <summary>
/// Selects a schema ID for the specified type.
/// </summary>
/// <param name="type">The type to generate a schema ID for.</param>
/// <returns>The schema ID.</returns>
string SchemaId(Type type);
}
@@ -1,8 +1,22 @@
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Defines a handler for discovering sub-types for polymorphic OpenAPI schemas.
/// </summary>
public interface ISubTypesHandler
{
/// <summary>
/// Determines whether this handler can discover sub-types for the specified type and document.
/// </summary>
/// <param name="type">The type to check.</param>
/// <param name="documentName">The OpenAPI document name.</param>
/// <returns><c>true</c> if this handler can handle the type; otherwise, <c>false</c>.</returns>
bool CanHandle(Type type, string documentName);
/// <summary>
/// Discovers sub-types for the specified type.
/// </summary>
/// <param name="type">The type to discover sub-types for.</param>
/// <returns>An enumerable of discovered sub-types.</returns>
IEnumerable<Type> Handle(Type type);
}
@@ -1,6 +1,14 @@
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Defines a selector for choosing sub-types from registered handlers.
/// </summary>
public interface ISubTypesSelector
{
/// <summary>
/// Selects sub-types for the specified type for polymorphic OpenAPI schema generation.
/// </summary>
/// <param name="type">The type to find sub-types for.</param>
/// <returns>An enumerable of sub-types.</returns>
IEnumerable<Type> SubTypes(Type type);
}
@@ -1,4 +1,4 @@
using Microsoft.OpenApi;
using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Extensions;
@@ -11,8 +11,13 @@ public class MimeTypeDocumentFilter : IDocumentFilter
{
private readonly string _documentName;
/// <summary>
/// Initializes a new instance of the <see cref="MimeTypeDocumentFilter"/> class.
/// </summary>
/// <param name="documentName">The name of the OpenAPI document to filter.</param>
public MimeTypeDocumentFilter(string documentName) => _documentName = documentName;
/// <inheritdoc/>
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
if (context.DocumentName != _documentName)
@@ -24,7 +29,7 @@ public class MimeTypeDocumentFilter : IDocumentFilter
.SelectMany(path => path.Value.Operations?.Values ?? Enumerable.Empty<OpenApiOperation>())
.ToArray();
void RemoveUnwantedMimeTypes(IDictionary<string, OpenApiMediaType>? content)
static void RemoveUnwantedMimeTypes(IDictionary<string, OpenApiMediaType>? content)
{
if (content is null || content.ContainsKey("application/json") is false)
{
@@ -6,14 +6,24 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.OpenApi;
// NOTE: Left unsealed on purpose, so it is extendable.
/// <summary>
/// Default handler for generating OpenAPI operation IDs for Umbraco API controllers.
/// </summary>
/// <remarks>
/// Left unsealed on purpose, so it is extendable by consuming APIs.
/// </remarks>
public class OperationIdHandler : IOperationIdHandler
{
private readonly ApiVersioningOptions _apiVersioningOptions;
/// <summary>
/// Initializes a new instance of the <see cref="OperationIdHandler"/> class.
/// </summary>
/// <param name="apiVersioningOptions">The API versioning options.</param>
public OperationIdHandler(IOptions<ApiVersioningOptions> apiVersioningOptions)
=> _apiVersioningOptions = apiVersioningOptions.Value;
/// <inheritdoc/>
public bool CanHandle(ApiDescription apiDescription)
{
if (apiDescription.ActionDescriptor is not ControllerActionDescriptor controllerActionDescriptor)
@@ -24,9 +34,16 @@ public class OperationIdHandler : IOperationIdHandler
return CanHandle(apiDescription, controllerActionDescriptor);
}
/// <summary>
/// Determines whether this handler can process the API description based on the controller namespace.
/// </summary>
/// <param name="apiDescription">The API description.</param>
/// <param name="controllerActionDescriptor">The controller action descriptor.</param>
/// <returns><c>true</c> if the controller is in an Umbraco.Cms.Api namespace; otherwise, <c>false</c>.</returns>
protected virtual bool CanHandle(ApiDescription apiDescription, ControllerActionDescriptor controllerActionDescriptor)
=> controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith("Umbraco.Cms.Api") is true;
/// <inheritdoc/>
public virtual string Handle(ApiDescription apiDescription)
=> UmbracoOperationId(apiDescription);
@@ -1,4 +1,4 @@
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace Umbraco.Cms.Api.Common.OpenApi;
@@ -3,19 +3,30 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Selects an operation ID for an API description using registered handlers.
/// </summary>
public class OperationIdSelector : IOperationIdSelector
{
private readonly IEnumerable<IOperationIdHandler> _operationIdHandlers;
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
/// <summary>
/// Initializes a new instance of the <see cref="OperationIdSelector"/> class.
/// </summary>
[Obsolete("Use non-obsolete constructor. Scheduled for removal in Umbraco 18.")]
public OperationIdSelector()
: this(Enumerable.Empty<IOperationIdHandler>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OperationIdSelector"/> class.
/// </summary>
/// <param name="operationIdHandlers">The registered operation ID handlers.</param>
public OperationIdSelector(IEnumerable<IOperationIdHandler> operationIdHandlers)
=> _operationIdHandlers = operationIdHandlers;
/// <inheritdoc/>
public virtual string? OperationId(ApiDescription apiDescription)
{
IOperationIdHandler? handler = _operationIdHandlers.FirstOrDefault(h => h.CanHandle(apiDescription));
@@ -1,4 +1,4 @@
using Microsoft.OpenApi;
using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Common.OpenApi;
@@ -10,9 +10,14 @@ public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter
{
private readonly string _documentName;
/// <summary>
/// Initializes a new instance of the <see cref="RemoveSecuritySchemesDocumentFilter"/> class.
/// </summary>
/// <param name="documentName">The name of the OpenAPI document to filter.</param>
public RemoveSecuritySchemesDocumentFilter(string documentName)
=> _documentName = documentName;
/// <inheritdoc/>
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
if (context.DocumentName != _documentName)
@@ -3,12 +3,20 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.OpenApi;
// NOTE: Left unsealed on purpose, so it is extendable.
/// <summary>
/// Default handler for generating OpenAPI schema IDs for Umbraco types.
/// </summary>
/// <remarks>
/// Left unsealed on purpose, so it is extendable by consuming APIs.
/// Adds "Model" suffix to avoid TypeScript name clashes and removes invalid characters.
/// </remarks>
public class SchemaIdHandler : ISchemaIdHandler
{
/// <inheritdoc/>
public virtual bool CanHandle(Type type)
=> type.Namespace?.StartsWith("Umbraco.Cms") is true;
/// <inheritdoc/>
public virtual string Handle(Type type)
=> UmbracoSchemaId(type);
@@ -1,12 +1,20 @@
namespace Umbraco.Cms.Api.Common.OpenApi;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Selects a schema ID for a type using registered handlers.
/// </summary>
public class SchemaIdSelector : ISchemaIdSelector
{
private readonly IEnumerable<ISchemaIdHandler> _schemaIdHandlers;
/// <summary>
/// Initializes a new instance of the <see cref="SchemaIdSelector"/> class.
/// </summary>
/// <param name="schemaIdHandlers">The registered schema ID handlers.</param>
public SchemaIdSelector(IEnumerable<ISchemaIdHandler> schemaIdHandlers)
=> _schemaIdHandlers = schemaIdHandlers;
/// <inheritdoc/>
public virtual string SchemaId(Type type)
{
ISchemaIdHandler? handler = _schemaIdHandlers.FirstOrDefault(h => h.CanHandle(type));
@@ -2,19 +2,33 @@ using Umbraco.Cms.Api.Common.Serialization;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Default handler for discovering sub-types for polymorphic OpenAPI schemas.
/// </summary>
public class SubTypesHandler : ISubTypesHandler
{
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
/// <summary>
/// Initializes a new instance of the <see cref="SubTypesHandler"/> class.
/// </summary>
/// <param name="umbracoJsonTypeInfoResolver">The JSON type info resolver for finding sub-types.</param>
public SubTypesHandler(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
=> _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
/// <summary>
/// Determines whether this handler can process the specified type based on namespace.
/// </summary>
/// <param name="type">The type to check.</param>
/// <returns><c>true</c> if the type is in an Umbraco.Cms namespace; otherwise, <c>false</c>.</returns>
protected virtual bool CanHandle(Type type)
=> type.Namespace?.StartsWith("Umbraco.Cms") is true;
/// <inheritdoc/>
public virtual bool CanHandle(Type type, string documentName)
=> CanHandle(type);
/// <inheritdoc/>
public virtual IEnumerable<Type> Handle(Type type)
=> _umbracoJsonTypeInfoResolver.FindSubTypes(type);
}
@@ -8,6 +8,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Selects sub-types for polymorphic OpenAPI schemas using registered handlers.
/// </summary>
public class SubTypesSelector : ISubTypesSelector
{
private readonly IHostingEnvironment _hostingEnvironment;
@@ -15,6 +18,13 @@ public class SubTypesSelector : ISubTypesSelector
private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers;
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
/// <summary>
/// Initializes a new instance of the <see cref="SubTypesSelector"/> class.
/// </summary>
/// <param name="hostingEnvironment">The hosting environment.</param>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
/// <param name="subTypeHandlers">The registered sub-type handlers.</param>
/// <param name="umbracoJsonTypeInfoResolver">The JSON type info resolver for finding sub-types.</param>
public SubTypesSelector(
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor httpContextAccessor,
@@ -27,6 +37,7 @@ public class SubTypesSelector : ISubTypesSelector
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}
/// <inheritdoc/>
public IEnumerable<Type> SubTypes(Type type)
{
var backOfficePath = _hostingEnvironment.GetBackOfficePath();
@@ -35,8 +46,7 @@ public class SubTypesSelector : ISubTypesSelector
if (_httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments(swaggerPath) ?? false)
{
// Split the path into segments
var segments = _httpContextAccessor.HttpContext.Request.Path.Value!
.Substring(swaggerPath.Length)
var segments = _httpContextAccessor.HttpContext.Request.Path.Value![swaggerPath.Length..]
.TrimStart(Constants.CharArrays.ForwardSlash)
.Split(Constants.CharArrays.ForwardSlash);
@@ -13,8 +13,15 @@ using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Pipeline filter that configures Swagger/OpenAPI endpoints for Umbraco APIs.
/// </summary>
public class SwaggerRouteTemplatePipelineFilter : UmbracoPipelineFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="SwaggerRouteTemplatePipelineFilter"/> class.
/// </summary>
/// <param name="name">The name of the pipeline filter.</param>
public SwaggerRouteTemplatePipelineFilter(string name)
: base(name)
=> PostPipeline = PostPipelineAction;
@@ -36,15 +43,36 @@ public class SwaggerRouteTemplatePipelineFilter : UmbracoPipelineFilter
applicationBuilder.UseSwaggerUI(swaggerUiOptions => SwaggerUiConfiguration(swaggerUiOptions, swaggerGenOptions.Value, applicationBuilder));
}
/// <summary>
/// Determines whether Swagger is enabled for the application.
/// </summary>
/// <param name="applicationBuilder">The application builder.</param>
/// <returns><c>true</c> if Swagger is enabled; otherwise, <c>false</c>.</returns>
protected virtual bool SwaggerIsEnabled(IApplicationBuilder applicationBuilder)
=> applicationBuilder.ApplicationServices.GetRequiredService<IWebHostEnvironment>().IsProduction() is false;
/// <summary>
/// Gets the route template for Swagger JSON endpoints.
/// </summary>
/// <param name="applicationBuilder">The application builder.</param>
/// <returns>The Swagger route template.</returns>
protected virtual string SwaggerRouteTemplate(IApplicationBuilder applicationBuilder)
=> $"{GetBackOfficePath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger/{{documentName}}/swagger.json";
/// <summary>
/// Gets the route prefix for the Swagger UI.
/// </summary>
/// <param name="applicationBuilder">The application builder.</param>
/// <returns>The Swagger UI route prefix.</returns>
protected virtual string SwaggerUiRoutePrefix(IApplicationBuilder applicationBuilder)
=> $"{GetBackOfficePath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger";
/// <summary>
/// Configures the Swagger UI options.
/// </summary>
/// <param name="swaggerUiOptions">The Swagger UI options to configure.</param>
/// <param name="swaggerGenOptions">The Swagger generation options.</param>
/// <param name="applicationBuilder">The application builder.</param>
protected virtual void SwaggerUiConfiguration(
SwaggerUIOptions swaggerUiOptions,
SwaggerGenOptions swaggerGenOptions,
@@ -4,30 +4,64 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.Rendering;
/// <summary>
/// Implements output expansion strategy for element-only rendering in the Delivery API.
/// </summary>
/// <remarks>
/// This strategy handles the expansion and filtering of properties when rendering content
/// through the Delivery API based on expand and fields query parameters.
/// </remarks>
public class ElementOnlyOutputExpansionStrategy : IOutputExpansionStrategy
{
/// <summary>
/// The parameter value indicating all properties should be included.
/// </summary>
protected const string All = "$all";
/// <summary>
/// The parameter value indicating no properties should be included.
/// </summary>
protected const string None = "";
/// <summary>
/// The name of the expand query parameter.
/// </summary>
protected const string ExpandParameterName = "expand";
/// <summary>
/// The name of the fields query parameter.
/// </summary>
protected const string FieldsParameterName = "fields";
private readonly IApiPropertyRenderer _propertyRenderer;
/// <summary>
/// Gets the stack of expand property nodes for tracking nested expansions.
/// </summary>
protected Stack<Node?> ExpandProperties { get; } = new();
/// <summary>
/// Gets the stack of include property nodes for tracking nested field selections.
/// </summary>
protected Stack<Node?> IncludeProperties { get; } = new();
/// <summary>
/// Initializes a new instance of the <see cref="ElementOnlyOutputExpansionStrategy"/> class.
/// </summary>
/// <param name="propertyRenderer">The property renderer for converting property values.</param>
public ElementOnlyOutputExpansionStrategy(
IApiPropertyRenderer propertyRenderer)
{
_propertyRenderer = propertyRenderer;
}
/// <inheritdoc/>
public virtual IDictionary<string, object?> MapContentProperties(IPublishedContent content)
=> content.ItemType == PublishedItemType.Content
? MapProperties(content.Properties)
: throw new ArgumentException($"Invalid item type. This method can only be used with item type {nameof(PublishedItemType.Content)}, got: {content.ItemType}");
/// <inheritdoc/>
public virtual IDictionary<string, object?> MapMediaProperties(IPublishedContent media, bool skipUmbracoProperties = true)
{
if (media.ItemType != PublishedItemType.Media)
@@ -45,6 +79,7 @@ public class ElementOnlyOutputExpansionStrategy : IOutputExpansionStrategy
: new Dictionary<string, object?>();
}
/// <inheritdoc/>
public virtual IDictionary<string, object?> MapElementProperties(IPublishedElement element)
=> MapProperties(element.Properties, true);
@@ -87,12 +122,27 @@ public class ElementOnlyOutputExpansionStrategy : IOutputExpansionStrategy
private object? GetPropertyValue(IPublishedProperty property)
=> _propertyRenderer.GetPropertyValue(property, ExpandProperties.Peek() is not null);
/// <summary>
/// Represents a node in the parsed expand/fields parameter tree structure.
/// </summary>
protected sealed class Node
{
/// <summary>
/// Gets the key of this node.
/// </summary>
public string Key { get; private set; } = string.Empty;
/// <summary>
/// Gets the child nodes of this node.
/// </summary>
public List<Node> Items { get; } = new();
/// <summary>
/// Parses an expand/fields parameter value into a node tree structure.
/// </summary>
/// <param name="value">The parameter value to parse.</param>
/// <returns>The root node of the parsed tree.</returns>
/// <exception cref="ArgumentException">Thrown when the value has invalid syntax.</exception>
public static Node Parse(string value)
{
// verify that there are as many start brackets as there are end brackets
@@ -2,35 +2,77 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Common.Security;
/// <summary>
/// Contains OAuth/OpenID Connect endpoint paths for Umbraco APIs.
/// </summary>
public static class Paths
{
/// <summary>
/// Contains endpoint paths for back-office authentication.
/// </summary>
public static class BackOfficeApi
{
/// <summary>
/// The base endpoint template for back-office security endpoints.
/// </summary>
public const string EndpointTemplate = "security/back-office";
/// <summary>
/// The authorization endpoint path.
/// </summary>
public static readonly string AuthorizationEndpoint = EndpointPath($"{EndpointTemplate}/authorize");
/// <summary>
/// The token endpoint path.
/// </summary>
public static readonly string TokenEndpoint = EndpointPath($"{EndpointTemplate}/token");
/// <summary>
/// The logout/sign-out endpoint path.
/// </summary>
public static readonly string LogoutEndpoint = EndpointPath($"{EndpointTemplate}/signout");
/// <summary>
/// The token revocation endpoint path.
/// </summary>
public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke");
private static string EndpointPath(string relativePath) => $"/umbraco{Constants.Web.ManagementApiPath}v1/{relativePath}";
}
/// <summary>
/// Contains endpoint paths for member authentication.
/// </summary>
public static class MemberApi
{
/// <summary>
/// The base endpoint template for member security endpoints.
/// </summary>
public const string EndpointTemplate = "security/member";
/// <summary>
/// The authorization endpoint path.
/// </summary>
public static readonly string AuthorizationEndpoint = EndpointPath($"{EndpointTemplate}/authorize");
/// <summary>
/// The token endpoint path.
/// </summary>
public static readonly string TokenEndpoint = EndpointPath($"{EndpointTemplate}/token");
/// <summary>
/// The logout/sign-out endpoint path.
/// </summary>
public static readonly string LogoutEndpoint = EndpointPath($"{EndpointTemplate}/signout");
/// <summary>
/// The token revocation endpoint path.
/// </summary>
public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke");
/// <summary>
/// The user info endpoint path.
/// </summary>
public static readonly string UserinfoEndpoint = EndpointPath($"{EndpointTemplate}/userinfo");
// NOTE: we're NOT using /api/v1.0/ here because it will clash with the Delivery API docs
@@ -2,9 +2,22 @@ using System.Text.Json.Serialization.Metadata;
namespace Umbraco.Cms.Api.Common.Serialization;
/// <summary>
/// Extends <see cref="IJsonTypeInfoResolver"/> with Umbraco-specific type resolution for polymorphic JSON serialization.
/// </summary>
public interface IUmbracoJsonTypeInfoResolver : IJsonTypeInfoResolver
{
/// <summary>
/// Finds all sub-types of the specified type for polymorphic serialization.
/// </summary>
/// <param name="type">The base type to find sub-types for.</param>
/// <returns>An enumerable of sub-types.</returns>
IEnumerable<Type> FindSubTypes(Type type);
/// <summary>
/// Gets the type discriminator value used for polymorphic serialization.
/// </summary>
/// <param name="type">The type to get the discriminator value for.</param>
/// <returns>The discriminator value, or <c>null</c> if not applicable.</returns>
string? GetTypeDiscriminatorValue(Type type);
}
@@ -8,14 +8,26 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.Serialization;
/// <summary>
/// Implements JSON type info resolution for Umbraco with support for polymorphic serialization.
/// </summary>
/// <remarks>
/// This resolver discovers sub-types of interfaces for polymorphic JSON serialization,
/// caching results for performance. It also handles type discriminator values for OpenAPI schema generation.
/// </remarks>
public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, IUmbracoJsonTypeInfoResolver
{
private readonly ITypeFinder _typeFinder;
private readonly ConcurrentDictionary<Type, ISet<Type>> _subTypesCache = new ConcurrentDictionary<Type, ISet<Type>>();
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoJsonTypeInfoResolver"/> class.
/// </summary>
/// <param name="typeFinder">The type finder for discovering sub-types.</param>
public UmbracoJsonTypeInfoResolver(ITypeFinder typeFinder)
=> _typeFinder = typeFinder;
/// <inheritdoc/>
public IEnumerable<Type> FindSubTypes(Type type)
{
JsonDerivedTypeAttribute[] explicitJsonDerivedTypes = type
@@ -44,6 +56,7 @@ public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, I
return result;
}
/// <inheritdoc/>
public string? GetTypeDiscriminatorValue(Type type)
{
JsonDerivedTypeAttribute? jsonDerivedTypeAttribute = type
@@ -62,6 +75,7 @@ public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, I
return typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? type.Name : null;
}
/// <inheritdoc/>
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo result = base.GetTypeInfo(type, options);
@@ -1,14 +1,28 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace Umbraco.Cms.Api.Common.ViewModels.Pagination;
/// <summary>
/// Represents a paged collection of items with total count.
/// </summary>
/// <typeparam name="T">The type of items in the collection.</typeparam>
public class PagedViewModel<T>
{
/// <summary>
/// Gets or sets the total number of items available.
/// </summary>
[Required]
public long Total { get; set; }
/// <summary>
/// Gets or sets the items in the current page.
/// </summary>
[Required]
public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
/// <summary>
/// Creates an empty paged view model.
/// </summary>
/// <returns>An empty <see cref="PagedViewModel{T}"/> instance.</returns>
public static PagedViewModel<T> Empty() => new();
}
@@ -2,16 +2,33 @@ using System.ComponentModel.DataAnnotations;
namespace Umbraco.Cms.Api.Common.ViewModels.Pagination;
/// <summary>
/// Represents a subset of items with counts of items before and after the subset.
/// </summary>
/// <typeparam name="T">The type of items in the collection.</typeparam>
public class SubsetViewModel<T>
{
/// <summary>
/// Gets or sets the total number of items before this subset.
/// </summary>
[Required]
public long TotalBefore { get; set; }
/// <summary>
/// Gets or sets the total number of items after this subset.
/// </summary>
[Required]
public long TotalAfter { get; set; }
/// <summary>
/// Gets or sets the items in the subset.
/// </summary>
[Required]
public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
/// <summary>
/// Creates an empty subset view model.
/// </summary>
/// <returns>An empty <see cref="SubsetViewModel{T}"/> instance.</returns>
public static SubsetViewModel<T> Empty() => new();
}
@@ -7,6 +7,7 @@ using Umbraco.Cms.Api.Delivery.Configuration;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Features;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Controllers;
namespace Umbraco.Cms.Api.Delivery.Controllers;
@@ -14,6 +15,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers;
[JsonOptionsName(Constants.JsonOptionsNames.DeliveryApi)]
[MapToApi(DeliveryApiConfiguration.ApiName)]
[Authorize(Policy = AuthorizationPolicies.UmbracoFeatureEnabled)]
[MaintenanceModeActionFilter]
public abstract class DeliveryApiControllerBase : Controller, IUmbracoFeature
{
protected string DecodePath(string path)
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Builder;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Extensions;
/// <summary>
/// <see cref="IApplicationBuilder" /> extensions for the Umbraco Delivery API.
/// </summary>
public static class DeliveryApiApplicationBuilderExtensions
{
/// <summary>
/// Sets up routes for the Umbraco Delivery API.
/// </summary>
/// <remarks>
/// This method maps attribute-routed controllers including the Delivery API endpoints.
/// Call this when using <c>AddDeliveryApi()</c> without <c>AddBackOffice()</c>, as the
/// backoffice endpoints normally handle the controller mapping.
/// </remarks>
/// <param name="builder">The Umbraco endpoint builder context.</param>
/// <returns>The <see cref="IUmbracoEndpointBuilderContext" /> for chaining.</returns>
public static IUmbracoEndpointBuilderContext UseDeliveryApiEndpoints(this IUmbracoEndpointBuilderContext builder)
{
builder.EndpointRouteBuilder.MapControllers();
return builder;
}
}
@@ -22,7 +22,6 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Infrastructure.Security;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
@@ -30,8 +29,20 @@ namespace Umbraco.Extensions;
public static class UmbracoBuilderExtensions
{
/// <summary>
/// Add services for the Umbraco Delivery API (headless content delivery).
/// </summary>
/// <remarks>
/// This method assumes that either <c>AddBackOffice()</c> or <c>AddCore()</c> has already been called.
/// It registers Delivery API-specific services such as controllers, output caching, and member authentication.
/// </remarks>
/// <param name="builder">The Umbraco builder.</param>
/// <returns>The Umbraco builder.</returns>
public static IUmbracoBuilder AddDeliveryApi(this IUmbracoBuilder builder)
{
// Delivery API supports member authentication for protected content
builder.AddMembersIdentity();
builder.Services.AddScoped<IRequestStartItemProvider, RequestStartItemProvider>();
builder.Services.AddScoped<RequestContextOutputExpansionStrategy>();
builder.Services.AddScoped<RequestContextOutputExpansionStrategyV2>();
@@ -4,7 +4,11 @@ using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Filters;
internal sealed class DeliveryApiAccessAttribute : TypeFilterAttribute
/// <summary>
/// An action filter attribute that verifies public or preview access to the Delivery API, returning
/// a <c>401 Unauthorized</c> result if access is denied.
/// </summary>
public sealed class DeliveryApiAccessAttribute : TypeFilterAttribute
{
public DeliveryApiAccessAttribute()
: base(typeof(DeliveryApiAccessFilter))
@@ -4,7 +4,11 @@ using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Filters;
internal sealed class DeliveryApiMediaAccessAttribute : TypeFilterAttribute
/// <summary>
/// An action filter attribute that verifies public access to the media Delivery API, returning
/// a <c>401 Unauthorized</c> result if access is denied.
/// </summary>
public sealed class DeliveryApiMediaAccessAttribute : TypeFilterAttribute
{
public DeliveryApiMediaAccessAttribute()
: base(typeof(DeliveryApiMediaAccessFilter))
@@ -1,12 +1,13 @@
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Examine;
namespace Umbraco.Cms.Api.Delivery.Indexing.Selectors;
public sealed class AncestorsSelectorIndexer : IContentIndexHandler
{
// NOTE: "id" is a reserved field name
internal const string FieldName = "itemId";
internal const string FieldName = UmbracoExamineFieldNames.DeliveryApiContentIndex.ItemId;
public IEnumerable<IndexFieldValue> GetFieldValues(IContent content, string? culture)
=> new[] { new IndexFieldValue { FieldName = FieldName, Values = new object[] { content.Key } } };
@@ -2,7 +2,10 @@ using Umbraco.Cms.Web.Common.Routing;
namespace Umbraco.Cms.Api.Delivery.Routing;
internal sealed class VersionedDeliveryApiRouteAttribute : BackOfficeRouteAttribute
/// <summary>
/// A routing attribute that ensures consistent Delivery API endpoint paths.
/// </summary>
public sealed class VersionedDeliveryApiRouteAttribute : BackOfficeRouteAttribute
{
public VersionedDeliveryApiRouteAttribute(string template)
: base($"delivery/api/v{{version:apiVersion}}/{template.TrimStart('/')}")
@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Api.Delivery.Services;
/// </summary>
internal sealed class ApiContentQueryProvider : IApiContentQueryProvider
{
private const string ItemIdFieldName = "itemId";
private const string ItemIdFieldName = UmbracoExamineFieldNames.DeliveryApiContentIndex.ItemId;
private readonly IExamineManager _examineManager;
private readonly ILogger<ApiContentQueryProvider> _logger;
private readonly ApiContentQuerySelectorBuilder _selectorBuilder;
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Api.Management.Controllers.Document.GetPublicAccessDocumentController.GetPublicAccess(System.Threading.CancellationToken,System.Guid)</Target>
<Left>lib/net10.0/Umbraco.Cms.Api.Management.dll</Left>
<Right>lib/net10.0/Umbraco.Cms.Api.Management.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Api.Management.Controllers.UrlSegment.ResizeImagingController.Urls(System.Collections.Generic.HashSet{System.Guid},System.Int32,System.Int32,System.Nullable{Umbraco.Cms.Core.Models.ImageCropMode})</Target>
<Left>lib/net10.0/Umbraco.Cms.Api.Management.dll</Left>
<Right>lib/net10.0/Umbraco.Cms.Api.Management.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
@@ -140,15 +140,20 @@ public class ConfigureBackOfficeCookieOptions : IConfigureNamedOptions<CookieAut
await securityStampValidator.ValidateAsync(ctx);
// We have to manually specify Issued and Expires,
// because the SecurityStampValidator refreshes the principal every 30 minutes,
// When the principal is refreshed the Issued is update to time of refresh, however, the Expires remains unchanged
// When we then try and renew, the difference of issued and expires effectively becomes the new ExpireTimeSpan
// meaning we effectively lose 30 minutes of our ExpireTimeSpan for EVERY principal refresh if we don't
// https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/CookieAuthenticationHandler.cs#L115
ctx.Properties.IssuedUtc = _timeProvider.GetUtcNow();
ctx.Properties.ExpiresUtc = _timeProvider.GetUtcNow().Add(_globalSettings.TimeOut);
ctx.ShouldRenew = true;
// Only reset timestamps when a renewal was already triggered (by the SecurityStampValidator
// or by EnsureTicketRenewalIfKeepUserLoggedIn above).
// When the SecurityStampValidator refreshes the principal, it sets ShouldRenew but updates
// IssuedUtc without updating ExpiresUtc, causing the effective cookie lifetime to shrink
// with each validation. The manual reset here fixes that drift.
// IMPORTANT: Do NOT unconditionally set ShouldRenew or reset IssuedUtc - doing so prevents
// the SecurityStampValidator from ever exceeding its ValidationInterval during active use,
// which breaks AllowConcurrentLogins enforcement.
if (ctx.ShouldRenew)
{
DateTimeOffset now = _timeProvider.GetUtcNow();
ctx.Properties.IssuedUtc = now;
ctx.Properties.ExpiresUtc = now.Add(_globalSettings.TimeOut);
}
},
OnSigningIn = ctx =>
{
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Configuration;
/// <summary>
/// Used to configure <see cref="CookieAuthenticationOptions" /> for the back office "exposed" authentication type
/// </summary>
public class ConfigureBackOfficeExposedCookieOptions : IConfigureNamedOptions<CookieAuthenticationOptions>
{
private readonly SecuritySettings _securitySettings;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureBackOfficeExposedCookieOptions" /> class.
/// </summary>
/// <param name="securitySettings">The <see cref="SecuritySettings" /> options</param>
public ConfigureBackOfficeExposedCookieOptions(IOptions<SecuritySettings> securitySettings)
=> _securitySettings = securitySettings.Value;
/// <inheritdoc />
public void Configure(string? name, CookieAuthenticationOptions options)
{
if (name != Constants.Security.BackOfficeExposedAuthenticationType)
{
return;
}
Configure(options);
}
/// <inheritdoc />
public void Configure(CookieAuthenticationOptions options)
{
options.Cookie.Name = _securitySettings.AuthCookieName.IsNullOrWhiteSpace()
? Constants.Security.BackOfficeExposedCookieName
: $"{_securitySettings.AuthCookieName}{Constants.Security.BackOfficeExposedCookieNamePostfix}";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.SlidingExpiration = true;
}
}
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Web.Common.Security;
@@ -13,6 +13,11 @@ public class ConfigureBackOfficeSecurityStampValidatorOptions : IConfigureOption
private readonly SecuritySettings _securitySettings;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureBackOfficeSecurityStampValidatorOptions"/> class with the specified security settings and time provider.
/// </summary>
/// <param name="securitySettings">The <see cref="IOptions{SecuritySettings}"/> used to access security-related configuration options.</param>
/// <param name="timeProvider">The <see cref="TimeProvider"/> used for time-based operations.</param>
public ConfigureBackOfficeSecurityStampValidatorOptions(IOptions<SecuritySettings> securitySettings, TimeProvider timeProvider)
{
_timeProvider = timeProvider;
@@ -23,6 +28,6 @@ public class ConfigureBackOfficeSecurityStampValidatorOptions : IConfigureOption
public void Configure(BackOfficeSecurityStampValidatorOptions options)
{
options.TimeProvider = _timeProvider;
ConfigureSecurityStampOptions.ConfigureOptions(options, _securitySettings);
ConfigureSecurityStampOptions.ConfigureOptions(options, _securitySettings.GetUserAllowConcurrentLogins());
}
}
@@ -9,15 +9,30 @@ using Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Management.Configuration;
/// <summary>
/// Provides configuration for Swagger generation options specific to the Umbraco Management API.
/// This class is used to customize the Swagger documentation for the API endpoints.
/// </summary>
public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureUmbracoManagementApiSwaggerGenOptions"/> class.
/// </summary>
/// <param name="umbracoJsonTypeInfoResolver">An instance of <see cref="IUmbracoJsonTypeInfoResolver"/> used to resolve JSON type information for Umbraco.</param>
public ConfigureUmbracoManagementApiSwaggerGenOptions(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}
/// <summary>
/// Configures the <see cref="SwaggerGenOptions"/> for the Umbraco Management API.
/// Sets up the Swagger documentation, including API metadata, security definitions for OAuth2 authentication,
/// operation filters for response headers and security requirements, and schema filters for non-nullable properties.
/// Also configures polymorphism handling and discriminator properties for OpenAPI schemas.
/// </summary>
/// <param name="swaggerGenOptions">The <see cref="SwaggerGenOptions"/> instance to configure for the Management API.</param>
public void Configure(SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SwaggerDoc(
@@ -7,23 +7,9 @@ using Umbraco.Cms.Core.Hosting;
namespace Umbraco.Cms.Api.Management;
[BindProperties]
public class BackOfficeLoginModel
{
/// <summary>
/// Gets or sets the value of the "ReturnUrl" query parameter or defaults to the configured Umbraco directory.
/// </summary>
[FromQuery(Name = "ReturnUrl")]
public string? ReturnUrl { get; set; }
/// <summary>
/// The configured Umbraco directory.
/// </summary>
public string? UmbracoUrl { get; set; }
public bool UserIsAlreadyLoggedIn { get; set; }
}
/// <summary>
/// Provides endpoints for managing back office user authentication and login operations.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
[Route(LoginPath)]
public class BackOfficeLoginController : Controller
@@ -32,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)
@@ -41,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);
@@ -0,0 +1,26 @@
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
{
/// <summary>
/// Gets or sets the value of the "ReturnUrl" query parameter or defaults to the configured Umbraco directory.
/// </summary>
[FromQuery(Name = "ReturnUrl")]
public string? ReturnUrl { get; set; }
/// <summary>
/// The configured Umbraco directory.
/// </summary>
public string? UmbracoUrl { get; set; }
/// <summary>
/// Indicates whether the user is already logged in to the back office.
/// </summary>
public bool UserIsAlreadyLoggedIn { get; set; }
}
@@ -14,6 +14,13 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Content;
/// <summary>
/// Serves as a base controller for managing collections of content items, providing shared functionality for handling content collections and their variants.
/// </summary>
/// <typeparam name="TContent">The content entity type.</typeparam>
/// <typeparam name="TCollectionResponseModel">The response model type for the content collection.</typeparam>
/// <typeparam name="TValueResponseModelBase">The base type for value response models within the collection.</typeparam>
/// <typeparam name="TVariantResponseModel">The response model type for content variants.</typeparam>
public abstract class ContentCollectionControllerBase<TContent, TCollectionResponseModel, TValueResponseModelBase, TVariantResponseModel> : ManagementApiControllerBase
where TContent : class, IContentBase
where TCollectionResponseModel : ContentResponseModelBase<TValueResponseModelBase, TVariantResponseModel>
@@ -8,6 +8,9 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Content;
/// <summary>
/// Serves as the base controller for content management operations in the Umbraco CMS API, providing shared functionality for content-related controllers.
/// </summary>
public abstract class ContentControllerBase : ManagementApiControllerBase
{
protected IActionResult ContentEditingOperationStatusResult(ContentEditingOperationStatus status)
@@ -53,6 +56,15 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
ContentEditingOperationStatus.PropertyTypeNotFound => NotFound(problemDetailsBuilder
.WithTitle("One or more property types could not be found")
.Build()),
ContentEditingOperationStatus.PropertyTypeCultureVarianceMismatch => BadRequest(problemDetailsBuilder
.WithTitle("Property type culture variance mismatch")
.WithDetail("One or more property values specify a culture for an invariant property, or are missing a culture for a culture-variant property. "
+ "This can happen when a property is inherited from a variant composition on an invariant content type, which downgrades it to invariant.")
.Build()),
ContentEditingOperationStatus.PropertyTypeSegmentVarianceMismatch => BadRequest(problemDetailsBuilder
.WithTitle("Property type segment variance mismatch")
.WithDetail("One or more property values have a segment that does not match the property type's segment variance.")
.Build()),
ContentEditingOperationStatus.InTrash => BadRequest(problemDetailsBuilder
.WithTitle("Content is in the recycle bin")
.WithDetail("Could not perform the operation because the targeted content was in the recycle bin.")
@@ -79,11 +91,11 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
.Build()),
ContentEditingOperationStatus.CannotDeleteWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle("Cannot delete a referenced content item")
.WithDetail("Cannot delete a referenced document, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.WithDetail("Cannot delete a referenced content item, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.Build()),
ContentEditingOperationStatus.CannotMoveToRecycleBinWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle("Cannot move a referenced document to the recycle bin")
.WithDetail("Cannot move a referenced document to the recycle bin, while the setting ContentSettings.DisableUnpublishWhenReferenced is enabled.")
.WithTitle("Cannot move a referenced content item to the recycle bin")
.WithDetail("Cannot move a referenced content item to the recycle bin, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.Build()),
ContentEditingOperationStatus.Unknown => StatusCode(
StatusCodes.Status500InternalServerError,
@@ -9,12 +9,20 @@ 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;
@@ -22,12 +30,17 @@ public class AllCultureController : CultureControllerBase
}
/// <summary>
/// Returns all cultures available for creating languages.
/// Retrieves a paginated list of all available cultures, including their English and localized names.
/// </summary>
/// <returns></returns>
/// <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)]
[EndpointSummary("Gets a paginated collection of cultures available for creating languages.")]
[EndpointDescription("Gets a paginated collection containing the English and localized names of all available cultures.")]
public Task<PagedViewModel<CultureReponseModel>> GetAll(CancellationToken cancellationToken, int skip = 0, int take = 100)
{
CultureInfo[] all = _cultureService.GetValidCultureInfos();
@@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
namespace Umbraco.Cms.Api.Management.Controllers.Culture;
/// <summary>
/// Serves as the base controller for API endpoints that manage culture-related operations in the Umbraco CMS.
/// </summary>
[VersionedApiBackOfficeRoute("culture")]
[ApiExplorerSettings(GroupName = "Culture")]
public abstract class CultureControllerBase : ManagementApiControllerBase
@@ -0,0 +1,60 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Provides an API controller for retrieving the full details for multiple data types by key.
/// </summary>
[ApiVersion("1.0")]
public class BatchDataTypesController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="BatchDataTypesController"/> class.
/// </summary>
/// <param name="dataTypeService">The data type service.</param>
/// <param name="umbracoMapper">The presentation model mapper.</param>
public BatchDataTypesController(IDataTypeService dataTypeService, IUmbracoMapper umbracoMapper)
{
_dataTypeService = dataTypeService;
_umbracoMapper = umbracoMapper;
}
[HttpGet("batch")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(BatchResponseModel<DataTypeResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets multiple data types.")]
[EndpointDescription("Gets multiple data types identified by the provided Ids.")]
public async Task<IActionResult> Batch(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
Guid[] requestedIds = [.. ids];
if (requestedIds.Length == 0)
{
return Ok(new BatchResponseModel<DataTypeResponseModel>());
}
IEnumerable<IDataType> dataTypes = await _dataTypeService.GetAllAsync(requestedIds);
List<IDataType> ordered = OrderByRequestedIds(dataTypes, requestedIds);
var responseModels = ordered.Select(dt => _umbracoMapper.Map<DataTypeResponseModel>(dt)!).ToList();
return Ok(new BatchResponseModel<DataTypeResponseModel>
{
Total = responseModels.Count,
Items = responseModels,
});
}
}
@@ -0,0 +1,71 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// <summary>
/// Controller for retrieving multiple data type value schemas in a single request.
/// </summary>
[ApiVersion("1.0")]
public class BatchSchemasDataTypeController : DataTypeControllerBase
{
private readonly IPropertyEditorSchemaService _schemaService;
/// <summary>
/// Initializes a new instance of the <see cref="BatchSchemasDataTypeController"/> class.
/// </summary>
/// <param name="schemaService">The property editor schema service.</param>
public BatchSchemasDataTypeController(IPropertyEditorSchemaService schemaService)
=> _schemaService = schemaService;
/// <summary>
/// Gets the value schemas for multiple data types.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
/// <param name="ids">The unique identifiers of the data types.</param>
/// <returns>The schema information for the requested data types.</returns>
/// <remarks>
/// Returns schema information for property editors that implement <c>IValueSchemaProvider</c>.
/// Each item includes an error field if the schema could not be retrieved (e.g., data type not found or schema not supported).
/// </remarks>
[HttpGet("schemas/batch")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FetchResponseModel<DataTypeSchemaItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetSchemas(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] Guid[] ids)
{
Guid[] requestedIds = [.. ids.Distinct()];
if (requestedIds.Length == 0)
{
return Ok(new FetchResponseModel<DataTypeSchemaItemResponseModel>());
}
var items = new List<DataTypeSchemaItemResponseModel>();
foreach (Guid id in requestedIds)
{
Attempt<PropertyValueSchema, PropertyEditorSchemaOperationStatus> attempt = await _schemaService.GetSchemaAsync(id);
items.Add(new DataTypeSchemaItemResponseModel
{
Id = id,
ValueTypeName = attempt.Success ? attempt.Result.ValueType?.FullName : null,
JsonSchema = attempt.Success ? attempt.Result.JsonSchema : null,
Error = attempt.Success ? null : attempt.Status.ToString(),
});
}
return Ok(new FetchResponseModel<DataTypeSchemaItemResponseModel>
{
Total = items.Count,
Items = items,
});
}
}
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
@@ -8,22 +8,40 @@ 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)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Gets a data type.")]
[EndpointDescription("Gets a data type identified by the provided Id.")]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id)
{
IDataType? dataType = await _dataTypeService.GetAsync(id);
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@@ -8,16 +8,30 @@ 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)]
[EndpointSummary("Gets the data type configuration.")]
[EndpointDescription("Gets the configuration settings for data types.")]
public Task<IActionResult> Configuration(CancellationToken cancellationToken)
{
var responseModel = new DatatypeConfigurationResponseModel
@@ -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,16 +22,32 @@ 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)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Copies a data type.")]
[EndpointDescription("Creates a duplicate of an existing data type identified by the provided unique Id. The copied data type will be given a new Id and have ' (copy)' appended to its name. Optionally, the copy can be placed in a specific container by providing a target container Id.")]
public async Task<IActionResult> Copy(CancellationToken cancellationToken, Guid id, CopyDataTypeRequestModel copyDataTypeRequestModel)
{
IDataType? source = await _dataTypeService.GetAsync(id);
@@ -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,11 +37,21 @@ 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)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Creates a new data type.")]
[EndpointDescription("Creates a new data type with the configuration specified in the request model.")]
public async Task<IActionResult> Create(CancellationToken cancellationToken, CreateDataTypeRequestModel createDataTypeRequestModel)
{
var attempt = await _dataTypePresentationFactory.CreateAsync(createDataTypeRequestModel);
@@ -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,17 +21,30 @@ 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)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Deletes a data type.")]
[EndpointDescription("Deletes a data type identified by the provided Id.")]
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id)
{
Attempt<IDataType?, DataTypeOperationStatus> result = await _dataTypeService.DeleteAsync(id, CurrentUserKey(_backOfficeSecurityAccessor));
@@ -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.
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -11,21 +11,41 @@ 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)]
[EndpointSummary("Gets a filtered collection of data types.")]
[EndpointDescription("Filters data types based on the provided criteria with support for pagination.")]
public async Task<IActionResult> Filter(
CancellationToken cancellationToken,
int skip = 0,
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
@@ -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,9 +25,19 @@ 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)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Gets a data type folder.")]
[EndpointDescription("Gets a data type folder identified by the provided Id.")]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id) => await GetFolderAsync(id);
}

Some files were not shown because too many files have changed in this diff Show More