Compare commits

..
Author SHA1 Message Date
Nhu DinhandGitHub 9505011d71 Build: Updated nightly E2E test pipeline schedule in v16 (#22805)
Updated nightly E2E test pipeline schedule
2026-05-12 15:36:39 +07:00
Nhu DinhandGitHub 2579aaf2db Build: Cherry pick #22164 for V16 (#22171)
Serialize E2E stages and stagger branch schedules to reduce agent usage
2026-03-19 21:19:45 +07:00
Andy Butland 2b7784a226 Merge branch 'release/16.5.1' into v16/dev 2026-03-10 06:36:50 +01:00
8555a97b39 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 c2dd685a4b Merge commit from fork 2026-03-10 05:10:31 +01:00
Andy ButlandandGitHub 66fc819379 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
Andy Butland 4f1f7e15c4 Bump version to 16.5.1. 2026-02-23 16:38:23 +01:00
Andy Butland a826c52e2e Merge branch 'release/16.5' into v16/dev and bumped version to 16.6.0-rc 2026-01-22 06:45:06 +01:00
Andy Butland 8b2c22aaf1 Merge branch 'release/16.5' of https://github.com/umbraco/Umbraco-CMS into release/16.5 2026-01-21 17:40:17 +01:00
Andy Butland aecfee4469 Bump version to 16.5.0. 2026-01-21 17:39:59 +01:00
Niels LyngsøandGitHub 9c785a9c5b Varying Compositions in Invariant Document Types, Cherrypick of #21267 (#21472)
* cherry picked a5a6d0645f

* correct to getVariesBySegment
2026-01-21 15:27:26 +00:00
Nhu DinhandGitHub 2fe10387ee E2E: V16 QA Update acceptance tests to use refactored UI helpers (#21271)
* Bumped version of test helper

* Bumped version

* Bumped version
2026-01-05 17:25:23 +07:00
8642b9e615 Content Types: Fix property variation change when content exists only in non-default language (#21182)
* Content Types: Fix property variation change when content exists only in non-default language (closes #11771)

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

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

* Add tests for AllowEditInvariantFromNonDefault enabled scenario

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 15:21:17 +01:00
Andreas ZerbstandGitHub 2a604c8719 E2E: QA Replaced unreliable Thread.Sleep(500) with a counter/gate pattern that ensures both transactions are initialized before releasing them to compete for locks (#21165)
Fix flaky test
2025-12-16 12:02:07 +00:00
9c0a0a1086 V16/drag event media (#20893)
* updates the drag event to convert types to lowercase

* clearing up

* clearing up

* Fixed style problem in safari and an unused parameter.

* Fixed lint error.

---------

Co-authored-by: engjlr <enl@umbraco.dk>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
2025-12-15 14:16:57 +00:00
Andy Butland 1a4256f997 Add option to hide colors from icon picker (#20650)
* Add option to hide colors from icon picker

* Hide colors

* Hide colors from config
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.element.ts
2025-12-11 06:57:29 +01:00
Andy Butland 80ae0380a2 oEmbed Providers: Updated the X oEmbed provider to use the x.com domain (closes #21052) (#21053)
* Updated the X oEmbed provider to use the x.com domain.

* Fixed issues raised in code review.
2025-12-05 10:55:09 +01:00
Andy Butland fd01282798 Merge branch 'release/16.4.1' into v16/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-12-05 06:44:51 +01:00
f7ba2eaa62 Property Editors: Hide "add button" when maximum configuration is 1 (fixes #20407) (#20738)
Hide add button when max 1

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2025-12-04 16:34:10 +01:00
9485a95c0e Image cropper modal import missing component (#20651)
* Import missing component

* Handle nullable type

* Vertically center image

* Add minimum width for SVG without dimensions

* 100% height until max height

* 100% height minus top/bottom padding

* Revert "100% height minus top/bottom padding"

This reverts commit 67ada4c70f4b75dfcfa2b54ce139ec7465a17ce1.

* Revert "Handle nullable type"

This reverts commit 3130e11a4be83a18b5a7d8c1c24ee23c94d8765d.

* Removed flexbox style

* Fixed circular dependency

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2025-12-04 16:32:10 +01:00
f1ab605bb9 Debug mode: Marks UMB-DEBUG cookie as HttpOnly and Secure (#21032)
* fix: sets profiling cookie to httpOnly and strict in order to run non-secure

* fix: adds extra message to explain when you can set a cookie

* fix: simplify cookie explanation comment in WebProfilerRepository

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

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

* fix: checks that the profiler is actually enabled and/or disabled and warns the user if that is not the case

* Update src/Umbraco.Web.UI.Client/src/assets/lang/en.ts

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-04 10:24:12 +01:00
Andy Butland 3472ff9ba3 Bump version to 16.4.1. 2025-12-03 15:38:10 +01:00
577dc06d55 Delivery API: Only add default strategy if delivery API is not registered. (#20982)
* Only add if not already present

* Update src/Umbraco.Cms.Api.Management/DependencyInjection/WebhooksBuilderExtensions.cs

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2025-12-03 15:37:03 +01:00
f4771d1495 Delivery API: Only add default strategy if delivery API is not registered. (#20982)
* Only add if not already present

* Update src/Umbraco.Cms.Api.Management/DependencyInjection/WebhooksBuilderExtensions.cs

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2025-12-03 12:32:30 +01:00
Andy Butland 4b3ce53acf Merge branch 'release/16.4' into v16/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-11-26 07:18:09 +01:00
Andy Butland ca267047d3 Bumped version to 16.4.0. 2025-11-25 12:32:28 +01:00
Jacob Overgaard 0543163817 bumps version to 16.5.0-rc 2025-11-24 16:29:11 +01:00
Jacob Overgaard 72f43a5821 Merge branch 'release/16.4' into v16/dev 2025-11-24 16:28:24 +01:00
aea9034adf Localization: Restores region-specific cultures (#20939) (#20942)
* Adds localization manifests for region-specific cultures

This is to support backwards-compatibility and v13 upgradability.

* Removed `uiCulture` from Vietnamese localizations

since it duplicated the English fallback texts.

* 'en' localization file formatting

* Update src/Umbraco.Web.UI.Client/src/assets/lang/en.ts



---------

Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 15:27:35 +00:00
Jacob Overgaard 6e6f822761 bumps version to 16.4.0-rc3 2025-11-24 15:24:58 +01:00
Niels LyngsøandGitHub 137aa20a10 Block Editors: avoid discard changes for no changes (Fixes #20680) (#20941)
* ensure Block List only updates if it has an update

* ensures RTE and Grid Block Editor ony updates value if there is a change
2025-11-24 14:04:59 +00:00
Callum WhyteandZeegaan d7231c5435 Preserve existing Examine FieldDefinitionCollection if it already exists (#20931)
* Preserve existing Examine FieldDefinitionCollection if it already exists (#20267)

* Fix missing bracket

* Minor tidy/addition of comments; addition of unit tests.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
(cherry picked from commit 908974c6ac)
2025-11-24 12:47:08 +09:00
Andy Butland be116436d9 Migrations: Handles rich text blocks created with TinyMCE in convert local links migration and refreshes internal datatype cache following migration requiring cache rebuild (closes #20885) (#20887)
Handles rich text blocks created with TinyMCE in convert local links migration.
Refreshes internal datatype cache following migration requiring cache rebuild.
# Conflicts:
#	src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
2025-11-19 15:35:48 +01:00
Jacob Overgaard aed7505e4b Merge remote-tracking branch 'origin/release/16.4' into v16/dev 2025-11-17 16:39:38 +01:00
590a020303 Redact back-office PKCE codes from the server (V16) (#20851)
Redact back-office PKCE codes from the server (#20847)

* Redact back-office PKCE codes from the server

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

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-11-17 11:17:18 +01:00
Jacob Overgaard 15c6ca7628 Merge remote-tracking branch 'origin/release/16.4' into v16/dev 2025-11-13 14:15:24 +01:00
49ba89c22a Move access/refresh tokens to secure cookies (#20779)
* feat: adds the `credentials: include` header to all manual requests

* feat: adds `credentials: include` as a configurable option to xhr requests (and sets it by default to true)

* feat: configures the auto-generated fetch client from hey-api to include credentials by default

* Add OpenIddict handler to hide tokens from the back-office client

* Make back-office token redaction optional (default false)

* Clear back-office token cookies on logout

* Add configuration for backoffice cookie settings

* Make cookies forcefully secure + move cookie handler enabling to the BackOfficeTokenCookieSettings

* Use the "__Host-" prefix for cookie names

* docs: adds documentation on cookie settings

* build: sets up launch profile for vscode with new cookie recommended settings

* docs: adds extra note around SameSite settings

* docs: adds extra note around SameSite settings

* Respect sites that do not use HTTPS

* Explicitly invalidate potentially valid, old refresh tokens that should no longer be used

* Removed obsolete const

---------

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2025-11-13 08:19:42 +01:00
Andy Butland c295271757 Bumped version to 16.4.0-rc2. 2025-11-13 06:39:10 +01:00
Niels LyngsøandNiels Lyngsø 12b483ff05 Fix block list inline mode (#20745)
* Fix block list inline mode

https://github.com/umbraco/Umbraco-CMS/issues/20618

* Fixed potential runtime errors

* Code cleanup

* Fixed Code Health Review

* Revert some changes

Commented out unused state properties and related code.

* Remove commented-out state property in block workspace view

* fix localization

* no need for question mark after ids, they should be presented as required

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2025-11-10 17:42:16 +01:00
Andy ButlandandGitHub 7502a38033 Dependencies: Update dotnet sdk and node development dependency to latest secure version of current major (16) (#20734)
* Update dotnet sdk and node development dependency to latest secure version of current major.

* Update package-lock.json.
2025-11-05 20:02:11 +01:00
Jacob Overgaard 4e74dbf218 Merge branch 'release/16.4' into v16/dev 2025-11-04 14:07:30 +01:00
Jacob OvergaardandGitHub fa5c53b571 Auth: Cleans up stale or completed auth details from storage (#20725)
* fix: cleans up stale PKCE keys after auth regardless of success or error

* fix: cleans up stale PKCE data on logout
2025-11-04 11:31:34 +00:00
Andreas ZerbstandGitHub 76fed82e91 E2E: QA cherry picked acceptance tests updates from 17 (#20714)
* Updated tests

* Bumped version

* Added v16 to nightly e2e run
2025-11-04 08:23:04 +01:00
Rick ButterfieldandAndy Butland 43ac32282c Preview: Add allow-forms to iframe sandbox attributes (#20701)
Add 'allow-forms' to iframe sandbox attributes
2025-10-31 13:37:35 +01:00
96ecef0a92 Performance: Request cache referenced entities when saving documents with block editors (#20590)
* Added request cache to content and media lookups in mult URL picker.

* Allow property editors to cache referenced entities from block data.

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

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

* Add obsoletions.

* Minor spellcheck

* Ensure request cache is available before relying on it.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: kjac <kja@umbraco.dk>
2025-10-31 12:41:46 +01:00
Laura Neto 5e87dead44 Task: Dependency track (#20670)
* Generate BOM files on build

* Upload BOM to Dependency Track

* Move Backoffice BOM generation to right after install

The build and/or pack steps are deleting files that are needed for the BOM to be generated properly.

* Split the BOM uploads into different jobs

* Fix wrong usage of parameters

* Move order of dependency track stage

* Fix wrong umbracoVersion value

* Small fixes

* Log curl response headers

* Correct version sent to dependency track

* Adjusted curl flags

* Fix bom file path

* Fix dotnet bom file name

* Add Login UI to dependency track

* Generate BOM for E2E Tests

* Move dependency track stage

* Move acceptance test .env generation to e2e install template

Needed as the post install script is expecting this to exist.

* Use major version if public release

* Missing ')'

* Reverted npm install command changes in static assets project
2025-10-31 12:11:32 +01:00
7af67d2944 Have to control of the state store navigation for custom sections or … (#20637)
* Have to control of the state store navigation for custom sections or overrides

* revert wording

* move logic and update comment

---------

Co-authored-by: Lucas Bach Bisgaard <lucas.bisgaard@kraftvaerk.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-10-29 20:00:36 +01:00
Jacob Overgaard ce59537006 build: updates lockfile 2025-10-29 11:40:10 +01:00
Jacob Overgaard f87e15b941 build: adds back the ^ missing from openapi-ts to allow newer versions to be used 2025-10-29 11:38:44 +01:00
Andy Butland 1f82bdde3d Merge branch 'release/16.3.4'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-10-29 06:45:38 +01:00
0d2393d866 Caching: Resolves publish and install issues related to stale cached data retrieval (closes #20539 and #20630) (#20640)
* Request cache published content creation with version.

* Reload memory cache after install with package migrations.

* Improve message on install for database cache rebuild.

* Update src/Umbraco.Infrastructure/Install/MigrationPlansExecutedNotificationHandler.cs

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

* Relocated memory cache refresh after package install from notification handler to unattended upgrader.

* Fix construtor breaking change

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-10-28 13:26:26 +01:00
bea21d7b99 Caching: Resolves publish and install issues related to stale cached data retrieval (closes #20539 and #20630) (#20640)
* Request cache published content creation with version.

* Reload memory cache after install with package migrations.

* Improve message on install for database cache rebuild.

* Update src/Umbraco.Infrastructure/Install/MigrationPlansExecutedNotificationHandler.cs

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

* Relocated memory cache refresh after package install from notification handler to unattended upgrader.

* Fix construtor breaking change

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-10-28 12:25:13 +00:00
Andy Butland 3dc65c48b3 Bump package-lock.json to 16.3.4. 2025-10-28 09:58:31 +01:00
Niels LyngsøandAndy Butland 18ab333afc Hotfix: Implement a specific sorting method for statuses as the existing has … (#20609)
Implement a specific sorting method for statuses as the existing has to support deprecated implementation of custom getUnique method
2025-10-28 09:15:17 +01:00
Niels LyngsøandAndy Butland fd91f88a7e Item Repository: Sort statuses by order of unique (#20603)
* utility

* ability to replace

* deprecate removeStatus

* no need to call this any longer

* Sort statuses and ensure not appending statuses, only updating them
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts
2025-10-28 09:15:05 +01:00
Andy Butland 13c164d81f Bump version to 16.3.4. 2025-10-28 08:58:03 +01:00
Andy ButlandandGitHub f33eb3f678 Media types: Handle null configured file extensions when populating allowed media types (closes #20620) (#20635)
* Handle null configured file extensions when populating allowed media types.

* Added clarifying comment.
2025-10-27 13:42:48 +01:00
Jan SkovgaardandSebastiaan Janssen e893682723 Don't call generateAlias on #onAliasChange()
Currently it's not possible to use characters like "_" and "-" in aliases due to this check - At least that is was @nul800sebastiaan told me 😇

Suggested fix for #20622
2025-10-25 10:47:56 +02:00
d9c201e3d1 docs: Add backoffice preview URL to README files (#20623)
* docs: Add backoffice preview URL to README files

Added links to https://backofficepreview.umbraco.com/ in both the main repository README and the Umbraco.Web.UI.Client package README to make the live backoffice preview easily discoverable.

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

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

* docs: fix link

* Update .github/README.md

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-10-24 08:36:34 +02:00
Andy Butland a5fcfc231d Merge branch 'main' of https://github.com/umbraco/Umbraco-CMS 2025-10-24 06:44:08 +02:00
Andy Butland 6ba03a48c8 Merge branch 'release/16.3.3'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-10-24 06:43:55 +02:00
8434c7d0cb Icon Picker: Fix empty selection allowed on mandatory fields and add validation. (#20536)
* Not show the empty tile when filtering is active.

* Added mandatory property to the icon picker.

* Avoid deselecting the icon on second click when not showing the empty option.

* Extends the form control mixin to the icon picker.

* Used super.value.

* Support mandatory from settings config.

* Removed mandatoryConf.

* remove requestUpdate

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-10-23 13:49:14 +00:00
Bjarne FyrstenborgandGitHub 3854b2bd53 Block List: Remove bold label from inline editing (#20437)
Remove bold label from block list inline editing
2025-10-23 14:32:37 +02:00
dependabot[bot]andJacob Overgaard 08d217360e Bump vite from 7.1.9 to 7.1.11 in /src/Umbraco.Web.UI.Login
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.9 to 7.1.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-23 12:05:29 +02:00
Sebastiaan JanssenandGitHub b762135554 Exclude 'release/no-notes' from release labels 2025-10-23 11:21:25 +02:00
Andy Butland 644334c63b Trees: Restore backward compatibility for file system based tree controllers (closes #20602) (#20608)
* Restore backward compatibility for file system based tree controllers.

* Aligned obsoletion messages.
2025-10-22 16:43:12 +02:00
Andy Butland a09e1777c4 Migrations: Use reliable GUID to check for existence of data type when creating (#20604)
* Use reliable GUID to check for existence of data type in migration.

* Retrieve just a single field in existence check.
2025-10-22 16:43:04 +02:00
Andy Butland 9cb59fe1b4 Bumped version to 16.3.3. 2025-10-22 16:42:12 +02:00
Andy ButlandandGitHub 6bc498ad41 Trees: Restore backward compatibility for file system based tree controllers (closes #20602) (#20608)
* Restore backward compatibility for file system based tree controllers.

* Aligned obsoletion messages.
2025-10-22 14:20:20 +00:00
f88e28d642 Filesystem: Prevent tree showing other filetypes than the supported ones (#20567)
* Added check to only find .css files in FileSystemTreeServiceBase.cs

* Marking GetFiles as virtual and overriding it in StyleSheetTreeService.cs to only find .css files

* Redone tests to fit new format

* Fix tests to use file extensions

* Adding file extensions to all other relevant tests

* Adding file filter to remaining trees

* Adding tests to ensure invalid filetypes wont show

* Encapulation and resolved minor warnings in tests.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-10-22 15:12:39 +02:00
Mads RasmussenandNiels Lyngsø 194fee7c91 Use tryExecute for delete API call
Replaces direct await of #delete with tryExecute to improve error handling in the delete method of UmbManagementApiDetailDataRequestManager.
2025-10-22 14:31:41 +02:00
Niels LyngsøandGitHub 4a65f56d9d Hotfix: Implement a specific sorting method for statuses as the existing has … (#20609)
Implement a specific sorting method for statuses as the existing has to support deprecated implementation of custom getUnique method
2025-10-22 11:57:09 +00:00
Sven GeusensandGitHub 62c1d44a5d Webhooks: Register OutputExpansionStrategy for webhooks if Delivery API is not enabled (#20559)
* Register slimmed down OutputExpansionStrategy for webhooks if deliveryapi is not enabled

* PR review comment resolution
2025-10-22 13:46:56 +02:00
Andy Butland c2eea5d6cc Populate IncludeDescendants on ContentPublishedNotification when publishing branch (forward port of #20578). 2025-10-22 13:37:19 +02:00
Niels Lyngsø 21bf23b67d Dictionary: Fix shortcut Ctrl + S not saving dictionary items (#20605)
* switched event listener from 'change' to 'input'

* Update workspace-view-dictionary-editor.element.ts
2025-10-22 12:37:13 +02:00
Andy ButlandandGitHub 48759b9852 Migrations: Use reliable GUID to check for existence of data type when creating (#20604)
* Use reliable GUID to check for existence of data type in migration.

* Retrieve just a single field in existence check.
2025-10-22 10:21:42 +00:00
Niels Lyngsø 79639c0571 Item Repository: Sort statuses by order of unique (#20603)
* utility

* ability to replace

* deprecate removeStatus

* no need to call this any longer

* Sort statuses and ensure not appending statuses, only updating them
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts
2025-10-22 11:52:09 +02:00
Andy Butland 0792e4358b Merge branch 'release/16.3.2'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-10-22 07:11:02 +02:00
Jacob Overgaard caeb3454e1 build(dev): adds umbracoapplicationurl to vscode launch params 2025-10-21 16:22:57 +02:00
Jacob Overgaard 942ccc82d9 docs: Add 'Running Umbraco in Different Modes' section to copilot-instructions 2025-10-21 16:22:36 +02:00
Andy Butland 8aa9dc8f19 Hybrid Cache: Resolve start-up errors with mis-matched types (#20554)
* Be consistent in use of GetOrCreateAsync overload in exists and retrieval.
Ensure nullability of ContentCacheNode is consistent in exists and retrieval.

* Applied suggestion from code review.

* Move seeding to Umbraco application starting rather than started, ensuring an initial request is served.

* Tighten up hybrid cache exists check with locking around check and remove, and use of cancellation token.
2025-10-21 15:27:15 +02:00
Andy Butland 5488c77e0e Bumped version to 16.3.2. 2025-10-21 15:26:29 +02:00
Andy ButlandandGitHub daace4b4a0 Publishing: Resolve exceptions on publish branch (#20464)
* Reduce log level of image cropper converter to avoid flooding logs with expected exceptions.

* Don't run publish branch long running operation on a background thread such that UmbracoContext is available.

* Revert to background thread and use EnsureUmbracoContext to ensure we can get an IUmbracoContext in the URL providers.

* Updated tests.

* Applied suggestion from code review.

* Clarified comment.
2025-10-21 12:05:10 +02:00
Andy ButlandandGitHub 1ceec183a3 Media: Fixes SQL error to ensure database relation between user group media start folder and deleted media item is removed (closes #20555) (#20572)
Fixes SQL error to ensure database relation between user group media start folder and deleted media item is removed.
2025-10-21 11:38:01 +02:00
Andy ButlandandGitHub 81a8a0c191 Hybrid Cache: Resolve start-up errors with mis-matched types (#20554)
* Be consistent in use of GetOrCreateAsync overload in exists and retrieval.
Ensure nullability of ContentCacheNode is consistent in exists and retrieval.

* Applied suggestion from code review.

* Move seeding to Umbraco application starting rather than started, ensuring an initial request is served.

* Tighten up hybrid cache exists check with locking around check and remove, and use of cancellation token.
2025-10-21 09:57:29 +02:00
ae41438a36 Tiptap RTE: Allow removal of unregistered extensions (#20571)
* Tiptap toolbar config: enable removal of unregistered extensions

* Tiptap statusbar config: enable removal of unregistered extensions

* Tiptap toolbar config: Typescript tidy-up

* Tiptap toolbar sorting amend

Removed the need for the `tiptap-toolbar-alias` attribute,
we can reuse the `data-mark`.

* Tiptap extension config UI amend

If the extension doesn't have a `description`,
then add the `alias` to the title/tooltip, to give a DX hint.

* Tiptap toolbar: adds `title` to placeholder skeleton

* Added missing `forExtensions` for Style Select and Horizontal Rule toolbar extensions

* Update src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/toolbar-configuration/property-editor-ui-tiptap-toolbar-configuration.element.ts

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

* Update src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/statusbar-configuration/property-editor-ui-tiptap-statusbar-configuration.element.ts

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-21 07:28:01 +00:00
dependabot[bot]andJacob Overgaard 5337c38f2c Bump vite from 7.1.9 to 7.1.11 in /src/Umbraco.Web.UI.Client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.9 to 7.1.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-21 09:13:34 +02:00
Nhu DinhandGitHub 7751e40ba8 E2E: QA Fixed the flaky tests related to publishing content with image cropper (#20577)
Added more waits
2025-10-21 08:50:13 +02:00
Lee KelleherandGitHub d5a2f0572e Preview: Redirect to published URL on exit (#20556)
* Preview Exit: Gets the page's published URL on exit for redirect

* Preview Open Website: Uses the page's published URL

* Tweaked the published URL logic

* Code amends based on @copilot's suggestions
2025-10-20 11:51:38 +02:00
Andy Butland 5a65eb1758 Update OpenApi.json and client-side models. 2025-10-17 15:01:09 +02:00
Anders ReusandGitHub 105cb9da41 Added trashed state so when requesting content from the recycle bin via the management api it will return trashed instead of published state (#20542)
Added trashed state so when requesting content from the recycle bin via the management api, the state will be trashed instead of published.
2025-10-17 14:40:18 +02:00
Andy ButlandandJacob Overgaard a3a8be4717 Templates: Retain layout from file when loading template (closes #20524) (#20529)
Retain layout from file when loading template.
2025-10-17 10:47:21 +02:00
Jacob Overgaard d17ba805b2 build(deps): bumps @umbraco-ui/uui from 1.16.0-rc.0 to 1.16.0 2025-10-17 09:56:42 +02:00
Andy Butland 96f597e440 Merge branch 'release/16.3.1'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-10-17 09:55:20 +02:00
Ben WhiteandAndy Butland 6458bb40f9 Don't use non-generic ILogger as a fallback in BlockEditorPropertyValueEditor (#20532)
Update logger service retrieval in BlockEditorPropertyValueEditor
2025-10-16 21:07:16 +02:00
Ben WhiteandGitHub 31bcbc1147 Don't use non-generic ILogger as a fallback in BlockEditorPropertyValueEditor (#20532)
Update logger service retrieval in BlockEditorPropertyValueEditor
2025-10-16 21:06:10 +02:00
Andy Butland 62edad17a1 Bumped version to 16.3.1. 2025-10-16 20:50:59 +02:00
Kenn JacobsenandAndy Butland 369b020d9d Explicitly flush isolated caches by key for content updates (#20519)
* Explicitly flush isolated caches by key for content updates

* Apply suggestions from code review

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-10-16 14:17:04 +02:00
Laura Neto ec354cef92 Merge branch 'release/16.3'
# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-10-16 10:05:33 +02:00
Laura Neto a504fd1ef8 Bump version to 16.3.0 2025-10-16 08:22:11 +02:00
Warren BuckleyandGitHub 4c05a114c5 Fixes 20476 - Changes icon to be no entry sign (#20496) 2025-10-15 14:10:39 +02:00
Andy ButlandandGitHub e71f36d816 Back Office: Fixes link to workspace root from breadcrumb trail (closes: #20455) (#20459)
Fixes link to workspace root from breadcrumb trail.
2025-10-15 10:06:00 +02:00
Andy ButlandandGitHub fdf759d08d Content Types: Prevent creation of document type with an alias that case insensitively matches an existing alias (closes #20467) (#20471)
Prevent creation of document type with an alias that case insensitively matches an existing alias.
2025-10-15 09:41:41 +02:00
1ab13a970b Dashboard: Add tests for create and using custom dashboard (#20253)
* add tests for custom dashboard

* update test dashboard using helper

* remove extensionRegistry for playwright config

* update helper version for dashboard

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/CustomDashboard.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* fix format code

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
2025-10-15 13:33:41 +07:00
e22b459d9c WorkspaceView: Add tests for create and using custom workspace view (#20408)
* WorkspaceView: Add tests for create and using custom workspace view

* update helper version

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/WorkspaceView.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/WorkspaceView.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* update format code

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/WorkspaceView.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/WorkspaceView.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/WorkspaceView.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
2025-10-15 13:33:24 +07:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>iOvergaardleekelleher
a19b9fb5fe UFM: Add camelCase aliases for UFM filters to support UFMJS expressions (closes #20500) (#20501)
* Initial plan

* Add camelCase aliases for UFM filters with hyphens (stripHtml, titleCase, wordLimit)

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

* Add manifest tests for camelCase filter aliases

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

* discards tests that are not useful

* test: updates imports for stripHtml api

* Exports `UmbUfmStripHtmlFilterApi` class

---------

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: leekelleher <leekelleher@gmail.com>
2025-10-14 16:35:42 +00:00
Anders ReusandGitHub cdf9ee4566 Added culture to the ApiContentRouteBuilder to include variant languages. (#20366) (#20499)
Added culture to the ApiContentRouteBuilder to include variant languages.
2025-10-14 16:06:48 +02:00
Sven GeusensandGitHub e53220c8f5 Delivery API: Fix not reindexing branch descendants when branch root already published but unchanged (closes #20370) (#20462)
* Fix deliveryApi not reindexing branch descendants when branch root already published and unchanged

* Commit update and name improvement
2025-10-14 12:15:01 +02:00
Andy ButlandandGitHub 12adfd52bd Performance: Reduce number of database calls in save and publish operations (#20485)
* Added request caching to media picker media retrieval, to improve performance in save operations.

* WIP: Update or insert in bulk when updating property data.

* Add tests verifying UpdateBatch.

* Fixed issue with UpdateBatch and SQL Server.

* Removed stopwatch.

* Fix test on SQLite (failing on SQLServer).

* Added temporary test for direct call to NPoco UpdateBatch.

* Fixed test on SQLServer.

* Add integration test verifying the same property data is persisted as before the performance refactor.

* Log expected warning in DocumentUrlService as debug.
2025-10-14 11:22:21 +02:00
494674d354 Entity Actions: More create button discernible text, extension of #20434 (#20458)
* added hovering and focus border to RTE

* fix main to OG

* fix to main again

* I'm going to cry

* Missing localiztion feature, maybe UmbLitElement?

* added localization controller to fetch localized version

* localization successful for viewActionsFor and CreateFor

* clean up button text

* Changed label for content header to display proper name

* clean up code

* Included button labels for media section

* clean code

* Relocated localization keys,

as `actions_viewActionsFor` already existed.

Also made into a function, to support a fallback label.

* Simplified the "Create for" label/localization

Removed the need for a `getCreateAriaLabel()` method.

* Removed the double-localizations (of `actions_viewActionsFor`)

as the "umb-entity-actions-bundle" component handles this now.

* imports tidy-up

* Simplified localization key condition

* switched to new localization key for other sections for new labeling

* Bumped `@umbraco/playwright-testhelpers` 16.0.55

https://github.com/umbraco/Umbraco.Playwright.Testhelpers/releases/tag/release%2F16.0.55

---------

Co-authored-by: Oskar kruger <obk@umbraco.dk>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2025-10-14 07:20:01 +00:00
Lee KelleherandGitHub 4ba186633c UFM: Adds $index support to Block editors (fixes #20470) (#20488)
* Block List: adds `$index` support for UFM labels

* Block Grid: adds `$index` support for UFM labels

* Block RTE: adds `$index` support for UFM labels

Which is always zero `0`.
But has been wired up if we do implement the index order in future.
2025-10-14 08:46:48 +02:00
Andy Butland e336f9dfb0 Merge branch 'release/16.3'
# Conflicts:
#	version.json
2025-10-14 08:22:07 +02:00
Lee KelleherandGitHub 3ac37f3686 Recycle Bin: Trigger cache invalidation for trashed document/media items (#20483)
* Configure document/media items to listen for `Trashed` server-events for cache invalidation

* Fire reload event on restore destination tree/menu

* Removed "trashed" part of the code comment
2025-10-13 16:32:48 +01:00
0a027dd80d Dependencies: Fixed dependency conflicts when installing Microsoft.EntityFrameworkCore.Design (closes #20421) (#20474)
* Add explicit references to Microsoft.CodeAnalysis.* packages to fix conflicts when installing Microsoft.EntityFrameworkCore.Design

This allows consumers to simply install Microsoft.EntityFrameworkCore.Design without having to manually install specific versions to deal with transitive dependency problems.

* Disable CPM for UI project to better reflect consumers

* Update src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-13 13:12:45 +02:00
Bjarne FyrstenborgandGitHub 7b4684cd70 UX: Center align log type in media history view (#20469)
Center align log type in media
2025-10-13 06:34:02 +02:00
3df8b9e41a Refactoring: Fixed spelling mistake in method name (#20460)
* Fixed spelling mistake in method name.

* Apply suggestion from @Copilot

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

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 13:00:47 +00:00
Bjarne FyrstenborgandGitHub fd34ce5bd7 Icon Picker: Fit icons scroll container to modal height (#20438)
* Fill height and align icons to top

* Auto scrollbar instead

* Auto height of grid rows

* Enforce scroll again
2025-10-10 10:32:19 +02:00
99c2aaf17a Members: Forward port of fix for member lockout issue #16988 from PR #17007 for 16 (#20441)
* Port PR #17007

* Update src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 09:52:57 +02:00
a4c373d3b5 Entity Actions: Create button discernible text (fixes #20205) (#20434)
* added hovering and focus border to RTE

* fix main to OG

* fix to main again

* I'm going to cry

* Missing localiztion feature, maybe UmbLitElement?

* added localization controller to fetch localized version

* localization successful for viewActionsFor and CreateFor

* clean up button text

* Changed label for content header to display proper name

* clean up code

* Included button labels for media section

* clean code

* Relocated localization keys,

as `actions_viewActionsFor` already existed.

Also made into a function, to support a fallback label.

* Simplified the "Create for" label/localization

Removed the need for a `getCreateAriaLabel()` method.

* Removed the double-localizations (of `actions_viewActionsFor`)

as the "umb-entity-actions-bundle" component handles this now.

* imports tidy-up

* Simplified localization key condition

---------

Co-authored-by: Oskar kruger <obk@umbraco.dk>
Co-authored-by: leekelleher <leekelleher@gmail.com>
2025-10-09 13:56:30 +00:00
bcedc8de2a Emails: Add Expires header (#20285)
* Add `Expiry` header to emails, set default expiry to 30 days and allow user config via `appsettings`

* Remove `IsSmtpExpirationConfigured` as it will always have a value

* Check for `emailExpiration` value

* Removed `EmailExpiration` default value as it should be opt-in

* Simplify SMTP email expiration condition

* Fix APICompat issue

* Add implementation to `NotImplementedEmailSender`

* Rename `emailExpiration` to `expires` to match the SMTP header

* Obsolete interfaces without `expires` parameter, delegate to an existing method.

* Set expiry TimeSpan values from user configurable settings with defaults

* Fix formating

* Handle breaking changes, add obsoletion messages and simplify interfaces.

* Fix default of invite expires timespan (was being parsed as 72 days not 72 hours).

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-10-09 14:27:53 +02:00
767894b723 Color Picker: Validate uniqueness of selected colors (#20431)
* Added unique color checker to color picker.

* Added Unittest for duplicates

* optimized for codescene

* removed the bump and simplified the function

* Fixed behaviour for duplicate checks so unit test passes.
A little refactoring.

* Adds continue so invalid colors aren't checked for duplicates.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-10-09 11:50:11 +02:00
1fe7931d07 Migrations: Adjust the JsonBlockValueConverter to handle conflicts with 'values' property (#20429)
* Adjust the `JsonBlockValueConverter` to handle conflicts with 'values' property (due to old data schema)

* Simplify code

* Add unit test to verify change.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-10-09 09:41:41 +02:00
Andy ButlandandGitHub 16132b0075 Update Umbraco version for LTS release in template 2025-10-09 06:37:21 +02:00
Andy Butland dab9df3f10 Bumped version to 16.3.0-rc4. 2025-10-08 07:59:10 +02:00
Andy Butland b036eb3a75 Performance: Added request cache to media type retrieval in media picker validation (#20405)
* Added request cache to media type retrieval in media picker validation.

* Applied suggestions from code review.
2025-10-08 07:58:35 +02:00
Nikolaj Geisle 629e905187 Bump version 2025-10-06 21:21:14 +02:00
Andy ButlandandNikolaj Geisle bfd2594c7b Hybrid cache: Check for ContentCacheNode instead of object on exists for hybrid cache to ensure correct deserialization (closes #20352) (#20383)
Checked for ContentCacheNode instead of object on exists for hybrid cache to ensure correct deserialization.

(cherry picked from commit 184c17e2c8)
2025-10-06 21:20:56 +02:00
Andy Butland d9592aa26d Bumped version to 16.3.0-rc2. 2025-10-02 21:16:25 +02:00
Andy ButlandandKenn Jacobsen 97e0c79d94 Caching: Fixes regression of the caching of null representations for missing dictionary items (closes #20336 for 16) (#20349)
* Ports fix to regression of the caching of null representations for missing dictionary items.

* Fixed error raised in code review.

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2025-10-02 21:15:21 +02:00
2248 changed files with 29206 additions and 48717 deletions
+10 -3
View File
@@ -37,7 +37,7 @@ In order to work with the Umbraco source code locally, first make sure you have
### Familiarizing yourself with the code
Umbraco is a .NET application using C#. The solution is broken down into multiple projects. There are several class libraries. The `Umbraco.Web.UI` project is the main project that hosts the back office and login screen. This is the project you will want to run to see your changes.
Umbraco is a .NET application using C#. The solution is broken down into multiple projects. There are several class libraries. The `Umbraco.Web.UI` project is the main project that hosts the back office and login screen. This is the project you will want to run to see your changes.
There are two web projects in the solution with client-side assets based on TypeScript, `Umbraco.Web.UI.Client` and `Umbraco.Web.UI.Login`.
@@ -73,13 +73,20 @@ Just be careful not to include this change in your PR.
Conversely, if you are working on front-end only, you want to build the back-end once and then run it. Before you do so, update the configuration in `appSettings.json` to add the following under `Umbraco:Cms:Security`:
```
```json
"BackOfficeHost": "http://localhost:5173",
"AuthorizeCallbackPathName": "/oauth_complete",
"AuthorizeCallbackLogoutPathName": "/logout",
"AuthorizeCallbackErrorPathName": "/error"
"AuthorizeCallbackErrorPathName": "/error",
"BackOfficeTokenCookie": {
"Enabled": true,
"SameSite": "None"
}
```
> [!NOTE]
> If you get stuck in a login loop, try clearing your browser cookies for localhost, and make sure that the `BackOfficeTokenCookie` settings are correct. Namely, that `SameSite` should be set to `None` when running the front-end server separately.
Then run Umbraco from the command line.
```
+8
View File
@@ -38,6 +38,14 @@ Some important documentation links to get you started:
- [Getting to know Umbraco](https://docs.umbraco.com/umbraco-cms/fundamentals/get-to-know-umbraco)
- [Tutorials for creating a basic website and customizing the editing experience](https://docs.umbraco.com/umbraco-cms/tutorials/overview)
## Backoffice Preview
Want to see the latest backoffice UI in action? Check out our live preview:
**[backofficepreview.umbraco.com](https://backofficepreview.umbraco.com/)**
This preview is automatically deployed from the main branch and showcases the latest backoffice features and improvements. It runs from mock data and persistent edits are not supported.
## Get help
If you need a bit of feedback while building your Umbraco projects, we are [chatty on Discord](https://discord.umbraco.com). Our Discord server serves as a social space for all Umbracians. If you have any questions or need some help with a problem, head over to our [dedicated forum](https://forum.umbraco.com/) where the Umbraco Community will be happy to help.
+21 -6
View File
@@ -94,19 +94,34 @@ The solution contains 30 C# projects organized as follows:
## Common Tasks
### Frontend Development
For frontend-only changes:
1. Configure backend for frontend development:
```json
<!-- Add to src/Umbraco.Web.UI/appsettings.json under Umbraco:Cms:Security: -->
### 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"
"AuthorizeCallbackErrorPathName": "/error",
"BackOfficeTokenCookie": {
"Enabled": true,
"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:
@@ -52,7 +52,7 @@ jobs:
for (const item of items) {
const releaseLabels = (item.labels || [])
.map(l => (typeof l === "string" ? l : l.name)) // always get the name
.filter(n => typeof n === "string" && n.startsWith("release/"));
.filter(n => typeof n === "string" && n.startsWith("release/") && n !== "release/no-notes");
if (releaseLabels.length === 0) continue;
core.info(`#${item.number}: ${releaseLabels.join(", ")}`);
+5 -1
View File
@@ -101,10 +101,14 @@
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "https://localhost:44339",
"UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL": "https://localhost:44339",
"UMBRACO__CMS__SECURITY__BACKOFFICEHOST": "http://localhost:5173",
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKPATHNAME": "/oauth_complete",
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKLOGOUTPATHNAME": "/logout",
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKERRORPATHNAME": "/error"
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKERRORPATHNAME": "/error",
"UMBRACO__CMS__SECURITY__KEEPUSERLOGGEDIN": "true",
"UMBRACO__CMS__SECURITY__BACKOFFICETOKENCOOKIE__ENABLED": "true",
"UMBRACO__CMS__SECURITY__BACKOFFICETOKENCOOKIE__SAMESITE": "None"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Umbraco.Web.UI/Views"
-3
View File
@@ -1,8 +1,5 @@
{
"cSpell.words": [
"backoffice",
"pickable",
"Pickable",
"unprovide",
"Unproviding"
],
+2 -2
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Company>Umbraco HQ</Company>
<Authors>Umbraco</Authors>
<Copyright>Copyright © Umbraco $([System.DateTime]::Today.ToString('yyyy'))</Copyright>
@@ -40,7 +40,7 @@
<!-- Package Validation -->
<PropertyGroup>
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
<EnablePackageValidation>false</EnablePackageValidation> <!-- TODO (V17): Set to true with version 17.0.0 once this version is released. -->
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>16.0.0</PackageValidationBaselineVersion>
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
+58 -37
View File
@@ -5,35 +5,36 @@
</PropertyGroup>
<!-- Global packages (private, build-time packages for all projects) -->
<ItemGroup>
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.8.118" />
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" />
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<GlobalPackageReference Include="Umbraco.Code" Version="2.4.0" />
<GlobalPackageReference Include="Umbraco.Code" Version="2.3.0" />
<GlobalPackageReference Include="Umbraco.GitVersioning.Extensions" Version="0.2.0" />
</ItemGroup>
<!-- Microsoft packages -->
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.9.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.13.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.4.0" />
</ItemGroup>
<!-- Umbraco packages -->
<ItemGroup>
@@ -46,21 +47,21 @@
<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="HtmlAgilityPack" Version="1.12.1" />
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="MailKit" Version="4.14.0" />
<PackageVersion Include="MailKit" Version="4.11.0" />
<PackageVersion Include="Markdown" Version="2.2.1" />
<PackageVersion Include="MessagePack" Version="3.1.4" />
<PackageVersion Include="MessagePack" Version="3.1.3" />
<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="OpenIddict.Abstractions" Version="7.1.0" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="7.1.0" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="7.1.0" />
<PackageVersion Include="Serilog" Version="4.3.0" />
<PackageVersion Include="ncrontab" Version="3.3.3" />
<PackageVersion Include="NPoco" Version="5.7.1" />
<PackageVersion Include="NPoco.SqlServer" Version="5.7.1" />
<PackageVersion Include="OpenIddict.Abstractions" Version="6.2.1" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="6.2.1" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.2.1" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<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" />
@@ -70,13 +71,33 @@
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.6" />
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.5" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup>
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
<ItemGroup>
<!-- Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer brings in a vulnerable version of Azure.Identity -->
<!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
<!-- Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of System.Runtime.Caching -->
<PackageVersion Include="System.Runtime.Caching" Version="9.0.4" />
<!-- Dazinator.Extensions.FileProviders brings in a vulnerable version of System.Net.Http -->
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<!-- Examine brings in a vulnerable version of System.Security.Cryptography.Xml -->
<PackageVersion Include="System.Security.Cryptography.Xml" Version="9.0.4" />
<!-- Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc brings in a vulnerable version of System.Text.RegularExpressions -->
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<!-- OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
<!-- Take top-level depedendency on Microsoft.IdentityModel.JsonWebTokens, because OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.8.0" />
<!-- Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer and Dazinator.Extensions.FileProviders brings in a legacy version of System.Text.Encodings.Web -->
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.4" />
<!-- NPoco.SqlServer brings in a vulnerable version of Microsoft.Data.SqlClient -->
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.1" />
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
</ItemGroup>
</Project>
+89 -8
View File
@@ -34,6 +34,10 @@ parameters:
displayName: Upload API docs
type: boolean
default: false
- name: uploadDependencyTrack
displayName: Upload BOMs to Dependency Track
type: boolean
default: false
- name: forceReleaseTestFilter
displayName: Force to use the release test filters
type: boolean
@@ -103,6 +107,15 @@ stages:
command: build
projects: $(solution)
arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
- powershell: |
dotnet tool install --global CycloneDX
dotnet-CycloneDX $(solution) --output $(Build.ArtifactStagingDirectory)/bom --filename bom-dotnet.xml
displayName: 'Generate Backend BOM'
- powershell: |
npm install --global @cyclonedx/cyclonedx-npm
cyclonedx-npm -o $(Build.ArtifactStagingDirectory)\bom\bom-login.xml --ignore-npm-errors --verbose
displayName: Generate Login UI BOM
workingDirectory: src/Umbraco.Web.UI.Login
- task: PublishPipelineArtifact@1
displayName: Publish nupkg
inputs:
@@ -113,6 +126,11 @@ stages:
inputs:
targetPath: $(Build.SourcesDirectory)
artifactName: build_output
- task: PublishPipelineArtifact@1
displayName: Publish Backend BOM
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/bom
artifactName: bom-backend
- job: B
displayName: Build Bellissima Package
@@ -124,6 +142,11 @@ stages:
lfs: false,
fetchDepth: 500
- template: templates/backoffice-install.yml
- powershell: |
npm install --global @cyclonedx/cyclonedx-npm
cyclonedx-npm -o $(Build.ArtifactStagingDirectory)/bom/bom-backoffice.xml --ignore-npm-errors --verbose
displayName: Generate Backoffice UI BOM
workingDirectory: src/Umbraco.Web.UI.Client
- script: npm run build:for:npm
displayName: Run build:for:npm
workingDirectory: src/Umbraco.Web.UI.Client
@@ -140,6 +163,35 @@ stages:
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/npm
artifactName: npm
- publish: $(Build.ArtifactStagingDirectory)/bom
artifact: bom-frontend
displayName: 'Publish Frontend BOM'
- stage: E2E_BOM
displayName: E2E Tests BOM Generation
dependsOn: []
jobs:
- job:
displayName: E2E Generate BOM
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 }}
- powershell: |
npm install --global @cyclonedx/cyclonedx-npm
cyclonedx-npm -o $(Build.ArtifactStagingDirectory)/bom/bom-e2e.xml --ignore-npm-errors --verbose
displayName: Generate E2E Tests BOM
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
- publish: $(Build.ArtifactStagingDirectory)/bom
artifact: bom-e2e
displayName: 'Publish E2E BOM'
- stage: Build_Docs
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.buildApiDocs}}))
@@ -313,8 +365,8 @@ stages:
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
macOSPart1Of3:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
@@ -325,8 +377,8 @@ stages:
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
macOSPart3Of3:
vmImage: "macOS-latest"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and the ManagementApi namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
pool:
vmImage: $(vmImage)
variables:
@@ -389,8 +441,8 @@ stages:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
LinuxPart1Of3:
vmImage: "ubuntu-latest"
SA_PASSWORD: UmbracoIntegration123!
@@ -410,8 +462,8 @@ stages:
SA_PASSWORD: UmbracoIntegration123!
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
pool:
vmImage: $(vmImage)
steps:
@@ -668,6 +720,34 @@ stages:
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
- stage: Dependency_Track
displayName: Dependency Track
dependsOn:
- Build
- E2E_BOM
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadDependencyTrack}}))
variables:
# Determine Umbraco version based on whether it's a public release or not. If public release, use major version, else use full NuGet package version.
umbracoVersion: $[ iif(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'], stageDependencies.Build.A.outputs['build.NBGV_NuGetPackageVersion']) ]
jobs:
- template: templates/dependency-track.yml
parameters:
projectName: "Umbraco-CMS"
umbracoVersion: $(umbracoVersion)
projects:
- name: "Backend"
artifact: "bom-backend"
bomFilePath: "bom-dotnet.xml"
- name: "Login"
artifact: "bom-backend"
bomFilePath: "bom-login.xml"
- name: "Backoffice"
artifact: "bom-frontend"
bomFilePath: "bom-backoffice.xml"
- name: "E2E"
artifact: "bom-e2e"
bomFilePath: "bom-e2e.xml"
###############################################
## Release
###############################################
@@ -874,3 +954,4 @@ stages:
ContainerName: "$web"
BlobPrefix: v$(umbracoMajorVersion)/ui-api
CleanTargetBeforeCopy: true
+7 -27
View File
@@ -26,38 +26,18 @@ steps:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
- pwsh: |
"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
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
# Cache and restore NPM packages
- task: Cache@2
displayName: Cache NPM packages
inputs:
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
restoreKeys: |
npm_e2e | "$(Agent.OS)"
npm_e2e
path: ${{ parameters.npm_config_cache }}
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
- template: templates/e2e-install.yml
parameters:
nodeVersion: ${{ parameters.nodeVersion }}
npm_config_cache: ${{ parameters.npm_config_cache }}
PlaywrightUserEmail: ${{ parameters.PlaywrightUserEmail }}
PlaywrightPassword: ${{ parameters.PlaywrightPassword }}
ASPNETCORE_URLS: ${{ parameters.ASPNETCORE_URLS }}
# Install Template
- pwsh: |
+32 -52
View File
@@ -4,13 +4,11 @@ pr: none
trigger: none
schedules:
- cron: '0 0 * * *'
displayName: Daily midnight build
- cron: '0 6 * * *'
displayName: Daily 6 AM build (v16/dev)
branches:
include:
- v15/dev
- main
- v17/dev
- v16/dev
parameters:
- name: skipIntegrationTests
@@ -21,7 +19,7 @@ parameters:
- name: differentAppSettingsAcceptanceTests
displayName: Run acceptance tests with different app settings
type: boolean
default: true
default: false
- name: skipDefaultConfigAcceptanceTests
displayName: Skip tests with DefaultConfig
@@ -123,39 +121,31 @@ stages:
matrix:
# Windows:
# vmImage: 'windows-latest'
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
LinuxPart1Of4:
# We split the tests into 3 parts for each OS to reduce the time it takes to run them on the pipeline
LinuxPart1Of3:
vmImage: "ubuntu-latest"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
LinuxPart2Of4:
LinuxPart2Of3:
vmImage: "ubuntu-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of4:
LinuxPart3Of3:
vmImage: "ubuntu-latest"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
LinuxPart4Of4:
vmImage: "ubuntu-latest"
# Filter tests that are part of the ManagementApi namespace
testFilter: "(FullyQualifiedName~ManagementApi)"
macOSPart1Of4:
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
macOSPart1Of3:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
macOSPart2Of4:
macOSPart2Of3:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
macOSPart3Of4:
macOSPart3Of3:
vmImage: "macOS-latest"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
macOSPart4Of4:
vmImage: "macOS-latest"
# Filter tests that are part of the ManagementApi namespace.
testFilter: "(FullyQualifiedName~ManagementApi)"
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
pool:
vmImage: $(vmImage)
variables:
@@ -195,55 +185,43 @@ stages:
SA_PASSWORD: UmbracoAcceptance123!
strategy:
matrix:
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
WindowsPart1Of4:
# We split the tests into 3 parts for each OS to reduce the time it takes to run them on the pipeline
WindowsPart1Of3:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
WindowsPart2Of4:
WindowsPart2Of3:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
WindowsPart3Of4:
WindowsPart3Of3:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
WindowsPart4Of4:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are part of the ManagementApi namespace.
testFilter: "(FullyQualifiedName~ManagementApi)"
LinuxPart1Of4:
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
LinuxPart1Of3:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
LinuxPart2Of4:
LinuxPart2Of3:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of4:
LinuxPart3Of3:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
LinuxPart4Of4:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are part of the ManagementApi namespace.
testFilter: "(FullyQualifiedName~ManagementApi)"
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
pool:
vmImage: $(vmImage)
steps:
@@ -315,7 +293,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
@@ -496,7 +475,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
@@ -505,7 +485,7 @@ stages:
jobs:
- job:
displayName: E2E Tests with Different App settings (SQL Server)
condition: ${{ eq(parameters.differentAppSettingsAcceptanceTests, true) }}
condition: ${{ or(eq(parameters.differentAppSettingsAcceptanceTests, true), eq(parameters.skipDefaultConfigAcceptanceTests, true)) }}
timeoutInMinutes: 180
variables:
SA_PASSWORD: UmbracoAcceptance123!
@@ -695,4 +675,4 @@ stages:
--data "$PAYLOAD" \
"$SLACK_WEBHOOK_URL"
env:
SLACK_WEBHOOK_URL: $(E2ESLACKWEBHOOKURL)
SLACK_WEBHOOK_URL: $(E2ESLACKWEBHOOKURL)
+56
View File
@@ -0,0 +1,56 @@
parameters:
- name: projectName
type: string
- name: umbracoVersion
type: string
- name: projects
type: object
jobs:
- job: Create_DT_Project
displayName: Create Dependency Track Project
steps:
- checkout: none
- bash: |
project_id=$(curl --no-progress-meter -H "X-Api-Key: $(DT_API_KEY)" "$(DT_API_URL)/v1/project/lookup?name=${{ parameters.projectName }}&version=${{ parameters.umbracoVersion }}" | jq -r '.uuid')
if [ "$project_id" != "null" ] && [ -n "$project_id" ]; then
echo "Project '${{ parameters.projectName }}' with version '${{ parameters.umbracoVersion }}' already exists (ID: $project_id)."
else
project_id=$(curl --no-progress-meter \
-X PUT "$(DT_API_URL)/v1/project" \
-H "X-Api-Key: $(DT_API_KEY)" \
-H "Content-Type: application/json" \
-d '{"name": "${{ parameters.projectName }}", "version": "${{ parameters.umbracoVersion }}", "collectionLogic": "AGGREGATE_DIRECT_CHILDREN"}' \
| jq -r '.uuid')
if [ -z "$project_id" ] || [ "$project_id" == "null" ]; then
echo "Failed to create project '${{ parameters.projectName }}' version '${{ parameters.umbracoVersion }}'."
exit 1
fi
echo "Created project '${{ parameters.projectName }}' with version '${{ parameters.umbracoVersion }}' (ID: $project_id)."
fi
displayName: Ensure main project exists in Dependency Track
- ${{ each project in parameters.projects }}:
- job:
displayName: Upload ${{ project.name }} BOM
dependsOn: Create_DT_Project
steps:
- checkout: none
- download: current
artifact: ${{ project.artifact }}
displayName: Download ${{ project.artifact }} artifact
- script: |
curl --no-progress-meter --fail-with-body \
-X POST "$(DT_API_URL)/v1/bom" \
-H "X-Api-Key: $(DT_API_KEY)" \
-H "Content-Type: multipart/form-data" \
-F "autoCreate=true" \
-F "projectName=${{ parameters.projectName }}-${{ project.name }}" \
-F "projectVersion=${{ parameters.umbracoVersion }}" \
-F "parentName=${{ parameters.projectName }}" \
-F "parentVersion=${{ parameters.umbracoVersion }}" \
-F "bom=@$(Pipeline.Workspace)/${{ project.artifact }}/${{ project.bomFilePath }}"
displayName: Upload ${{ project.name }} BOM to Dependency Track
+49
View File
@@ -0,0 +1,49 @@
parameters:
- name: nodeVersion
type: string
default: ''
- name: npm_config_cache
type: string
default: ''
- name: PlaywrightUserEmail
type: string
default: ''
- name: PlaywrightPassword
type: string
default: ''
- name: ASPNETCORE_URLS
type: string
default: ''
steps:
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
inputs:
versionSpec: $(nodeVersion)
- pwsh: |
"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
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
# Cache and restore NPM packages
- task: Cache@2
displayName: Cache NPM packages
inputs:
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
restoreKeys: |
npm_e2e | "$(Agent.OS)"
npm_e2e
path: ${{ parameters.npm_config_cache }}
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
+2 -2
View File
@@ -1,7 +1,7 @@
{
"sdk": {
"version": "10.0.100-rc.1.25451.107",
"version": "9.0.306",
"rollForward": "latestFeature",
"allowPrerelease": true
"allowPrerelease": false
}
}
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Common.Accessors;
public sealed class RequestContextOutputExpansionStrategyAccessor : RequestContextServiceAccessorBase<IOutputExpansionStrategy>, IOutputExpansionStrategyAccessor
{
public RequestContextOutputExpansionStrategyAccessor(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
{
}
}
@@ -0,0 +1,20 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Umbraco.Cms.Api.Common.Accessors;
public abstract class RequestContextServiceAccessorBase<T>
where T : class
{
private readonly IHttpContextAccessor _httpContextAccessor;
protected RequestContextServiceAccessorBase(IHttpContextAccessor httpContextAccessor)
=> _httpContextAccessor = httpContextAccessor;
public bool TryGetValue([NotNullWhen(true)] out T? requestStartNodeService)
{
requestStartNodeService = _httpContextAccessor.HttpContext?.RequestServices.GetService<T>();
return requestStartNodeService is not null;
}
}
@@ -0,0 +1,230 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using OpenIddict.Server;
using OpenIddict.Validation;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Web.Common.Security;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
internal sealed class HideBackOfficeTokensHandler
: IOpenIddictServerHandler<OpenIddictServerEvents.ApplyTokenResponseContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ApplyAuthorizationResponseContext>,
IOpenIddictServerHandler<OpenIddictServerEvents.ExtractTokenRequestContext>,
IOpenIddictValidationHandler<OpenIddictValidationEvents.ProcessAuthenticationContext>,
INotificationHandler<UserLogoutSuccessNotification>
{
private const string RedactedTokenValue = "[redacted]";
private const string AccessTokenCookieKey = "__Host-umbAccessToken";
private const string RefreshTokenCookieKey = "__Host-umbRefreshToken";
private const string PkceCodeCookieKey = "__Host-umbPkceCode";
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly BackOfficeTokenCookieSettings _backOfficeTokenCookieSettings;
private readonly GlobalSettings _globalSettings;
public HideBackOfficeTokensHandler(
IHttpContextAccessor httpContextAccessor,
IDataProtectionProvider dataProtectionProvider,
IOptions<BackOfficeTokenCookieSettings> backOfficeTokenCookieSettings,
IOptions<GlobalSettings> globalSettings)
{
_httpContextAccessor = httpContextAccessor;
_dataProtectionProvider = dataProtectionProvider;
_backOfficeTokenCookieSettings = backOfficeTokenCookieSettings.Value;
_globalSettings = globalSettings.Value;
}
/// <summary>
/// This is invoked when tokens (access and refresh tokens) are issued to a client. For the back-office client,
/// we will intercept the response, write the tokens from the response into HTTP-only cookies, and redact the
/// tokens from the response, so they are not exposed to the client.
/// </summary>
public ValueTask HandleAsync(OpenIddictServerEvents.ApplyTokenResponseContext context)
{
if (context.Request?.ClientId is not Constants.OAuthClientIds.BackOffice)
{
// Only ever handle the back-office client.
return ValueTask.CompletedTask;
}
HttpContext httpContext = GetHttpContext();
if (context.Response.AccessToken is not null)
{
SetCookie(httpContext, AccessTokenCookieKey, context.Response.AccessToken);
context.Response.AccessToken = RedactedTokenValue;
}
if (context.Response.RefreshToken is not null)
{
SetCookie(httpContext, RefreshTokenCookieKey, context.Response.RefreshToken);
context.Response.RefreshToken = RedactedTokenValue;
}
return ValueTask.CompletedTask;
}
/// <summary>
/// This is invoked when a PKCE code is issued to the client. For the back-office client, we will intercept the
/// response, write the PKCE code from the response into a HTTP-only cookie, and redact the code from the response,
/// so it's not exposed to the client.
/// </summary>
public ValueTask HandleAsync(OpenIddictServerEvents.ApplyAuthorizationResponseContext context)
{
if (context.Request?.ClientId is not Constants.OAuthClientIds.BackOffice)
{
// Only ever handle the back-office client.
return ValueTask.CompletedTask;
}
if (context.Response.Code is not null)
{
SetCookie(GetHttpContext(), PkceCodeCookieKey, context.Response.Code);
context.Response.Code = RedactedTokenValue;
}
return ValueTask.CompletedTask;
}
/// <summary>
/// This is invoked when requesting new tokens.
/// </summary>
public ValueTask HandleAsync(OpenIddictServerEvents.ExtractTokenRequestContext context)
{
if (context.Request?.ClientId != Constants.OAuthClientIds.BackOffice)
{
// Only ever handle the back-office client.
return ValueTask.CompletedTask;
}
// Handle when the PKCE code is being exchanged for an access token.
if (context.Request.Code == RedactedTokenValue
&& TryGetCookie(PkceCodeCookieKey, out var code))
{
context.Request.Code = code;
// We won't need the PKCE cookie after this, let's remove it.
RemoveCookie(GetHttpContext(), PkceCodeCookieKey);
}
else
{
// PCKE codes should always be redacted. If we got here, someone might be trying to pass another PKCE
// code. For security reasons, explicitly discard the code (if any) to be on the safe side.
context.Request.Code = null;
}
// Handle when a refresh token is being exchanged for a new access token.
if (context.Request.RefreshToken == RedactedTokenValue
&& TryGetCookie(RefreshTokenCookieKey, out var refreshToken))
{
context.Request.RefreshToken = refreshToken;
}
else
{
// If we got here, either the refresh token was not redacted, or nothing was found in the refresh token cookie.
// If OpenIddict found a refresh 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 refresh tokens to be explicitly redacted.
context.Request.RefreshToken = null;
}
return ValueTask.CompletedTask;
}
/// <summary>
/// This is invoked when extracting the auth context for a client request.
/// </summary>
public ValueTask HandleAsync(OpenIddictValidationEvents.ProcessAuthenticationContext context)
{
// For the back-office client, this only happens when an access token is sent to the API.
if (context.AccessToken != RedactedTokenValue)
{
return ValueTask.CompletedTask;
}
if (TryGetCookie(AccessTokenCookieKey, out var accessToken))
{
context.AccessToken = accessToken;
}
return ValueTask.CompletedTask;
}
public void Handle(UserLogoutSuccessNotification notification)
{
HttpContext? context = _httpContextAccessor.HttpContext;
if (context is null)
{
// For some reason there is no ambient HTTP context, so we can't clean up the cookies.
// This is OK, because the tokens in the cookies have already been revoked at user sign-out,
// so the cookie clean-up is mostly cosmetic.
return;
}
context.Response.Cookies.Delete(AccessTokenCookieKey);
context.Response.Cookies.Delete(RefreshTokenCookieKey);
}
private HttpContext GetHttpContext()
=> _httpContextAccessor.GetRequiredHttpContext();
private void SetCookie(HttpContext httpContext, string key, string value)
{
var cookieValue = EncryptionHelper.Encrypt(value, _dataProtectionProvider);
RemoveCookie(httpContext, key);
httpContext.Response.Cookies.Append(key, cookieValue, GetCookieOptions(httpContext));
}
private void RemoveCookie(HttpContext httpContext, string key)
=> httpContext.Response.Cookies.Delete(key, GetCookieOptions(httpContext));
private CookieOptions GetCookieOptions(HttpContext httpContext) =>
new()
{
// Prevent the client-side scripts from accessing the cookie.
HttpOnly = true,
// Mark the cookie as essential to the application, to enforce it despite any
// data collection consent options. This aligns with how ASP.NET Core Identity
// does when writing cookies for cookie authentication.
IsEssential = true,
// Cookie path must be root for optimal security.
Path = "/",
// For optimal security, the cooke must be secure. However, Umbraco allows for running development
// environments over HTTP, so we need to take that into account here.
// Thus, we will make the cookie secure if:
// - HTTPS is explicitly enabled by config (default for production environments), or
// - The current request is over HTTPS (meaning the environment supports it regardless of config).
Secure = _globalSettings.UseHttps || httpContext.Request.IsHttps,
// SameSite is configurable (see BackOfficeTokenCookieSettings for defaults):
SameSite = ParseSameSiteMode(_backOfficeTokenCookieSettings.SameSite),
};
private bool TryGetCookie(string key, [NotNullWhen(true)] out string? value)
{
if (GetHttpContext().Request.Cookies.TryGetValue(key, out var cookieValue))
{
value = EncryptionHelper.Decrypt(cookieValue, _dataProtectionProvider);
return true;
}
value = null;
return false;
}
private static SameSiteMode ParseSameSiteMode(string sameSiteMode) =>
Enum.TryParse(sameSiteMode, ignoreCase: true, out SameSiteMode result)
? result
: throw new ArgumentException($"The provided {nameof(sameSiteMode)} value could not be parsed into as SameSiteMode value.", nameof(sameSiteMode));
}
@@ -9,8 +9,9 @@ using Umbraco.Cms.Api.Common.Security;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.BackgroundJobs;
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.DistributedJobs;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
@@ -28,6 +29,11 @@ public static class UmbracoBuilderAuthExtensions
private static void ConfigureOpenIddict(IUmbracoBuilder builder)
{
// Optionally hide tokens from the back-office.
var hideBackOfficeTokens = (builder.Config
.GetSection(Constants.Configuration.ConfigBackOfficeTokenCookie)
.Get<BackOfficeTokenCookieSettings>() ?? new BackOfficeTokenCookieSettings()).Enabled;
builder.Services.AddOpenIddict()
// Register the OpenIddict server components.
.AddServer(options =>
@@ -113,6 +119,28 @@ public static class UmbracoBuilderAuthExtensions
{
configuration.UseSingletonHandler<ProcessRequestContextHandler>().SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ResolveRequestUri.Descriptor.Order - 1);
});
if (hideBackOfficeTokens)
{
options.AddEventHandler<OpenIddictServerEvents.ApplyTokenResponseContext>(configuration =>
{
configuration
.UseSingletonHandler<HideBackOfficeTokensHandler>()
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ProcessJsonResponse<OpenIddictServerEvents.ApplyTokenResponseContext>.Descriptor.Order - 1);
});
options.AddEventHandler<OpenIddictServerEvents.ApplyAuthorizationResponseContext>(configuration =>
{
configuration
.UseSingletonHandler<HideBackOfficeTokensHandler>()
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.Authentication.ProcessQueryResponse.Descriptor.Order - 1);
});
options.AddEventHandler<OpenIddictServerEvents.ExtractTokenRequestContext>(configuration =>
{
configuration
.UseSingletonHandler<HideBackOfficeTokensHandler>()
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest<OpenIddictServerEvents.ExtractTokenRequestContext>.Descriptor.Order + 1);
});
}
})
// Register the OpenIddict validation components.
@@ -137,9 +165,25 @@ public static class UmbracoBuilderAuthExtensions
{
configuration.UseSingletonHandler<ProcessRequestContextHandler>().SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ResolveRequestUri.Descriptor.Order - 1);
});
if (hideBackOfficeTokens)
{
options.AddEventHandler<OpenIddictValidationEvents.ProcessAuthenticationContext>(configuration =>
{
configuration
.UseSingletonHandler<HideBackOfficeTokensHandler>()
// IMPORTANT: the handler must be AFTER the built-in query string handler, because the client-side SignalR library sometimes appends access tokens to the query string.
.SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ExtractAccessTokenFromQueryString.Descriptor.Order + 1);
});
}
});
builder.Services.AddSingleton<IDistributedBackgroundJob, OpenIddictCleanupJob>();
builder.Services.AddRecurringBackgroundJob<OpenIddictCleanupJob>();
builder.Services.ConfigureOptions<ConfigureOpenIddict>();
if (hideBackOfficeTokens)
{
builder.AddNotificationHandler<UserLogoutSuccessNotification, HideBackOfficeTokensHandler>();
}
}
}
@@ -8,11 +8,11 @@ namespace Umbraco.Extensions;
public static class MethodInfoApiCommonExtensions
{
public static string GetMapToApiVersionAttributeValue(this MethodInfo methodInfo)
public static string? GetMapToApiVersionAttributeValue(this MethodInfo methodInfo)
{
MapToApiVersionAttribute[] mapToApis = methodInfo.GetCustomAttributes(typeof(MapToApiVersionAttribute), inherit: true).Cast<MapToApiVersionAttribute>().ToArray();
return string.Join("|", mapToApis.SelectMany(x => x.Versions));
return string.Join("|", mapToApis.SelectMany(x=>x.Versions));
}
public static string? GetMapToApiAttributeValue(this MethodInfo methodInfo)
@@ -15,6 +15,20 @@ public class SubTypesSelector : ISubTypesSelector
private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers;
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
[Obsolete("The settings parameter is not required anymore, use the other constructor instead. Scheduled for removal in Umbraco 17.")]
public SubTypesSelector(
IOptions<GlobalSettings> settings,
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor httpContextAccessor,
IEnumerable<ISubTypesHandler> subTypeHandlers,
IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
_hostingEnvironment = hostingEnvironment;
_httpContextAccessor = httpContextAccessor;
_subTypeHandlers = subTypeHandlers;
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}
public SubTypesSelector(
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor httpContextAccessor,
@@ -0,0 +1,148 @@
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.Rendering;
public class ElementOnlyOutputExpansionStrategy : IOutputExpansionStrategy
{
protected const string All = "$all";
protected const string None = "";
protected const string ExpandParameterName = "expand";
protected const string FieldsParameterName = "fields";
private readonly IApiPropertyRenderer _propertyRenderer;
protected Stack<Node?> ExpandProperties { get; } = new();
protected Stack<Node?> IncludeProperties { get; } = new();
public ElementOnlyOutputExpansionStrategy(
IApiPropertyRenderer propertyRenderer)
{
_propertyRenderer = propertyRenderer;
}
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}");
public virtual IDictionary<string, object?> MapMediaProperties(IPublishedContent media, bool skipUmbracoProperties = true)
{
if (media.ItemType != PublishedItemType.Media)
{
throw new ArgumentException($"Invalid item type. This method can only be used with item type {PublishedItemType.Media}, got: {media.ItemType}");
}
IPublishedProperty[] properties = media
.Properties
.Where(p => skipUmbracoProperties is false || p.Alias.StartsWith("umbraco") is false)
.ToArray();
return properties.Any()
? MapProperties(properties)
: new Dictionary<string, object?>();
}
public virtual IDictionary<string, object?> MapElementProperties(IPublishedElement element)
=> MapProperties(element.Properties, true);
private IDictionary<string, object?> MapProperties(IEnumerable<IPublishedProperty> properties, bool forceExpandProperties = false)
{
Node? currentExpandProperties = ExpandProperties.Count > 0 ? ExpandProperties.Peek() : null;
if (ExpandProperties.Count > 1 && currentExpandProperties is null && forceExpandProperties is false)
{
return new Dictionary<string, object?>();
}
Node? currentIncludeProperties = IncludeProperties.Count > 0 ? IncludeProperties.Peek() : null;
var result = new Dictionary<string, object?>();
foreach (IPublishedProperty property in properties)
{
Node? nextIncludeProperties = GetNextProperties(currentIncludeProperties, property.Alias);
if (currentIncludeProperties is not null && currentIncludeProperties.Items.Any() && nextIncludeProperties is null)
{
continue;
}
Node? nextExpandProperties = GetNextProperties(currentExpandProperties, property.Alias);
IncludeProperties.Push(nextIncludeProperties);
ExpandProperties.Push(nextExpandProperties);
result[property.Alias] = GetPropertyValue(property);
ExpandProperties.Pop();
IncludeProperties.Pop();
}
return result;
}
private Node? GetNextProperties(Node? currentProperties, string propertyAlias)
=> currentProperties?.Items.FirstOrDefault(i => i.Key == All)
?? currentProperties?.Items.FirstOrDefault(i => i.Key == "properties")?.Items.FirstOrDefault(i => i.Key == All || i.Key == propertyAlias);
private object? GetPropertyValue(IPublishedProperty property)
=> _propertyRenderer.GetPropertyValue(property, ExpandProperties.Peek() is not null);
protected sealed class Node
{
public string Key { get; private set; } = string.Empty;
public List<Node> Items { get; } = new();
public static Node Parse(string value)
{
// verify that there are as many start brackets as there are end brackets
if (value.CountOccurrences("[") != value.CountOccurrences("]"))
{
throw new ArgumentException("Value did not contain an equal number of start and end brackets");
}
// verify that the value does not start with a start bracket
if (value.StartsWith("["))
{
throw new ArgumentException("Value cannot start with a bracket");
}
// verify that there are no empty brackets
if (value.Contains("[]"))
{
throw new ArgumentException("Value cannot contain empty brackets");
}
var stack = new Stack<Node>();
var root = new Node { Key = "root" };
stack.Push(root);
var currentNode = new Node();
root.Items.Add(currentNode);
foreach (char c in value)
{
switch (c)
{
case '[': // Start a new node, child of the current node
stack.Push(currentNode);
currentNode = new Node();
stack.Peek().Items.Add(currentNode);
break;
case ',': // Start a new node, but at the same level of the current node
currentNode = new Node();
stack.Peek().Items.Add(currentNode);
break;
case ']': // Back to parent of the current node
currentNode = stack.Pop();
break;
default: // Add char to current node key
currentNode.Key += c;
break;
}
}
return root;
}
}
}
@@ -16,6 +16,11 @@
<PackageReference Include="OpenIddict.AspNetCore" />
</ItemGroup>
<ItemGroup>
<!-- Take top-level depedendency on OpenIddict.AspNetCore depends on a vulnerable version -->
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
@@ -35,28 +35,35 @@ public static class UmbracoBuilderExtensions
builder.Services.AddScoped<IRequestStartItemProvider, RequestStartItemProvider>();
builder.Services.AddScoped<RequestContextOutputExpansionStrategy>();
builder.Services.AddScoped<RequestContextOutputExpansionStrategyV2>();
builder.Services.AddScoped<IOutputExpansionStrategy>(provider =>
{
HttpContext? httpContext = provider.GetRequiredService<IHttpContextAccessor>().HttpContext;
ApiVersion? apiVersion = httpContext?.GetRequestedApiVersion();
if (apiVersion is null)
{
return provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();
}
// V1 of the Delivery API uses a different expansion strategy than V2+
return apiVersion.MajorVersion == 1
? provider.GetRequiredService<RequestContextOutputExpansionStrategy>()
: provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();
});
builder.Services.AddUnique<IOutputExpansionStrategy>(
provider =>
{
HttpContext? httpContext = provider.GetRequiredService<IHttpContextAccessor>().HttpContext;
ApiVersion? apiVersion = httpContext?.GetRequestedApiVersion();
if (apiVersion is null)
{
return provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();
}
// V1 of the Delivery API uses a different expansion strategy than V2+
return apiVersion.MajorVersion == 1
? provider.GetRequiredService<RequestContextOutputExpansionStrategy>()
: provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();
},
ServiceLifetime.Scoped);
builder.Services.AddSingleton<IRequestCultureService, RequestCultureService>();
builder.Services.AddSingleton<IRequestSegmmentService, RequestSegmentService>();
builder.Services.AddSingleton<IRequestSegmentService, RequestSegmentService>();
builder.Services.AddSingleton<IRequestRoutingService, RequestRoutingService>();
builder.Services.AddSingleton<IRequestRedirectService, RequestRedirectService>();
builder.Services.AddSingleton<IRequestPreviewService, RequestPreviewService>();
builder.Services.AddSingleton<IOutputExpansionStrategyAccessor, RequestContextOutputExpansionStrategyAccessor>();
// Webooks register a more basic implementation, remove it.
builder.Services.AddUnique<IOutputExpansionStrategyAccessor, RequestContextOutputExpansionStrategyAccessor>(ServiceLifetime.Singleton);
builder.Services.AddSingleton<IRequestStartItemProviderAccessor, RequestContextRequestStartItemProviderAccessor>();
builder.Services.AddSingleton<IApiAccessService, ApiAccessService>();
builder.Services.AddSingleton<IApiContentQueryService, ApiContentQueryService>();
builder.Services.AddSingleton<IApiContentQueryProvider, ApiContentQueryProvider>();
@@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Delivery.Querying;
@@ -11,6 +14,30 @@ public abstract class QueryOptionBase
private readonly IApiDocumentUrlService _apiDocumentUrlService;
private readonly IVariationContextAccessor _variationContextAccessor;
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public QueryOptionBase(
IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService)
: this(
requestRoutingService,
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>())
{
}
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public QueryOptionBase(
IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService,
IRequestPreviewService requestPreviewService,
IRequestCultureService requestCultureService,
IApiDocumentUrlService apiDocumentUrlService,
IVariationContextAccessor variationContextAccessor)
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
{
}
public QueryOptionBase(
IRequestRoutingService requestRoutingService,
IRequestPreviewService requestPreviewService,
@@ -1,6 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Delivery.Indexing.Selectors;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services.Navigation;
namespace Umbraco.Cms.Api.Delivery.Querying.Selectors;
@@ -10,6 +13,46 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
private readonly IDocumentNavigationQueryService _navigationQueryService;
private const string AncestorsSpecifier = "ancestors:";
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public AncestorsSelector(
IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService,
IDocumentNavigationQueryService navigationQueryService,
IRequestPreviewService requestPreviewService)
: this(
requestRoutingService,
requestPreviewService,
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(),
navigationQueryService)
{
}
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public AncestorsSelector(
IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService,
IDocumentNavigationQueryService navigationQueryService)
: this(
requestRoutingService,
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(),
navigationQueryService)
{
}
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
public AncestorsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
: this(
requestRoutingService,
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(),
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>())
{
}
public AncestorsSelector(
IRequestRoutingService requestRoutingService,
IRequestPreviewService requestPreviewService,
@@ -19,6 +62,18 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
: base(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
=> _navigationQueryService = navigationQueryService;
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
public AncestorsSelector(
IRequestRoutingService requestRoutingService,
IPublishedContentCache publishedContentCache,
IRequestPreviewService requestPreviewService,
IApiDocumentUrlService apiDocumentUrlService,
IVariationContextAccessor variationContextAccessor,
IDocumentNavigationQueryService navigationQueryService)
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor, navigationQueryService)
{
}
/// <inheritdoc />
public bool CanHandle(string query)
=> query.StartsWith(AncestorsSpecifier, StringComparison.OrdinalIgnoreCase);
@@ -12,6 +12,27 @@ public sealed class ChildrenSelector : QueryOptionBase, ISelectorHandler
{
private const string ChildrenSpecifier = "children:";
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public ChildrenSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
: this(
requestRoutingService,
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>())
{
}
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public ChildrenSelector(
IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService,
IRequestPreviewService requestPreviewService,
IApiDocumentUrlService apiDocumentUrlService,
IVariationContextAccessor variationContextAccessor)
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
{
}
public ChildrenSelector(
IRequestRoutingService requestRoutingService,
IRequestPreviewService requestPreviewService,
@@ -12,6 +12,27 @@ public sealed class DescendantsSelector : QueryOptionBase, ISelectorHandler
{
private const string DescendantsSpecifier = "descendants:";
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public DescendantsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
: this(
requestRoutingService,
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>())
{
}
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public DescendantsSelector(
IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService,
IRequestPreviewService requestPreviewService,
IApiDocumentUrlService apiDocumentUrlService,
IVariationContextAccessor variationContextAccessor)
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
{
}
public DescendantsSelector(
IRequestRoutingService requestRoutingService,
IRequestPreviewService requestPreviewService,
@@ -1,62 +1,25 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Api.Common.Rendering;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Delivery.Rendering;
internal sealed class RequestContextOutputExpansionStrategyV2 : IOutputExpansionStrategy
internal sealed class RequestContextOutputExpansionStrategyV2 : ElementOnlyOutputExpansionStrategy, IOutputExpansionStrategy
{
private const string All = "$all";
private const string None = "";
private const string ExpandParameterName = "expand";
private const string FieldsParameterName = "fields";
private readonly IApiPropertyRenderer _propertyRenderer;
private readonly ILogger<RequestContextOutputExpansionStrategyV2> _logger;
private readonly Stack<Node?> _expandProperties;
private readonly Stack<Node?> _includeProperties;
public RequestContextOutputExpansionStrategyV2(
IHttpContextAccessor httpContextAccessor,
IApiPropertyRenderer propertyRenderer,
ILogger<RequestContextOutputExpansionStrategyV2> logger)
: base(propertyRenderer)
{
_propertyRenderer = propertyRenderer;
_logger = logger;
_expandProperties = new Stack<Node?>();
_includeProperties = new Stack<Node?>();
InitializeExpandAndInclude(httpContextAccessor);
}
public 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}");
public IDictionary<string, object?> MapMediaProperties(IPublishedContent media, bool skipUmbracoProperties = true)
{
if (media.ItemType != PublishedItemType.Media)
{
throw new ArgumentException($"Invalid item type. This method can only be used with item type {PublishedItemType.Media}, got: {media.ItemType}");
}
IPublishedProperty[] properties = media
.Properties
.Where(p => skipUmbracoProperties is false || p.Alias.StartsWith("umbraco") is false)
.ToArray();
return properties.Any()
? MapProperties(properties)
: new Dictionary<string, object?>();
}
public IDictionary<string, object?> MapElementProperties(IPublishedElement element)
=> MapProperties(element.Properties, true);
private void InitializeExpandAndInclude(IHttpContextAccessor httpContextAccessor)
{
string? QueryValue(string key) => httpContextAccessor.HttpContext?.Request.Query[key];
@@ -66,7 +29,7 @@ internal sealed class RequestContextOutputExpansionStrategyV2 : IOutputExpansion
try
{
_expandProperties.Push(Node.Parse(toExpand));
ExpandProperties.Push(Node.Parse(toExpand));
}
catch (ArgumentException ex)
{
@@ -76,7 +39,7 @@ internal sealed class RequestContextOutputExpansionStrategyV2 : IOutputExpansion
try
{
_includeProperties.Push(Node.Parse(toInclude));
IncludeProperties.Push(Node.Parse(toInclude));
}
catch (ArgumentException ex)
{
@@ -84,102 +47,4 @@ internal sealed class RequestContextOutputExpansionStrategyV2 : IOutputExpansion
throw new ArgumentException($"Could not parse the '{FieldsParameterName}' parameter: {ex.Message}");
}
}
private IDictionary<string, object?> MapProperties(IEnumerable<IPublishedProperty> properties, bool forceExpandProperties = false)
{
Node? currentExpandProperties = _expandProperties.Peek();
if (_expandProperties.Count > 1 && currentExpandProperties is null && forceExpandProperties is false)
{
return new Dictionary<string, object?>();
}
Node? currentIncludeProperties = _includeProperties.Peek();
var result = new Dictionary<string, object?>();
foreach (IPublishedProperty property in properties)
{
Node? nextIncludeProperties = GetNextProperties(currentIncludeProperties, property.Alias);
if (currentIncludeProperties is not null && currentIncludeProperties.Items.Any() && nextIncludeProperties is null)
{
continue;
}
Node? nextExpandProperties = GetNextProperties(currentExpandProperties, property.Alias);
_includeProperties.Push(nextIncludeProperties);
_expandProperties.Push(nextExpandProperties);
result[property.Alias] = GetPropertyValue(property);
_expandProperties.Pop();
_includeProperties.Pop();
}
return result;
}
private Node? GetNextProperties(Node? currentProperties, string propertyAlias)
=> currentProperties?.Items.FirstOrDefault(i => i.Key == All)
?? currentProperties?.Items.FirstOrDefault(i => i.Key == "properties")?.Items.FirstOrDefault(i => i.Key == All || i.Key == propertyAlias);
private object? GetPropertyValue(IPublishedProperty property)
=> _propertyRenderer.GetPropertyValue(property, _expandProperties.Peek() is not null);
private sealed class Node
{
public string Key { get; private set; } = string.Empty;
public List<Node> Items { get; } = new();
public static Node Parse(string value)
{
// verify that there are as many start brackets as there are end brackets
if (value.CountOccurrences("[") != value.CountOccurrences("]"))
{
throw new ArgumentException("Value did not contain an equal number of start and end brackets");
}
// verify that the value does not start with a start bracket
if (value.StartsWith("["))
{
throw new ArgumentException("Value cannot start with a bracket");
}
// verify that there are no empty brackets
if (value.Contains("[]"))
{
throw new ArgumentException("Value cannot contain empty brackets");
}
var stack = new Stack<Node>();
var root = new Node { Key = "root" };
stack.Push(root);
var currentNode = new Node();
root.Items.Add(currentNode);
foreach (char c in value)
{
switch (c)
{
case '[': // Start a new node, child of the current node
stack.Push(currentNode);
currentNode = new Node();
stack.Peek().Items.Add(currentNode);
break;
case ',': // Start a new node, but at the same level of the current node
currentNode = new Node();
stack.Peek().Items.Add(currentNode);
break;
case ']': // Back to parent of the current node
currentNode = stack.Pop();
break;
default: // Add char to current node key
currentNode.Key += c;
break;
}
}
return root;
}
}
}
@@ -66,14 +66,9 @@ internal sealed class DeliveryApiItemsEndpointsMatcherPolicy : MatcherPolicy, IE
{
ApiVersion[]? supportedApiVersions = endpoint.Metadata.GetMetadata<MapToApiVersionAttribute>()?.Versions.ToArray();
// If the endpoint is versioned, the requested API version must be among the API versions supported by the endpoint.
// If the endpoint is NOT versioned, it cannot be used with a requested API version.
if (supportedApiVersions is not null && requestedApiVersion is not null)
{
return supportedApiVersions.Contains(requestedApiVersion);
}
return requestedApiVersion is null;
// if the endpoint is versioned, the requested API version must be among the API versions supported by the endpoint.
// if the endpoint is NOT versioned, it cannot be used with a requested API version
return supportedApiVersions?.Contains(requestedApiVersion) ?? requestedApiVersion is null;
}
private static bool IsByIdsController(ControllerActionDescriptor? controllerActionDescriptor)
@@ -66,7 +66,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
private IPublishedContent? TryGetByPath(string path, IPublishedMediaCache mediaCache)
{
var segments = path.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries);
IEnumerable<IPublishedContent> currentChildren = GetRootContent(mediaCache);
IEnumerable<IPublishedContent> currentChildren = mediaCache.GetAtRoot();
IPublishedContent? resolvedMedia = null;
foreach (var segment in segments)
@@ -103,7 +103,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
IPublishedMediaCache mediaCache = GetRequiredPublishedMediaCache();
if (childrenOf.Trim(Constants.CharArrays.ForwardSlash).Length == 0)
{
return GetRootContent(mediaCache);
return mediaCache.GetAtRoot();
}
IPublishedContent? parent = Guid.TryParse(childrenOf, out Guid parentKey)
@@ -196,8 +196,4 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
return Attempt.SucceedWithStatus(ApiMediaQueryOperationStatus.Success, result);
}
private IEnumerable<IPublishedContent> GetRootContent(IPublishedMediaCache mediaCache)
=> _mediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys) is false ? []
: rootKeys.Select(x => mediaCache.GetById(false, x)).WhereNotNull();
}
@@ -19,6 +19,12 @@ internal sealed partial class RequestCultureService : RequestHeaderHandler, IReq
return ValidLanguageHeaderRegex().IsMatch(acceptLanguage) ? acceptLanguage : null;
}
[Obsolete("Use IVariationContextAccessor to manipulate the variation context. Scheduled for removal in V17.")]
public void SetRequestCulture(string culture)
{
// no-op
}
// at the time of writing we're introducing this to get rid of accept-language header values like "en-GB,en-US;q=0.9,en;q=0.8",
// so we don't want to be too restrictive in this regex - keep it simple for now.
[GeneratedRegex(@"^[\w-]*$")]
@@ -3,7 +3,6 @@
<Title>Umbraco CMS - Delivery API</Title>
<Description>Contains the presentation layer for the Umbraco CMS Delivery API.</Description>
</PropertyGroup>
<PropertyGroup>
<!--
TODO: Fix and remove overrides:
@@ -12,18 +11,11 @@
-->
<WarningsNotAsErrors>$(WarningsNotAsErrors),ASP0019,CS0618,CS0612</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Cms.Api.Common\Umbraco.Cms.Api.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Linq.Async">
<ExcludeAssets>compile</ExcludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
@@ -220,44 +219,10 @@ public class ConfigureBackOfficeCookieOptions : IConfigureNamedOptions<CookieAut
}
return Task.CompletedTask;
},
// FIXME: We want to change this over to using an attribute on the backoffice controllers
// See this for more: https://github.com/dotnet/aspnetcore/issues/63093#issuecomment-3201530217
OnRedirectToLogin = context =>
{
if (IsXhr(context.Request))
{
context.Response.Headers.Location = context.RedirectUri;
context.Response.StatusCode = 401;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.CompletedTask;
},
OnRedirectToAccessDenied = context =>
{
if (IsXhr(context.Request))
{
context.Response.Headers.Location = context.RedirectUri;
context.Response.StatusCode = 403;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.CompletedTask;
},
}
};
}
private bool IsXhr(HttpRequest request) =>
string.Equals(request.Query[HeaderNames.XRequestedWith], "XMLHttpRequest", StringComparison.Ordinal) ||
string.Equals(request.Headers.XRequestedWith, "XMLHttpRequest", StringComparison.Ordinal);
/// <summary>
/// Ensures the ticket is renewed if the <see cref="SecuritySettings.KeepUserLoggedIn" /> is set to true
/// and the current request is for the get user seconds endpoint
@@ -29,6 +29,39 @@ public abstract class ContentCollectionControllerBase<TContent, TCollectionRespo
_flagProviders = flagProvider;
}
[Obsolete("Use the constructer with all parameters. To be removed in Umbraco 18")]
protected ContentCollectionControllerBase(IUmbracoMapper mapper)
: this(mapper, StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>())
{
}
[Obsolete("This method is no longer used and will be removed in Umbraco 17.")]
protected IActionResult CollectionResult(ListViewPagedModel<TContent> result)
{
PagedModel<TContent> collectionItemsResult = result.Items;
ListViewConfiguration collectionConfiguration = result.ListViewConfiguration;
var collectionPropertyAliases = collectionConfiguration
.IncludeProperties
.Select(p => p.Alias)
.WhereNotNull()
.ToArray();
List<TCollectionResponseModel> collectionResponseModels =
_mapper.MapEnumerable<TContent, TCollectionResponseModel>(collectionItemsResult.Items, context =>
{
context.SetIncludedProperties(collectionPropertyAliases);
});
var pageViewModel = new PagedViewModel<TCollectionResponseModel>
{
Items = collectionResponseModels,
Total = collectionItemsResult.Total,
};
return Ok(pageViewModel);
}
/// <summary>
/// Creates a collection result from the provided collection response models and total number of items.
/// </summary>
@@ -0,0 +1,40 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
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;
[ApiVersion("1.0")]
[Obsolete("Please use ReferencedByDataTypeController and the referenced-by endpoint. Scheduled for removal in Umbraco 17.")]
public class ReferencesDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IDataTypeReferencePresentationFactory _dataTypeReferencePresentationFactory;
public ReferencesDataTypeController(IDataTypeService dataTypeService, IDataTypeReferencePresentationFactory dataTypeReferencePresentationFactory)
{
_dataTypeService = dataTypeService;
_dataTypeReferencePresentationFactory = dataTypeReferencePresentationFactory;
}
[HttpGet("{id:guid}/references")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DataTypeReferenceResponseModel[]), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> References(CancellationToken cancellationToken, Guid id)
{
Attempt<IReadOnlyDictionary<Udi, IEnumerable<string>>, DataTypeOperationStatus> result = await _dataTypeService.GetReferencesAsync(id);
if (result.Success == false)
{
return DataTypeOperationStatusResult(result.Status);
}
DataTypeReferenceResponseModel[] viewModels = _dataTypeReferencePresentationFactory.CreateDataTypeReferenceViewModels(result.Result).ToArray();
return Ok(viewModels);
}
}
@@ -2,10 +2,13 @@ using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Querying;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
@@ -19,6 +22,31 @@ public class ByKeyDocumentController : DocumentControllerBase
private readonly IDocumentPresentationFactory _documentPresentationFactory;
private readonly IContentQueryService _contentQueryService;
[Obsolete("Scheduled for removal in v17")]
public ByKeyDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
IDocumentPresentationFactory documentPresentationFactory)
{
_authorizationService = authorizationService;
_documentPresentationFactory = documentPresentationFactory;
_contentQueryService = StaticServiceProvider.Instance.GetRequiredService<IContentQueryService>();
}
// needed for greedy selection until other constructor remains in v17
[Obsolete("Scheduled for removal in v17")]
public ByKeyDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
IDocumentPresentationFactory documentPresentationFactory,
IContentQueryService contentQueryService)
{
_authorizationService = authorizationService;
_documentPresentationFactory = documentPresentationFactory;
_contentQueryService = contentQueryService;
}
[ActivatorUtilitiesConstructor]
public ByKeyDocumentController(
IAuthorizationService authorizationService,
IDocumentPresentationFactory documentPresentationFactory,
@@ -23,6 +23,12 @@ public abstract class DocumentCollectionControllerBase : ContentCollectionContro
{
}
[Obsolete("Please use the constructor with all parameters. Scheduled to be removed in V18")]
protected DocumentCollectionControllerBase(IUmbracoMapper mapper)
: base(mapper)
{
}
protected IActionResult CollectionOperationStatusResult(ContentCollectionOperationStatus status)
=> ContentCollectionOperationStatusResult(status, "document");
}
@@ -1,53 +0,0 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
public class DocumentPreviewUrlController : DocumentControllerBase
{
private readonly IContentService _contentService;
private readonly IDocumentUrlFactory _documentUrlFactory;
public DocumentPreviewUrlController(
IContentService contentService,
IDocumentUrlFactory documentUrlFactory)
{
_contentService = contentService;
_documentUrlFactory = documentUrlFactory;
}
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/preview-url")]
[ProducesResponseType(typeof(DocumentUrlInfo), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetPreviewUrl(Guid id, string providerAlias, string? culture, string? segment)
{
IContent? content = _contentService.GetById(id);
if (content is null)
{
return NotFound(new ProblemDetailsBuilder()
.WithTitle("Document not found")
.WithDetail("The requested document did not exist.")
.Build());
}
DocumentUrlInfo? previewUrlInfo = await _documentUrlFactory.GetPreviewUrlAsync(content, providerAlias, culture, segment);
if (previewUrlInfo is null)
{
return BadRequest(new ProblemDetailsBuilder()
.WithTitle("No preview URL for document")
.WithDetail("Failed to produce a preview URL for the requested document.")
.Build());
}
return Ok(previewUrlInfo);
}
}
@@ -1,31 +1,60 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
public class DomainsController : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IDomainService _domainService;
private readonly IUmbracoMapper _umbracoMapper;
public DomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper)
[ActivatorUtilitiesConstructor]
public DomainsController(IAuthorizationService authorizationService, IDomainService domainService, IUmbracoMapper umbracoMapper)
{
_authorizationService = authorizationService;
_domainService = domainService;
_umbracoMapper = umbracoMapper;
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper)
: this(
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
domainService,
umbracoMapper)
{
}
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/domains")]
[ProducesResponseType(typeof(DomainsResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Domains(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
AuthorizationPolicies.ContentPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
IDomain[] assignedDomains = (await _domainService.GetAssignedDomainsAsync(id, true))
.OrderBy(d => d.SortOrder)
.ToArray();
@@ -1,13 +1,19 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
@@ -15,17 +21,30 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
public class UpdateDomainsController : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IDomainService _domainService;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IDomainPresentationFactory _domainPresentationFactory;
public UpdateDomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper, IDomainPresentationFactory domainPresentationFactory)
[ActivatorUtilitiesConstructor]
public UpdateDomainsController(IAuthorizationService authorizationService, IDomainService domainService, IUmbracoMapper umbracoMapper, IDomainPresentationFactory domainPresentationFactory)
{
_authorizationService = authorizationService;
_domainService = domainService;
_umbracoMapper = umbracoMapper;
_domainPresentationFactory = domainPresentationFactory;
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public UpdateDomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper, IDomainPresentationFactory domainPresentationFactory)
: this(
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
domainService,
umbracoMapper,
domainPresentationFactory)
{
}
[MapToApiVersion("1.0")]
[HttpPut("{id:guid}/domains")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -37,6 +56,16 @@ public class UpdateDomainsController : DocumentControllerBase
Guid id,
UpdateDomainsRequestModel updateModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(ActionAssignDomain.ActionLetter, id),
AuthorizationPolicies.ContentPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
DomainsUpdateModel domainsUpdateModel = _umbracoMapper.Map<DomainsUpdateModel>(updateModel)!;
Attempt<DomainUpdateResult, DomainOperationStatus> result = await _domainService.UpdateDomainsAsync(id, domainsUpdateModel);
@@ -1,33 +1,63 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
public class UpdateNotificationsController : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IContentEditingService _contentEditingService;
private readonly INotificationService _notificationService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public UpdateNotificationsController(IContentEditingService contentEditingService, INotificationService notificationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
[ActivatorUtilitiesConstructor]
public UpdateNotificationsController(IAuthorizationService authorizationService, IContentEditingService contentEditingService, INotificationService notificationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_authorizationService = authorizationService;
_contentEditingService = contentEditingService;
_notificationService = notificationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public UpdateNotificationsController(IContentEditingService contentEditingService, INotificationService notificationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: this(
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
contentEditingService,
notificationService,
backOfficeSecurityAccessor)
{
}
[MapToApiVersion("1.0")]
[HttpPut("{id:guid}/notifications")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateNotifications(CancellationToken cancellationToken, Guid id, UpdateDocumentNotificationsRequestModel updateModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
AuthorizationPolicies.ContentPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
IContent? content = await _contentEditingService.GetAsync(id);
if (content == null)
{
@@ -21,6 +21,20 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
private readonly IContentEditingService _contentEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public ValidateCreateDocumentController(
IAuthorizationService authorizationService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
IContentEditingService contentEditingService)
: this(
authorizationService,
documentEditingPresentationFactory,
contentEditingService,
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
{
}
[ActivatorUtilitiesConstructor]
public ValidateCreateDocumentController(
IAuthorizationService authorizationService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
@@ -21,6 +21,20 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public ValidateUpdateDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory)
: this(
authorizationService,
contentEditingService,
documentEditingPresentationFactory,
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
{
}
[ActivatorUtilitiesConstructor]
public ValidateUpdateDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
@@ -16,12 +16,35 @@ public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase
private readonly IUmbracoMapper _mapper;
private readonly IContentTypeSearchService _contentTypeSearchService;
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
public SearchDocumentTypeItemController(IEntitySearchService entitySearchService, IContentTypeService contentTypeService, IUmbracoMapper mapper)
: this(mapper, StaticServiceProvider.Instance.GetRequiredService<IContentTypeSearchService>())
{
}
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
// We need to have this constructor, or else we get ambiguous constructor error
public SearchDocumentTypeItemController(
IEntitySearchService entitySearchService,
IContentTypeService contentTypeService,
IUmbracoMapper mapper,
IContentTypeSearchService contentTypeSearchService)
: this(mapper, contentTypeSearchService)
{
}
[ActivatorUtilitiesConstructor]
public SearchDocumentTypeItemController(IUmbracoMapper mapper, IContentTypeSearchService contentTypeSearchService)
{
_mapper = mapper;
_contentTypeSearchService = contentTypeSearchService;
}
[NonAction]
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
=> await SearchDocumentType(cancellationToken, query, null, skip, take);
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<DocumentTypeItemResponseModel>), StatusCodes.Status200OK)]
@@ -26,9 +26,9 @@ public class RebuildIndexerController : IndexerControllerBase
}
/// <summary>
/// Rebuilds the index.
/// Rebuilds the index
/// </summary>
/// <param name="indexName">The name of the index to rebuild.</param>
/// <param name="indexName"></param>
/// <returns></returns>
[HttpPost("{indexName}/rebuild")]
[MapToApiVersion("1.0")]
@@ -36,7 +36,7 @@ public class RebuildIndexerController : IndexerControllerBase
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken, string indexName)
public Task<IActionResult> Rebuild(CancellationToken cancellationToken, string indexName)
{
if (!_examineManager.TryGetIndex(indexName, out IIndex? index))
{
@@ -48,7 +48,7 @@ public class RebuildIndexerController : IndexerControllerBase
Type = "Error",
};
return NotFound(invalidModelProblem);
return Task.FromResult<IActionResult>(NotFound(invalidModelProblem));
}
if (!_indexingRebuilderService.CanRebuild(index.Name))
@@ -57,19 +57,19 @@ public class RebuildIndexerController : IndexerControllerBase
{
Title = "Could not validate the populator",
Detail =
$"The index {index.Name} could not be rebuilt because we could not validate its associated {typeof(IIndexPopulator)}",
$"The index {index?.Name} could not be rebuilt because we could not validate its associated {typeof(IIndexPopulator)}",
Status = StatusCodes.Status400BadRequest,
Type = "Error",
};
return BadRequest(invalidModelProblem);
return Task.FromResult<IActionResult>(BadRequest(invalidModelProblem));
}
_logger.LogInformation("Rebuilding index '{IndexName}'", indexName);
if (await _indexingRebuilderService.TryRebuildAsync(index, indexName))
if (_indexingRebuilderService.TryRebuild(index, indexName))
{
return Ok();
return Task.FromResult<IActionResult>(Ok());
}
var problemDetails = new ProblemDetails
@@ -80,6 +80,6 @@ public class RebuildIndexerController : IndexerControllerBase
Type = "Error",
};
return Conflict(problemDetails);
return Task.FromResult<IActionResult>(Conflict(problemDetails));
}
}
@@ -26,6 +26,12 @@ public abstract class MediaCollectionControllerBase : ContentCollectionControlle
{
}
[Obsolete("Please use the constructor with all parameters. Scheduled to be removed in Umbraco 18")]
protected MediaCollectionControllerBase(IUmbracoMapper mapper)
: base(mapper)
{
}
protected IActionResult CollectionOperationStatusResult(ContentCollectionOperationStatus status)
=> ContentCollectionOperationStatusResult(status, "media");
}
@@ -24,15 +24,15 @@ public class SearchMemberItemController : MemberItemControllerBase
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<MemberItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null)
public Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null)
{
PagedModel<IEntitySlim> searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Member, query, null, allowedMemberTypes, false, "*", skip, take);
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Member, query, null, allowedMemberTypes, skip, take);
var result = new PagedModel<MemberItemResponseModel>
{
Items = searchResult.Items.OfType<IMemberEntitySlim>().Select(_memberPresentationFactory.CreateItemResponseModel),
Total = searchResult.Total,
Total = searchResult.Total
};
return await Task.FromResult<IActionResult>(Ok(result));
return Task.FromResult<IActionResult>(Ok(result));
}
}
@@ -1,25 +0,0 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class ByKeyMemberTypeFolderController : MemberTypeFolderControllerBase
{
public ByKeyMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id) => await GetFolderAsync(id);
}
@@ -1,31 +0,0 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class CreateMemberTypeFolderController : MemberTypeFolderControllerBase
{
public CreateMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Create(
CancellationToken cancellationToken,
CreateFolderRequestModel createFolderRequestModel)
=> await CreateFolderAsync<ByKeyMemberTypeFolderController>(
createFolderRequestModel,
controller => nameof(controller.ByKey)).ConfigureAwait(false);
}
@@ -1,25 +0,0 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class DeleteMemberTypeFolderController : MemberTypeFolderControllerBase
{
public DeleteMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id) => await DeleteFolderAsync(id);
}
@@ -1,23 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.MemberType}/folder")]
[ApiExplorerSettings(GroupName = "Member Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
public abstract class MemberTypeFolderControllerBase : FolderManagementControllerBase<IMemberType>
{
protected MemberTypeFolderControllerBase(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
}
@@ -1,30 +0,0 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class UpdateMemberTypeFolderController : MemberTypeFolderControllerBase
{
public UpdateMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpPut("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(
CancellationToken cancellationToken,
Guid id,
UpdateFolderResponseModel updateFolderResponseModel)
=> await UpdateFolderAsync(id, updateFolderResponseModel);
}
@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.ModelsBuilder;
using Umbraco.Cms.Infrastructure.ModelsBuilder.Building;
@@ -40,8 +39,7 @@ public class BuildModelsBuilderController : ModelsBuilderControllerBase
{
try
{
if (_modelsBuilderSettings.ModelsMode != Constants.ModelsBuilder.ModelsModes.SourceCodeManual
&& _modelsBuilderSettings.ModelsMode != Constants.ModelsBuilder.ModelsModes.SourceCodeAuto)
if (!_modelsBuilderSettings.ModelsMode.SupportsExplicitGeneration())
{
var problemDetailsModel = new ProblemDetails
{
@@ -1,22 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Services.NewsDashboard;
using Umbraco.Cms.Api.Management.ViewModels.NewsDashboard;
namespace Umbraco.Cms.Api.Management.Controllers.NewsDashboard;
public class NewsDashboardController : NewsDashboardControllerBase
{
private readonly INewsDashboardService _newsDashboardService;
public NewsDashboardController(INewsDashboardService newsDashboardService) => _newsDashboardService = newsDashboardService;
[HttpGet]
[ProducesResponseType(typeof(NewsDashboardResponseModel), StatusCodes.Status200OK)]
public async Task<IActionResult> GetDashboard()
{
NewsDashboardResponseModel content = await _newsDashboardService.GetItemsAsync();
return Ok(content);
}
}
@@ -1,10 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
namespace Umbraco.Cms.Api.Management.Controllers.NewsDashboard;
[VersionedApiBackOfficeRoute("news-dashboard")]
[ApiExplorerSettings(GroupName = "News Dashboard")]
public abstract class NewsDashboardControllerBase : ManagementApiControllerBase
{
}
@@ -1,34 +1,32 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Tree;
[ApiVersion("1.0")]
public class AncestorsPartialViewTreeController : PartialViewTreeControllerBase
{
private readonly IPartialViewTreeService _partialViewTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public AncestorsPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: this(partialViewTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _partialViewTreeService = partialViewTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public AncestorsPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems) =>
_partialViewTreeService = partialViewTreeService;
public AncestorsPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: base(partialViewTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public AncestorsPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public AncestorsPartialViewTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IPartialViewTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,34 +1,33 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Tree;
[ApiVersion("1.0")]
public class ChildrenPartialViewTreeController : PartialViewTreeControllerBase
{
private readonly IPartialViewTreeService _partialViewTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public ChildrenPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: this(partialViewTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _partialViewTreeService = partialViewTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public ChildrenPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems) =>
_partialViewTreeService = partialViewTreeService;
public ChildrenPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: base(partialViewTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ChildrenPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ChildrenPartialViewTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IPartialViewTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Controllers.Tree;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Web.Common.Authorization;
@@ -16,30 +14,28 @@ namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Tree;
[Authorize(Policy = AuthorizationPolicies.TreeAccessPartialViews)]
public class PartialViewTreeControllerBase : FileSystemTreeControllerBase
{
private readonly IPartialViewTreeService _partialViewTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public PartialViewTreeControllerBase(IPartialViewTreeService partialViewTreeService)
: this(partialViewTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>()) =>
_partialViewTreeService = partialViewTreeService;
: base(partialViewTreeService)
{
FileSystem = null!;
}
// FileSystem is required therefore, we can't remove it without some wizadry. When obsoletion is due, remove this.
[ActivatorUtilitiesConstructor]
[Obsolete("Scheduled for removal in Umbraco 18.")]
// FileSystem is required therefore, we can't remove it without some wizardry. When obsoletion is due, remove this.
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public PartialViewTreeControllerBase(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService)
{
_partialViewTreeService = partialViewTreeService;
FileSystem = fileSystems.PartialViewsFileSystem ??
throw new ArgumentException("Missing scripts file system", nameof(fileSystems));
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 18.")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public PartialViewTreeControllerBase(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IPartialViewTreeService>())
: base()
=> FileSystem = fileSystems.PartialViewsFileSystem ??
throw new ArgumentException("Missing scripts file system", nameof(fileSystems));
[Obsolete("Included in the service class. Scheduled to be removed in Umbraco 18.")]
[Obsolete("Included in the service class. Scheduled to be removed in Umbraco 19.")]
protected override IFileSystem FileSystem { get; }
}
@@ -1,34 +1,33 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Tree;
[ApiVersion("1.0")]
public class RootPartialViewTreeController : PartialViewTreeControllerBase
{
private readonly IPartialViewTreeService _partialViewTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public RootPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: this(partialViewTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _partialViewTreeService = partialViewTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public RootPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems) =>
_partialViewTreeService = partialViewTreeService;
public RootPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: base(partialViewTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public RootPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public RootPartialViewTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IPartialViewTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,32 +1,31 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Tree;
public class SiblingsPartialViewTreeController : PartialViewTreeControllerBase
{
private readonly IPartialViewTreeService _partialViewTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public SiblingsPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: this(partialViewTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _partialViewTreeService = partialViewTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public SiblingsPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems) =>
_partialViewTreeService = partialViewTreeService;
public SiblingsPartialViewTreeController(IPartialViewTreeService partialViewTreeService)
: base(partialViewTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public SiblingsPartialViewTreeController(IPartialViewTreeService partialViewTreeService, FileSystems fileSystems)
: base(partialViewTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public SiblingsPartialViewTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IPartialViewTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -7,7 +7,6 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Preview;
[ApiVersion("1.0")]
[Obsolete("Do not use this. Preview state is initiated implicitly by the preview URL generation. Scheduled for removal in V18.")]
public class EnterPreviewController : PreviewControllerBase
{
private readonly IPreviewService _previewService;
@@ -1,10 +1,9 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Script.Tree;
@@ -12,22 +11,22 @@ namespace Umbraco.Cms.Api.Management.Controllers.Script.Tree;
[ApiVersion("1.0")]
public class AncestorsScriptTreeController : ScriptTreeControllerBase
{
private readonly IScriptTreeService _scriptTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public AncestorsScriptTreeController(IScriptTreeService scriptTreeService)
: this(scriptTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _scriptTreeService = scriptTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public AncestorsScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems) =>
_scriptTreeService = scriptTreeService;
public AncestorsScriptTreeController(IScriptTreeService scriptTreeService)
: base(scriptTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public AncestorsScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public AncestorsScriptTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IScriptTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,34 +1,33 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Script.Tree;
[ApiVersion("1.0")]
public class ChildrenScriptTreeController : ScriptTreeControllerBase
{
private readonly IScriptTreeService _scriptTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public ChildrenScriptTreeController(IScriptTreeService scriptTreeService)
: this(scriptTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _scriptTreeService = scriptTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public ChildrenScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems) =>
_scriptTreeService = scriptTreeService;
public ChildrenScriptTreeController(IScriptTreeService scriptTreeService)
: base(scriptTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ChildrenScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ChildrenScriptTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IScriptTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,34 +1,33 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Script.Tree;
[ApiVersion("1.0")]
public class RootScriptTreeController : ScriptTreeControllerBase
{
private readonly IScriptTreeService _scriptTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public RootScriptTreeController(IScriptTreeService scriptTreeService)
: this(scriptTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _scriptTreeService = scriptTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public RootScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems) =>
_scriptTreeService = scriptTreeService;
public RootScriptTreeController(IScriptTreeService scriptTreeService)
: base(scriptTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public RootScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public RootScriptTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IScriptTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Controllers.Tree;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Web.Common.Authorization;
@@ -16,30 +14,28 @@ namespace Umbraco.Cms.Api.Management.Controllers.Script.Tree;
[Authorize(Policy = AuthorizationPolicies.TreeAccessScripts)]
public class ScriptTreeControllerBase : FileSystemTreeControllerBase
{
private readonly IScriptTreeService _scriptTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public ScriptTreeControllerBase(IScriptTreeService scriptTreeService)
: this(scriptTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>()) =>
_scriptTreeService = scriptTreeService;
: base(scriptTreeService)
{
FileSystem = null!;
}
// FileSystem is required therefore, we can't remove it without some wizadry. When obsoletion is due, remove this.
[ActivatorUtilitiesConstructor]
[Obsolete("Scheduled for removal in Umbraco 18.")]
// FileSystem is required therefore, we can't remove it without some wizardry. When obsoletion is due, remove this.
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ScriptTreeControllerBase(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService)
{
_scriptTreeService = scriptTreeService;
FileSystem = fileSystems.ScriptsFileSystem ??
throw new ArgumentException("Missing scripts file system", nameof(fileSystems));
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 18.")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ScriptTreeControllerBase(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IScriptTreeService>())
: base()
=> FileSystem = fileSystems.ScriptsFileSystem ??
throw new ArgumentException("Missing scripts file system", nameof(fileSystems));
[Obsolete("Included in the service class. Scheduled to be removed in Umbraco 18.")]
[Obsolete("Included in the service class. Scheduled to be removed in Umbraco 19.")]
protected override IFileSystem FileSystem { get; }
}
@@ -1,32 +1,31 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Script.Tree;
public class SiblingsScriptTreeController : ScriptTreeControllerBase
{
private readonly IScriptTreeService _scriptTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public SiblingsScriptTreeController(IScriptTreeService scriptTreeService)
: this(scriptTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _scriptTreeService = scriptTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public SiblingsScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems) =>
_scriptTreeService = scriptTreeService;
public SiblingsScriptTreeController(IScriptTreeService scriptTreeService)
: base(scriptTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public SiblingsScriptTreeController(IScriptTreeService scriptTreeService, FileSystems fileSystems)
: base(scriptTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public SiblingsScriptTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IScriptTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -254,7 +254,7 @@ public class BackOfficeController : SecurityControllerBase
if (associatedUser is not null)
{
// log current datetime as last login (this also ensures that the user is not flagged as inactive)
associatedUser.LastLoginDate = DateTime.UtcNow;
associatedUser.LastLoginDateUtc = DateTime.UtcNow;
await _backOfficeUserManager.UpdateAsync(associatedUser);
return await SignInBackOfficeUser(associatedUser, request);
@@ -17,6 +17,12 @@ public class BackOfficeDefaultController : Controller
public BackOfficeDefaultController(IRuntime umbracoRuntime)
=> _umbracoRuntime = umbracoRuntime;
[Obsolete("Use the non obsoleted constructor instead. Scheduled to be removed in v17")]
public BackOfficeDefaultController()
: this(StaticServiceProvider.Instance.GetRequiredService<IRuntime>())
{
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Index(CancellationToken cancellationToken)
@@ -1,10 +1,9 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Tree;
@@ -12,22 +11,22 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Tree;
[ApiVersion("1.0")]
public class AncestorsStylesheetTreeController : StylesheetTreeControllerBase
{
private readonly IStyleSheetTreeService _styleSheetTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public AncestorsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: this(styleSheetTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _styleSheetTreeService = styleSheetTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public AncestorsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems) =>
_styleSheetTreeService = styleSheetTreeService;
public AncestorsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: base(styleSheetTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public AncestorsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public AncestorsStylesheetTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IStyleSheetTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,36 +1,36 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Tree;
[ApiVersion("1.0")]
public class ChildrenStylesheetTreeController : StylesheetTreeControllerBase
{
private readonly IStyleSheetTreeService _styleSheetTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public ChildrenStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: this(styleSheetTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _styleSheetTreeService = styleSheetTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public ChildrenStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems) =>
_styleSheetTreeService = styleSheetTreeService;
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public ChildrenStylesheetTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IStyleSheetTreeService>(), fileSystems)
public ChildrenStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: base(styleSheetTreeService)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ChildrenStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public ChildrenStylesheetTreeController(FileSystems fileSystems)
: base(fileSystems)
{
}
[HttpGet("children")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemPresentationModel>), StatusCodes.Status200OK)]
@@ -1,34 +1,33 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Tree;
[ApiVersion("1.0")]
public class RootStylesheetTreeController : StylesheetTreeControllerBase
{
private readonly IStyleSheetTreeService _styleSheetTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public RootStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: this(styleSheetTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _styleSheetTreeService = styleSheetTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public RootStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems) =>
_styleSheetTreeService = styleSheetTreeService;
public RootStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: base(styleSheetTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public RootStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public RootStylesheetTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IStyleSheetTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,32 +1,31 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Tree;
public class SiblingsStylesheetTreeController : StylesheetTreeControllerBase
{
private readonly IStyleSheetTreeService _styleSheetTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public SiblingsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: this(styleSheetTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>())
=> _styleSheetTreeService = styleSheetTreeService;
[ActivatorUtilitiesConstructor]
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
public SiblingsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems) =>
_styleSheetTreeService = styleSheetTreeService;
public SiblingsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService)
: base(styleSheetTreeService)
{
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 19")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public SiblingsStylesheetTreeController(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService, fileSystems)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public SiblingsStylesheetTreeController(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IStyleSheetTreeService>(), fileSystems)
: base(fileSystems)
{
}
@@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Controllers.Tree;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Web.Common.Authorization;
@@ -16,30 +14,28 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Tree;
[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)]
public class StylesheetTreeControllerBase : FileSystemTreeControllerBase
{
private readonly IStyleSheetTreeService _styleSheetTreeService;
// TODO Remove the static service provider, and replace with base when the other constructors are obsoleted.
public StylesheetTreeControllerBase(IStyleSheetTreeService styleSheetTreeService)
: this(styleSheetTreeService, StaticServiceProvider.Instance.GetRequiredService<FileSystems>()) =>
_styleSheetTreeService = styleSheetTreeService;
: base(styleSheetTreeService)
{
FileSystem = null!;
}
// FileSystem is required therefore, we can't remove it without some wizadry. When obsoletion is due, remove this.
[ActivatorUtilitiesConstructor]
[Obsolete("Scheduled for removal in Umbraco 18.")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public StylesheetTreeControllerBase(IStyleSheetTreeService styleSheetTreeService, FileSystems fileSystems)
: base(styleSheetTreeService)
{
_styleSheetTreeService = styleSheetTreeService;
FileSystem = fileSystems.ScriptsFileSystem ??
throw new ArgumentException("Missing scripts file system", nameof(fileSystems));
}
[Obsolete("Please use the other constructor. Scheduled to be removed in Umbraco 18.")]
[Obsolete("Please use the constructor taking all parameters. Scheduled to be removed in Umbraco 19.")]
public StylesheetTreeControllerBase(FileSystems fileSystems)
: this(StaticServiceProvider.Instance.GetRequiredService<IStyleSheetTreeService>())
: base()
=> FileSystem = fileSystems.ScriptsFileSystem ??
throw new ArgumentException("Missing scripts file system", nameof(fileSystems));
[Obsolete("Included in the service class. Scheduled to be removed in Umbraco 18.")]
[Obsolete("Included in the service class. Scheduled to be removed in Umbraco 19.")]
protected override IFileSystem FileSystem { get; }
}
@@ -43,6 +43,41 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
_publishedContentStatusFilteringService = publishedContentStatusFilteringService;
}
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public ExecuteTemplateQueryController(
IPublishedContentQuery publishedContentQuery,
IVariationContextAccessor variationContextAccessor,
IPublishedValueFallback publishedValueFallback,
IContentTypeService contentTypeService,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService documentNavigationQueryService,
IPublishedContentStatusFilteringService publishedContentStatusFilteringService)
: this(
publishedContentQuery,
publishedValueFallback,
contentTypeService,
documentNavigationQueryService,
publishedContentStatusFilteringService)
{
}
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public ExecuteTemplateQueryController(
IPublishedContentQuery publishedContentQuery,
IVariationContextAccessor variationContextAccessor,
IPublishedValueFallback publishedValueFallback,
IContentTypeService contentTypeService,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService documentNavigationQueryService)
: this(
publishedContentQuery,
publishedValueFallback,
contentTypeService,
documentNavigationQueryService,
StaticServiceProvider.Instance.GetRequiredService<IPublishedContentStatusFilteringService>())
{
}
[HttpPost("execute")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(TemplateQueryResultResponseModel), StatusCodes.Status200OK)]
@@ -5,7 +5,6 @@ using Umbraco.Cms.Api.Management.Extensions;
using Umbraco.Cms.Api.Management.Services.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.FileSystem;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Extensions;
@@ -13,23 +12,30 @@ namespace Umbraco.Cms.Api.Management.Controllers.Tree;
public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
{
private readonly IFileSystemTreeService _fileSystemTreeService;
private readonly IFileSystemTreeService _fileSystemTreeService = null!;
[Obsolete("Has been moved to the individual services. Scheduled to be removed in Umbraco 18.")]
/// <summary>
/// Indicates whether to use the IFileSystemTreeService or the legacy implementation.
/// </summary>
/// <remarks>
/// This is retained to ensure that any controllers outside of the CMS that use this base class with the obsolete constructor
/// continue to function until they can be updated to use the new service.
/// To be removed along with the constructor taking no parameters in Umbraco 19.
/// </remarks>
private readonly bool _useFileSystemTreeService = true;
[Obsolete("Has been moved to the individual services. Scheduled to be removed in Umbraco 19.")]
protected abstract IFileSystem FileSystem { get; }
[ActivatorUtilitiesConstructor]
protected FileSystemTreeControllerBase(IFileSystemTreeService fileSystemTreeService) => _fileSystemTreeService = fileSystemTreeService;
[Obsolete("Use the other constructor. Scheduled for removal in Umbraco 18.")]
protected FileSystemTreeControllerBase()
: this(StaticServiceProvider.Instance.GetRequiredService<IScriptTreeService>())
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
protected FileSystemTreeControllerBase() => _useFileSystemTreeService = false;
protected Task<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>> GetRoot(int skip, int take)
{
FileSystemTreeItemPresentationModel[] viewModels = _fileSystemTreeService.GetPathViewModels(string.Empty, skip, take, out var totalItems);
FileSystemTreeItemPresentationModel[] viewModels = GetPathViewModels(string.Empty, skip, take, out var totalItems);
PagedViewModel<FileSystemTreeItemPresentationModel> result = PagedViewModel(viewModels, totalItems);
return Task.FromResult<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>>(Ok(result));
@@ -37,14 +43,14 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
protected Task<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>> GetChildren(string path, int skip, int take)
{
FileSystemTreeItemPresentationModel[] viewModels = _fileSystemTreeService.GetPathViewModels(path, skip, take, out var totalItems);
FileSystemTreeItemPresentationModel[] viewModels = GetPathViewModels(path, skip, take, out var totalItems);
PagedViewModel<FileSystemTreeItemPresentationModel> result = PagedViewModel(viewModels, totalItems);
return Task.FromResult<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>>(Ok(result));
}
/// <summary>
/// Gets the sibling of the targeted item based on its path.
/// Gets the siblings of the targeted item based on its path.
/// </summary>
/// <param name="path">The path to the item.</param>
/// <param name="before">The amount of siblings you want to fetch from before the items position in the array.</param>
@@ -61,17 +67,19 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
protected virtual Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> GetAncestors(string path, bool includeSelf = true)
{
path = path.VirtualPathToSystemPath();
FileSystemTreeItemPresentationModel[] models = _fileSystemTreeService.GetAncestorModels(path, includeSelf);
FileSystemTreeItemPresentationModel[] models = GetAncestorModels(path, includeSelf);
return Task.FromResult<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>>(Ok(models));
}
private PagedViewModel<FileSystemTreeItemPresentationModel> PagedViewModel(IEnumerable<FileSystemTreeItemPresentationModel> viewModels, long totalItems)
=> new() { Total = totalItems, Items = viewModels };
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 18.")]
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 19.")]
protected virtual FileSystemTreeItemPresentationModel[] GetAncestorModels(string path, bool includeSelf)
{
if (_useFileSystemTreeService)
{
return _fileSystemTreeService.GetAncestorModels(path, includeSelf);
}
var directories = path.Split(Path.DirectorySeparatorChar).Take(Range.EndAt(Index.FromEnd(1))).ToArray();
var result = directories
.Select((directory, index) => MapViewModel(string.Join(Path.DirectorySeparatorChar, directories.Take(index + 1)), directory, true))
@@ -86,28 +94,59 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
return result.ToArray();
}
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 18.")]
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 19.")]
protected virtual string[] GetDirectories(string path) => FileSystem
.GetDirectories(path)
.OrderBy(directory => directory)
.ToArray();
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 18.")]
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 19.")]
protected virtual string[] GetFiles(string path) => FileSystem
.GetFiles(path)
.OrderBy(file => file)
.ToArray();
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 18.")]
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 19.")]
protected virtual bool DirectoryHasChildren(string path)
=> FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any();
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 18.")]
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 19.")]
private string GetFileSystemItemName(bool isFolder, string itemPath) => isFolder
? Path.GetFileName(itemPath)
: FileSystem.GetFileName(itemPath);
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 18.")]
private FileSystemTreeItemPresentationModel[] GetPathViewModels(string path, int skip, int take, out long totalItems)
{
if (_useFileSystemTreeService)
{
return _fileSystemTreeService.GetPathViewModels(path, skip, take, out totalItems);
}
path = path.VirtualPathToSystemPath();
var allItems = GetDirectories(path)
.Select(directory => new { Path = directory, IsFolder = true })
.Union(GetFiles(path).Select(file => new { Path = file, IsFolder = false }))
.ToArray();
totalItems = allItems.Length;
FileSystemTreeItemPresentationModel ViewModel(string itemPath, bool isFolder)
=> MapViewModel(
itemPath,
GetFileSystemItemName(isFolder, itemPath),
isFolder);
return allItems
.Skip(skip)
.Take(take)
.Select(item => ViewModel(item.Path, item.IsFolder))
.ToArray();
}
private PagedViewModel<FileSystemTreeItemPresentationModel> PagedViewModel(IEnumerable<FileSystemTreeItemPresentationModel> viewModels, long totalItems)
=> new() { Total = totalItems, Items = viewModels };
[Obsolete("Has been moved to FileSystemTreeServiceBase. Scheduled for removal in Umbraco 19.")]
private FileSystemTreeItemPresentationModel MapViewModel(string path, string name, bool isFolder)
{
var parentPath = Path.GetDirectoryName(path);
@@ -1,11 +1,14 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Controllers.UserGroup;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.User;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -25,11 +28,26 @@ public class UpdateUserGroupsUserController : UserGroupControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IUserGroupService _userGroupService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public UpdateUserGroupsUserController(IAuthorizationService authorizationService, IUserGroupService userGroupService)
[ActivatorUtilitiesConstructor]
public UpdateUserGroupsUserController(
IAuthorizationService authorizationService,
IUserGroupService userGroupService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_authorizationService = authorizationService;
_userGroupService = userGroupService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[Obsolete("Please use the constructor accepting all parameters. Scheduled for removal in Umbraco 19.")]
public UpdateUserGroupsUserController(IAuthorizationService authorizationService, IUserGroupService userGroupService)
: this(
authorizationService,
userGroupService,
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
{
}
[HttpPost("set-user-groups")]
@@ -51,7 +69,8 @@ public class UpdateUserGroupsUserController : UserGroupControllerBase
Attempt<UserGroupOperationStatus> result = await _userGroupService.UpdateUserGroupsOnUsersAsync(
requestModel.UserGroupIds.Select(x => x.Id).ToHashSet(),
requestModel.UserIds.Select(x => x.Id).ToHashSet());
requestModel.UserIds.Select(x => x.Id).ToHashSet(),
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
@@ -11,13 +11,13 @@ internal static class AuditLogBuilderExtensions
internal static IUmbracoBuilder AddAuditLogs(this IUmbracoBuilder builder)
{
builder.Services.AddTransient<IAuditLogPresentationFactory, AuditLogPresentationFactory>();
builder.AddNotificationAsyncHandler<UserLoginSuccessNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationAsyncHandler<UserLogoutSuccessNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationAsyncHandler<UserLoginFailedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationAsyncHandler<UserForgotPasswordRequestedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationAsyncHandler<UserForgotPasswordChangedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationAsyncHandler<UserPasswordChangedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationAsyncHandler<UserPasswordResetNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserLoginSuccessNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserLogoutSuccessNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserLoginFailedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserForgotPasswordRequestedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserForgotPasswordChangedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserPasswordChangedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserPasswordResetNotification, BackOfficeUserManagerAuditer>();
return builder;
}
@@ -1,15 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Services.NewsDashboard;
using Umbraco.Cms.Core.DependencyInjection;
namespace Umbraco.Cms.Api.Management.DependencyInjection;
internal static class NewsDashboardBuilderExtensions
{
internal static IUmbracoBuilder AddNewsDashboard(this IUmbracoBuilder builder)
{
builder.Services.AddSingleton<INewsDashboardService, NewsDashboardService>();
return builder;
}
}
@@ -25,7 +25,7 @@ public static class InstallerBuilderExtensions
builder.AddInstallSteps();
services.AddTransient<IInstallService, InstallService>();
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, PostUnattendedInstallNotificationHandler>();
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, CreateUnattendedUserNotificationHandler>();
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<InstallerViewModelsMapDefinition>();
return builder;
@@ -30,7 +30,7 @@ public static partial class UmbracoBuilderExtensions
.AddMembersIdentity()
.AddUmbracoProfiler()
.AddMvcAndRazor(configureMvc)
.AddBackgroundJobs()
.AddRecurringBackgroundJobs()
.AddUmbracoHybridCache()
.AddDistributedCache()
.AddCoreNotifications();
@@ -71,8 +71,7 @@ public static partial class UmbracoBuilderExtensions
.AddUserData()
.AddSegment()
.AddExport()
.AddImport()
.AddNewsDashboard();
.AddImport();
services
.ConfigureOptions<ConfigureApiBehaviorOptions>()
@@ -1,5 +1,9 @@
using Umbraco.Cms.Api.Management.Factories;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Cms.Api.Common.Accessors;
using Umbraco.Cms.Api.Common.Rendering;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Mapping.Webhook;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Extensions;
@@ -12,6 +16,11 @@ internal static class WebhooksBuilderExtensions
builder.Services.AddUnique<IWebhookPresentationFactory, WebhookPresentationFactory>();
builder.AddMapDefinition<WebhookEventMapDefinition>();
// We have to use TryAdd here, as if they are registered by the delivery API, we don't want to register them
// Delivery API will also overwrite these IF it is enabled.
builder.Services.TryAddScoped<IOutputExpansionStrategy, ElementOnlyOutputExpansionStrategy>();
builder.Services.TryAddSingleton<IOutputExpansionStrategyAccessor, RequestContextOutputExpansionStrategyAccessor>();
return builder;
}
}
@@ -14,6 +14,15 @@ public class DocumentCollectionPresentationFactory : ContentCollectionPresentati
private readonly IPublicAccessService _publicAccessService;
private readonly IEntityService _entityService;
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService)
: this(
mapper,
publicAccessService,
StaticServiceProvider.Instance.GetRequiredService<IEntityService>())
{
}
[ActivatorUtilitiesConstructor]
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService, IEntityService entityService)
: base(mapper)
@@ -65,6 +65,22 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory
_flagProviderCollection = flagProviderCollection;
}
[Obsolete("Schedule for removal in v17")]
public async Task<DocumentResponseModel> CreateResponseModelAsync(IContent content)
{
DocumentResponseModel responseModel = _umbracoMapper.Map<DocumentResponseModel>(content)!;
Guid? templateKey = content.TemplateId.HasValue
? _templateService.GetAsync(content.TemplateId.Value).Result?.Key
: null;
responseModel.Template = templateKey.HasValue
? new ReferenceByIdModel { Id = templateKey.Value }
: null;
return responseModel;
}
public async Task<PublishedDocumentResponseModel> CreatePublishedResponseModelAsync(IContent content)
{
PublishedDocumentResponseModel responseModel = _umbracoMapper.Map<PublishedDocumentResponseModel>(content)!;
@@ -165,6 +181,68 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory
public DocumentTypeReferenceResponseModel CreateDocumentTypeReferenceResponseModel(IDocumentEntitySlim entity)
=> _umbracoMapper.Map<DocumentTypeReferenceResponseModel>(entity)!;
[Obsolete("Use CreateCulturePublishScheduleModels instead. Scheduled for removal in v17")]
public Attempt<CultureAndScheduleModel, ContentPublishingOperationStatus> CreateCultureAndScheduleModel(PublishDocumentRequestModel requestModel)
{
var contentScheduleCollection = new ContentScheduleCollection();
var culturesToPublishImmediately = new HashSet<string>();
foreach (CultureAndScheduleRequestModel cultureAndScheduleRequestModel in requestModel.PublishSchedules)
{
if (cultureAndScheduleRequestModel.Schedule is null || (cultureAndScheduleRequestModel.Schedule.PublishTime is null && cultureAndScheduleRequestModel.Schedule.UnpublishTime is null))
{
culturesToPublishImmediately.Add(cultureAndScheduleRequestModel.Culture ?? Constants.System.InvariantCulture); // API have `null` for invariant, but service layer has "*".
continue;
}
if (cultureAndScheduleRequestModel.Schedule.PublishTime is not null)
{
if (cultureAndScheduleRequestModel.Schedule.PublishTime <= _timeProvider.GetUtcNow())
{
return Attempt.FailWithStatus(ContentPublishingOperationStatus.PublishTimeNeedsToBeInFuture, new CultureAndScheduleModel()
{
Schedules = contentScheduleCollection,
CulturesToPublishImmediately = culturesToPublishImmediately,
});
}
contentScheduleCollection.Add(new ContentSchedule(
cultureAndScheduleRequestModel.Culture ?? Constants.System.InvariantCulture,
cultureAndScheduleRequestModel.Schedule.PublishTime.Value.UtcDateTime,
ContentScheduleAction.Release));
}
if (cultureAndScheduleRequestModel.Schedule.UnpublishTime is not null)
{
if (cultureAndScheduleRequestModel.Schedule.UnpublishTime <= cultureAndScheduleRequestModel.Schedule.PublishTime)
{
return Attempt.FailWithStatus(ContentPublishingOperationStatus.UnpublishTimeNeedsToBeAfterPublishTime, new CultureAndScheduleModel()
{
Schedules = contentScheduleCollection,
CulturesToPublishImmediately = culturesToPublishImmediately,
});
}
if (cultureAndScheduleRequestModel.Schedule.UnpublishTime <= _timeProvider.GetUtcNow())
{
return Attempt.FailWithStatus(ContentPublishingOperationStatus.UpublishTimeNeedsToBeInFuture, new CultureAndScheduleModel()
{
Schedules = contentScheduleCollection,
CulturesToPublishImmediately = culturesToPublishImmediately,
});
}
contentScheduleCollection.Add(new ContentSchedule(
cultureAndScheduleRequestModel.Culture ?? Constants.System.InvariantCulture,
cultureAndScheduleRequestModel.Schedule.UnpublishTime.Value.UtcDateTime,
ContentScheduleAction.Expire));
}
}
return Attempt.SucceedWithStatus(ContentPublishingOperationStatus.Success, new CultureAndScheduleModel()
{
Schedules = contentScheduleCollection,
CulturesToPublishImmediately = culturesToPublishImmediately,
});
}
public Attempt<List<CulturePublishScheduleModel>, ContentPublishingOperationStatus> CreateCulturePublishScheduleModels(PublishDocumentRequestModel requestModel)
{
var model = new List<CulturePublishScheduleModel>();
@@ -1,45 +1,24 @@
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Factories;
public class DocumentUrlFactory : IDocumentUrlFactory
{
private readonly IPublishedUrlInfoProvider _publishedUrlInfoProvider;
private readonly UrlProviderCollection _urlProviders;
private readonly IPreviewService _previewService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IAbsoluteUrlBuilder _absoluteUrlBuilder;
private readonly ILogger<DocumentUrlFactory> _logger;
public DocumentUrlFactory(
IPublishedUrlInfoProvider publishedUrlInfoProvider,
UrlProviderCollection urlProviders,
IPreviewService previewService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IAbsoluteUrlBuilder absoluteUrlBuilder,
ILogger<DocumentUrlFactory> logger)
{
_publishedUrlInfoProvider = publishedUrlInfoProvider;
_urlProviders = urlProviders;
_previewService = previewService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_absoluteUrlBuilder = absoluteUrlBuilder;
_logger = logger;
}
public DocumentUrlFactory(IPublishedUrlInfoProvider publishedUrlInfoProvider)
=> _publishedUrlInfoProvider = publishedUrlInfoProvider;
public async Task<IEnumerable<DocumentUrlInfo>> CreateUrlsAsync(IContent content)
{
ISet<UrlInfo> urlInfos = await _publishedUrlInfoProvider.GetAllAsync(content);
return urlInfos
.Select(urlInfo => CreateDocumentUrlInfo(urlInfo, false))
.Where(urlInfo => urlInfo.IsUrl)
.Select(urlInfo => new DocumentUrlInfo { Culture = urlInfo.Culture, Url = urlInfo.Text })
.ToArray();
}
@@ -55,54 +34,4 @@ public class DocumentUrlFactory : IDocumentUrlFactory
return documentUrlInfoResourceSets;
}
public async Task<DocumentUrlInfo?> GetPreviewUrlAsync(IContent content, string providerAlias, string? culture, string? segment)
{
IUrlProvider? provider = _urlProviders.FirstOrDefault(provider => provider.Alias.InvariantEquals(providerAlias));
if (provider is null)
{
_logger.LogError("Could not resolve a URL provider requested for preview - it was not registered in the URL providers collection.");
return null;
}
UrlInfo? previewUrlInfo = await provider.GetPreviewUrlAsync(content, culture, segment);
if (previewUrlInfo is null)
{
_logger.LogError("The URL provider could not generate a preview URL for content with key: {contentKey}", content.Key);
return null;
}
// must initiate preview state for internal preview URLs
if (previewUrlInfo.Url is not null && previewUrlInfo.IsExternal is false)
{
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
if (currentUser is null)
{
_logger.LogError("Could not access the current backoffice user while attempting to authenticate for preview.");
return null;
}
if (await _previewService.TryEnterPreviewAsync(currentUser) is false)
{
_logger.LogError("A server error occured, could not initiate an authenticated preview state for the current user.");
return null;
}
}
return CreateDocumentUrlInfo(previewUrlInfo, previewUrlInfo.IsExternal is false);
}
private DocumentUrlInfo CreateDocumentUrlInfo(UrlInfo urlInfo, bool ensureAbsoluteUrl)
{
var url = urlInfo.Url?.ToString();
return new DocumentUrlInfo
{
Culture = urlInfo.Culture,
Url = ensureAbsoluteUrl && url is not null
? _absoluteUrlBuilder.ToAbsoluteUrl(url).ToString()
: url,
Message = urlInfo.Message,
Provider = urlInfo.Provider,
};
}
}
@@ -41,6 +41,14 @@ public class HealthCheckGroupPresentationFactory : IHealthCheckGroupPresentation
return groups;
}
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
public HealthCheckGroupWithResultResponseModel CreateHealthCheckGroupWithResultViewModel(IGrouping<string?, HealthCheck> healthCheckGroup)
=> CreateHealthCheckGroupWithResultViewModelAsync(healthCheckGroup).GetAwaiter().GetResult();
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
public HealthCheckWithResultPresentationModel CreateHealthCheckWithResultViewModel(HealthCheck healthCheck) =>
CreateHealthCheckWithResultViewModelAsync(healthCheck).GetAwaiter().GetResult();
public async Task<HealthCheckGroupWithResultResponseModel> CreateHealthCheckGroupWithResultViewModelAsync(IGrouping<string?, HealthCheck> healthCheckGroup)
{
var healthChecks = new List<HealthCheckWithResultPresentationModel>();
@@ -14,9 +14,17 @@ namespace Umbraco.Cms.Api.Management.Factories;
public interface IDocumentPresentationFactory
{
[Obsolete("Schedule for removal in v17")]
Task<DocumentResponseModel> CreateResponseModelAsync(IContent content);
Task<PublishedDocumentResponseModel> CreatePublishedResponseModelAsync(IContent content);
Task<DocumentResponseModel> CreateResponseModelAsync(IContent content, ContentScheduleCollection schedule);
Task<DocumentResponseModel> CreateResponseModelAsync(IContent content, ContentScheduleCollection schedule)
#pragma warning disable CS0618 // Type or member is obsolete
// Remove when obsolete CreateResponseModelAsync is removed
=> CreateResponseModelAsync(content);
#pragma warning restore CS0618 // Type or member is obsolete
DocumentItemResponseModel CreateItemResponseModel(IDocumentEntitySlim entity);
DocumentBlueprintItemResponseModel CreateBlueprintItemResponseModel(IDocumentEntitySlim entity);
@@ -25,6 +33,9 @@ public interface IDocumentPresentationFactory
DocumentTypeReferenceResponseModel CreateDocumentTypeReferenceResponseModel(IDocumentEntitySlim entity);
[Obsolete("Use CreateCulturePublishScheduleModels instead. Scheduled for removal in v17")]
Attempt<CultureAndScheduleModel, ContentPublishingOperationStatus> CreateCultureAndScheduleModel(PublishDocumentRequestModel requestModel);
Attempt<List<CulturePublishScheduleModel>, ContentPublishingOperationStatus> CreateCulturePublishScheduleModels(
PublishDocumentRequestModel requestModel)
{
@@ -8,6 +8,4 @@ public interface IDocumentUrlFactory
Task<IEnumerable<DocumentUrlInfo>> CreateUrlsAsync(IContent content);
Task<IEnumerable<DocumentUrlInfoResponseModel>> CreateUrlSetsAsync(IEnumerable<IContent> contentItems);
Task<DocumentUrlInfo?> GetPreviewUrlAsync(IContent content, string providerAlias, string? culture, string? segment);
}
@@ -7,8 +7,19 @@ public interface IHealthCheckGroupPresentationFactory
{
IEnumerable<IGrouping<string?, HealthCheck>> CreateGroupingFromHealthCheckCollection();
Task<HealthCheckGroupWithResultResponseModel> CreateHealthCheckGroupWithResultViewModelAsync(
IGrouping<string?, HealthCheck> healthCheckGroup);
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
HealthCheckGroupWithResultResponseModel CreateHealthCheckGroupWithResultViewModel(IGrouping<string?, HealthCheck> healthCheckGroup);
Task<HealthCheckWithResultPresentationModel> CreateHealthCheckWithResultViewModelAsync(HealthCheck healthCheck);
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
HealthCheckWithResultPresentationModel CreateHealthCheckWithResultViewModel(HealthCheck healthCheck);
Task<HealthCheckGroupWithResultResponseModel> CreateHealthCheckGroupWithResultViewModelAsync(IGrouping<string?, HealthCheck> healthCheckGroup)
#pragma warning disable CS0618 // Type or member is obsolete
=> Task.FromResult(CreateHealthCheckGroupWithResultViewModel(healthCheckGroup));
#pragma warning restore CS0618 // Type or member is obsolete
Task<HealthCheckWithResultPresentationModel> CreateHealthCheckWithResultViewModelAsync(HealthCheck healthCheck)
#pragma warning disable CS0618 // Type or member is obsolete
=> Task.FromResult(CreateHealthCheckWithResultViewModel(healthCheck));
#pragma warning restore CS0618 // Type or member is obsolete
}
@@ -5,8 +5,5 @@ namespace Umbraco.Cms.Api.Management.Factories;
public interface IIndexPresentationFactory
{
[Obsolete("Use CreateAsync() instead. Scheduled for removal in v19.")]
IndexResponseModel Create(IIndex index);
Task<IndexResponseModel> CreateAsync(IIndex index) => Task.FromResult(Create(index));
}
@@ -28,17 +28,21 @@ public class IndexPresentationFactory : IIndexPresentationFactory
_logger = logger;
}
/// <inheritdoc />
[Obsolete("Use CreateAsync() instead. Scheduled for removal in v19.")]
public IndexResponseModel Create(IIndex index)
=> CreateAsync(index).GetAwaiter().GetResult();
[Obsolete("Use the non obsolete method instead. Scheduled for removal in v17")]
public IndexPresentationFactory(IIndexDiagnosticsFactory indexDiagnosticsFactory, IIndexRebuilder indexRebuilder, IIndexingRebuilderService indexingRebuilderService)
:this(
indexDiagnosticsFactory,
indexRebuilder,
indexingRebuilderService,
StaticServiceProvider.Instance.GetRequiredService<ILogger<IndexPresentationFactory>>())
{
}
/// <inheritdoc />
public async Task<IndexResponseModel> CreateAsync(IIndex index)
public IndexResponseModel Create(IIndex index)
{
var isCorrupt = !TryGetSearcherName(index, out var searcherName);
if (await _indexingRebuilderService.IsRebuildingAsync(index.Name))
if (_indexingRebuilderService.IsRebuilding(index.Name))
{
return new IndexResponseModel
{
@@ -59,7 +63,7 @@ public class IndexPresentationFactory : IIndexPresentationFactory
var properties = new Dictionary<string, object?>();
foreach (KeyValuePair<string, object?> property in indexDiag.Metadata)
foreach (var property in indexDiag.Metadata)
{
if (property.Value is null)
{
@@ -67,7 +71,7 @@ public class IndexPresentationFactory : IIndexPresentationFactory
}
else
{
Type propertyType = property.Value.GetType();
var propertyType = property.Value.GetType();
properties[property.Key] = propertyType.IsClass && !propertyType.IsArray ? property.Value?.ToString() : property.Value;
}
}
@@ -29,7 +29,7 @@ public class ModelsBuilderPresentationFactory : IModelsBuilderPresentationFactor
new()
{
Mode = _modelsBuilderSettings.ModelsMode,
CanGenerate = _modelsBuilderSettings.ModelsMode is Constants.ModelsBuilder.ModelsModes.SourceCodeManual or Constants.ModelsBuilder.ModelsModes.SourceCodeAuto,
CanGenerate = _modelsBuilderSettings.ModelsMode.SupportsExplicitGeneration(),
OutOfDateModels = _outOfDateModels.IsOutOfDate,
LastError = _mbErrors.GetLastError(),
Version = ApiVersion.Current.Version.ToString(),
@@ -18,6 +18,16 @@ public class RelationTypePresentationFactory : IRelationTypePresentationFactory
private readonly IDocumentPresentationFactory _documentPresentationFactory;
private readonly IScopeProvider _scopeProvider;
[Obsolete("Please use the non obsoleted constructor. Scheduled for removal in v17")]
public RelationTypePresentationFactory(IUmbracoMapper umbracoMapper)
: this(
umbracoMapper,
StaticServiceProvider.Instance.GetRequiredService<IEntityRepository>(),
StaticServiceProvider.Instance.GetRequiredService<IDocumentPresentationFactory>(),
StaticServiceProvider.Instance.GetRequiredService<IScopeProvider>())
{
}
public RelationTypePresentationFactory(
IUmbracoMapper umbracoMapper,
IEntityRepository entityRepository,
@@ -43,6 +43,39 @@ public class UserPresentationFactory : IUserPresentationFactory
/// <summary>
/// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public UserPresentationFactory(
IEntityService entityService,
AppCaches appCaches,
MediaFileManager mediaFileManager,
IImageUrlGenerator imageUrlGenerator,
IUserGroupPresentationFactory userGroupPresentationFactory,
IAbsoluteUrlBuilder absoluteUrlBuilder,
IEmailSender emailSender,
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
IOptionsSnapshot<SecuritySettings> securitySettings,
IBackOfficeExternalLoginProviders externalLoginProviders)
: this(
entityService,
appCaches,
mediaFileManager,
imageUrlGenerator,
userGroupPresentationFactory,
absoluteUrlBuilder,
emailSender,
passwordConfigurationPresentationFactory,
securitySettings,
externalLoginProviders,
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
StaticServiceProvider.Instance.GetRequiredService<IContentService>(),
StaticServiceProvider.Instance.GetRequiredService<IEnumerable<IPermissionPresentationMapper>>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public UserPresentationFactory(
IEntityService entityService,
AppCaches appCaches,
@@ -54,6 +87,45 @@ public class UserPresentationFactory : IUserPresentationFactory
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
IOptionsSnapshot<SecuritySettings> securitySettings,
IBackOfficeExternalLoginProviders externalLoginProviders,
IUserService userService,
IContentService contentService)
: this(
entityService,
appCaches,
mediaFileManager,
imageUrlGenerator,
userGroupPresentationFactory,
absoluteUrlBuilder,
emailSender,
passwordConfigurationPresentationFactory,
securitySettings,
externalLoginProviders,
userService,
contentService,
StaticServiceProvider.Instance.GetRequiredService<IEnumerable<IPermissionPresentationMapper>>())
{
}
// TODO (V17): Remove the unused userService and contentService parameters from this constructor.
/// <summary>
/// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
/// </summary>
public UserPresentationFactory(
IEntityService entityService,
AppCaches appCaches,
MediaFileManager mediaFileManager,
IImageUrlGenerator imageUrlGenerator,
IUserGroupPresentationFactory userGroupPresentationFactory,
IAbsoluteUrlBuilder absoluteUrlBuilder,
IEmailSender emailSender,
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
IOptionsSnapshot<SecuritySettings> securitySettings,
IBackOfficeExternalLoginProviders externalLoginProviders,
#pragma warning disable IDE0060 // Remove unused parameter - need to keep these until the next major to avoid breaking changes and/or ambiguous constructor errors
IUserService userService,
IContentService contentService,
#pragma warning restore IDE0060 // Remove unused parameter
IEnumerable<IPermissionPresentationMapper> permissionPresentationMappers)
{
_entityService = entityService;
@@ -12,31 +12,27 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Install;
public class PostUnattendedInstallNotificationHandler : INotificationAsyncHandler<UnattendedInstallNotification>
public class CreateUnattendedUserNotificationHandler : INotificationAsyncHandler<UnattendedInstallNotification>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IOptions<UnattendedSettings> _unattendedSettings;
private readonly IUserService _userService;
private readonly IMetricsConsentService _metricsConsentService;
public PostUnattendedInstallNotificationHandler(IOptions<UnattendedSettings> unattendedSettings, IUserService userService, IServiceScopeFactory serviceScopeFactory, IMetricsConsentService metricsConsentService)
public CreateUnattendedUserNotificationHandler(IOptions<UnattendedSettings> unattendedSettings, IUserService userService, IServiceScopeFactory serviceScopeFactory)
{
_unattendedSettings = unattendedSettings;
_userService = userService;
_serviceScopeFactory = serviceScopeFactory;
_metricsConsentService = metricsConsentService;
}
/// <summary>
/// Listening for when the UnattendedInstallNotification fired after a successful unattended install
/// This creates the user and sets the telemetry level based on the 'Unattended' settings.
/// Listening for when the UnattendedInstallNotification fired after a sucessfulk
/// </summary>
/// <param name="notification"></param>
/// <param name="cancellationToken"></param>
public async Task HandleAsync(UnattendedInstallNotification notification, CancellationToken cancellationToken)
{
UnattendedSettings? unattendedSettings = _unattendedSettings.Value;
// Ensure we have the setting enabled (Sanity check)
// In theory this should always be true as the event only fired when a sucessfull
if (_unattendedSettings.Value.InstallUnattended == false)
@@ -87,7 +83,7 @@ public class PostUnattendedInstallNotificationHandler : INotificationAsyncHandle
$"No user found in membership provider with id of {Constants.Security.SuperUserIdAsString}.");
}
// To change the password here we actually need to reset it since we don't have an old one to use to change
//To change the password here we actually need to reset it since we don't have an old one to use to change
var resetToken = await backOfficeUserManager.GeneratePasswordResetTokenAsync(membershipUser);
if (string.IsNullOrWhiteSpace(resetToken))
{
@@ -102,7 +98,5 @@ public class PostUnattendedInstallNotificationHandler : INotificationAsyncHandle
throw new InvalidOperationException("Could not reset password: " +
string.Join(", ", resetResult.Errors.ToErrorMessage()));
}
await _metricsConsentService.SetConsentLevelAsync(unattendedSettings.UnattendedTelemetryLevel);
}
}
@@ -12,6 +12,7 @@ internal static class DocumentVariantStateHelper
culture,
content.Edited,
content.Published,
content.Trashed,
content.AvailableCultures,
content.EditedCultures ?? Enumerable.Empty<string>(),
content.PublishedCultures);
@@ -22,17 +23,23 @@ internal static class DocumentVariantStateHelper
culture,
content.Edited,
content.Published,
content.Trashed,
content.CultureNames.Keys,
content.EditedCultures,
content.PublishedCultures);
private static DocumentVariantState GetState(IEntity entity, string? culture, bool edited, bool published, IEnumerable<string> availableCultures, IEnumerable<string> editedCultures, IEnumerable<string> publishedCultures)
private static DocumentVariantState GetState(IEntity entity, string? culture, bool edited, bool published, bool trashed, IEnumerable<string> availableCultures, IEnumerable<string> editedCultures, IEnumerable<string> publishedCultures)
{
if (entity.Id <= 0 || (culture is not null && availableCultures.Contains(culture) is false))
{
return DocumentVariantState.NotCreated;
}
if (trashed)
{
return DocumentVariantState.Trashed;
}
var isDraft = published is false ||
(culture != null && publishedCultures.Contains(culture) is false);
if (isDraft)
@@ -22,6 +22,17 @@ public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissi
private readonly Lazy<IEntityService> _entityService;
private readonly Lazy<IUserService> _userService;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentPermissionMapper"/> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public DocumentPermissionMapper()
: this(
StaticServiceProvider.Instance.GetRequiredService<Lazy<IEntityService>>(),
StaticServiceProvider.Instance.GetRequiredService<Lazy<IUserService>>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DocumentPermissionMapper"/> class.
/// </summary>
@@ -21,6 +21,18 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware
private readonly IRuntimeState _runtimeState;
private readonly WebRoutingSettings _webRoutingSettings;
[Obsolete("Use the non-obsolete constructor. This will be removed in Umbraco 17.")]
public BackOfficeAuthorizationInitializationMiddleware(
UmbracoRequestPaths umbracoRequestPaths,
IServiceProvider serviceProvider,
IRuntimeState runtimeState,
IOptions<GlobalSettings> globalSettings,
IOptions<WebRoutingSettings> webRoutingSettings,
IHostingEnvironment hostingEnvironment)
: this(umbracoRequestPaths, serviceProvider, runtimeState, webRoutingSettings)
{
}
public BackOfficeAuthorizationInitializationMiddleware(
UmbracoRequestPaths umbracoRequestPaths,
IServiceProvider serviceProvider,
+187 -655
View File
@@ -880,6 +880,70 @@
]
}
},
"/umbraco/management/api/v1/data-type/{id}/references": {
"get": {
"tags": [
"Data Type"
],
"operationId": "GetDataTypeByIdReferences",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/DataTypeReferenceResponseModel"
}
]
}
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource"
}
},
"deprecated": true,
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/data-type/configuration": {
"get": {
"tags": [
@@ -8659,101 +8723,6 @@
]
}
},
"/umbraco/management/api/v1/document/{id}/preview-url": {
"get": {
"tags": [
"Document"
],
"operationId": "GetDocumentByIdPreviewUrl",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "providerAlias",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "culture",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "segment",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/DocumentUrlInfoModel"
}
]
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource"
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/document/{id}/public-access": {
"post": {
"tags": [
@@ -20321,461 +20290,6 @@
]
}
},
"/umbraco/management/api/v1/member-type/folder": {
"post": {
"tags": [
"Member Type"
],
"operationId": "PostMemberTypeFolder",
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/CreateFolderRequestModel"
}
]
}
},
"text/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/CreateFolderRequestModel"
}
]
}
},
"application/*+json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/CreateFolderRequestModel"
}
]
}
}
}
},
"responses": {
"201": {
"description": "Created",
"headers": {
"Umb-Generated-Resource": {
"description": "Identifier of the newly created resource",
"schema": {
"type": "string",
"description": "Identifier of the newly created resource"
}
},
"Location": {
"description": "Location of the newly created resource",
"schema": {
"type": "string",
"description": "Location of the newly created resource",
"format": "uri"
}
},
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
},
"400": {
"description": "Bad Request",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/member-type/folder/{id}": {
"get": {
"tags": [
"Member Type"
],
"operationId": "GetMemberTypeFolderById",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/FolderResponseModel"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource"
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
},
"delete": {
"tags": [
"Member Type"
],
"operationId": "DeleteMemberTypeFolderById",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "OK",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
},
"400": {
"description": "Bad Request",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
},
"put": {
"tags": [
"Member Type"
],
"operationId": "PutMemberTypeFolderById",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/UpdateFolderResponseModel"
}
]
}
},
"text/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/UpdateFolderResponseModel"
}
]
}
},
"application/*+json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/UpdateFolderResponseModel"
}
]
}
}
}
},
"responses": {
"200": {
"description": "OK",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
},
"400": {
"description": "Bad Request",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/tree/member-type/root": {
"get": {
"tags": [
@@ -22233,38 +21747,6 @@
]
}
},
"/umbraco/management/api/v1/news-dashboard": {
"get": {
"tags": [
"News Dashboard"
],
"operationId": "GetNewsDashboard",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/NewsDashboardResponseModel"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/object-types": {
"get": {
"tags": [
@@ -24451,7 +23933,6 @@
"description": "The resource is protected and requires an authentication token"
}
},
"deprecated": true,
"security": [
{
"Backoffice-User": [ ]
@@ -38160,6 +37641,34 @@
],
"type": "string"
},
"DataTypeContentTypeReferenceModel": {
"required": [
"icon",
"id",
"name",
"type"
],
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
},
"icon": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"DataTypeItemResponseModel": {
"required": [
"editorAlias",
@@ -38215,6 +37724,49 @@
},
"additionalProperties": false
},
"DataTypePropertyReferenceModel": {
"required": [
"alias",
"name"
],
"type": "object",
"properties": {
"name": {
"type": "string"
},
"alias": {
"type": "string"
}
},
"additionalProperties": false
},
"DataTypeReferenceResponseModel": {
"required": [
"contentType",
"properties"
],
"type": "object",
"properties": {
"contentType": {
"oneOf": [
{
"$ref": "#/components/schemas/DataTypeContentTypeReferenceModel"
}
]
},
"properties": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/DataTypePropertyReferenceModel"
}
]
}
}
},
"additionalProperties": false
},
"DataTypeResponseModel": {
"required": [
"canIgnoreStartNodes",
@@ -39168,6 +38720,7 @@
"flags",
"id",
"isTrashed",
"urls",
"values",
"variants"
],
@@ -39214,6 +38767,17 @@
}
]
},
"urls": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/DocumentUrlInfoModel"
}
]
},
"deprecated": true
},
"template": {
"oneOf": [
{
@@ -39935,8 +39499,6 @@
"DocumentUrlInfoModel": {
"required": [
"culture",
"message",
"provider",
"url"
],
"type": "object",
@@ -39946,14 +39508,6 @@
"nullable": true
},
"url": {
"type": "string",
"nullable": true
},
"message": {
"type": "string",
"nullable": true
},
"provider": {
"type": "string"
}
},
@@ -40166,7 +39720,8 @@
"NotCreated",
"Draft",
"Published",
"PublishedPendingChanges"
"PublishedPendingChanges",
"Trashed"
],
"type": "string"
},
@@ -40661,7 +40216,7 @@
},
"actionParameters": {
"type": "object",
"additionalProperties": { },
"additionalProperties": {},
"nullable": true
}
},
@@ -41286,7 +40841,7 @@
},
"extensions": {
"type": "array",
"items": { }
"items": {}
}
},
"additionalProperties": false
@@ -41519,6 +41074,7 @@
"id",
"isTrashed",
"mediaType",
"urls",
"values",
"variants"
],
@@ -41558,6 +41114,17 @@
]
}
},
"urls": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/MediaUrlInfoModel"
}
]
},
"deprecated": true
},
"isTrashed": {
"type": "boolean"
},
@@ -42149,8 +41716,7 @@
"nullable": true
},
"url": {
"type": "string",
"nullable": true
"type": "string"
}
},
"additionalProperties": false
@@ -43092,7 +42658,7 @@
"type": "object",
"properties": {
"mode": {
"type": "string"
"$ref": "#/components/schemas/ModelsModeModel"
},
"canGenerate": {
"type": "boolean"
@@ -43118,6 +42684,15 @@
},
"additionalProperties": false
},
"ModelsModeModel": {
"enum": [
"Nothing",
"InMemoryAuto",
"SourceCodeManual",
"SourceCodeAuto"
],
"type": "string"
},
"MoveDataTypeRequestModel": {
"type": "object",
"properties": {
@@ -43256,61 +42831,6 @@
},
"additionalProperties": false
},
"NewsDashboardItemResponseModel": {
"required": [
"header",
"priority"
],
"type": "object",
"properties": {
"priority": {
"type": "string"
},
"header": {
"type": "string"
},
"body": {
"type": "string",
"nullable": true
},
"buttonText": {
"type": "string",
"nullable": true
},
"imageUrl": {
"type": "string",
"nullable": true
},
"imageAltText": {
"type": "string",
"nullable": true
},
"url": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"NewsDashboardResponseModel": {
"required": [
"items"
],
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/NewsDashboardItemResponseModel"
}
]
}
}
},
"additionalProperties": false
},
"NoopSetupTwoFactorModel": {
"type": "object",
"additionalProperties": false
@@ -45158,7 +44678,7 @@
"nullable": true
}
},
"additionalProperties": { }
"additionalProperties": {}
},
"ProblemDetailsBuilderModel": {
"type": "object",
@@ -45369,6 +44889,7 @@
"flags",
"id",
"isTrashed",
"urls",
"values",
"variants"
],
@@ -45415,6 +44936,17 @@
}
]
},
"urls": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/DocumentUrlInfoModel"
}
]
},
"deprecated": true
},
"template": {
"oneOf": [
{
@@ -32,6 +32,10 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
_authorizeCallbackLogoutPathName = securitySettings.Value.AuthorizeCallbackLogoutPathName;
}
[Obsolete("Please use the overload that allows for multiple back-office hosts. Will be removed in V17.")]
public async Task EnsureBackOfficeApplicationAsync(Uri backOfficeUrl, CancellationToken cancellationToken = default)
=> await EnsureBackOfficeApplicationAsync([backOfficeUrl], cancellationToken);
public async Task EnsureBackOfficeApplicationAsync(IEnumerable<Uri> backOfficeHosts, CancellationToken cancellationToken = default)
{
// Install is okay without this, because we do not need a token to install,
@@ -96,7 +100,8 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
public async Task DeleteBackOfficeClientCredentialsApplicationAsync(string clientId, CancellationToken cancellationToken = default)
=> await Delete(clientId, cancellationToken);
internal OpenIddictApplicationDescriptor BackofficeOpenIddictApplicationDescriptor(Uri backOfficeUrl)
[Obsolete("Do not use - for internal usage only. Will be made internal in V17.")]
public OpenIddictApplicationDescriptor BackofficeOpenIddictApplicationDescriptor(Uri backOfficeUrl)
=> BackofficeOpenIddictApplicationDescriptor([backOfficeUrl]);
internal OpenIddictApplicationDescriptor BackofficeOpenIddictApplicationDescriptor(Uri[] backOfficeHosts)

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