Compare commits

...
Author SHA1 Message Date
Nhu DinhandGitHub 59f9bf290d Build: V15 Disable schedule nightly E2E trigger (#22203)
Disable schedule nightly E2E trigger
2026-03-20 17:00:08 +07:00
b6b8f54c65 V15 QA Added the authorization integration tests (#18419)
* Added authorization integration tests

* Removed unnecessary tests and update tests for preview controller

* Updated to use the newest changes from v15/dev and added an override for the AuthenticateClientAsync to use the userGroupKey

* Updated CompatibilitySuppressions to include changes from integration tests

* Updated pipelines

* Skips managementApi tests

* Only run necessary tests

* Added new schema per fixture to reduce test setup time

* Fixed failing tests

* Updated test setup

* Updated test

* Added suppression

* Fixed failing tests

* Updated addOnTeardown methods to protected

* Added method for clearing the host

* Added teardown

* Updated model usage

* Added a lot of cleanup for memory leak issues when running tests

* Added CompatibilitySuppressions.xml

* Updated tests

* Cleaned up

* Adjusted base classes

* Updated pipeline

* Updated CompatibilitySuppressions.xml

* Updated test logging

* Fixed reponse

* Updated condition to skip tests

* Updated tests, not done

* Reworked test to expect correct responses with correct setup

* Updated tests

* More updates to tests

* Updated tests

* Cleaned up tests

* Updated setup

* Cleaned up tests to match setup

* Cleaned up setup

* Removed suppression

* Fixed tests

* Move order of checks

* Fix naming

* Formatting

* Dispose of host

* Keep track of if we're disposed

* Compat suppression

* Dont dispose

* Fix failing tests

* removed unused virtual

* Updated CompatibilitySuppressions.xml

---------

Co-authored-by: Andreas Zerbst <andr317c@live.dk>
Co-authored-by: Zeegaan <skrivdetud@gmail.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2025-10-06 20:49:09 +02:00
Andreas ZerbstandGitHub 50139ee506 E2E Pipeline: QA Remove continue on error condition (#20356)
* Removed continue on error

* Planted flaky test for testing on pipeline

* Fixed test
2025-10-03 09:39:05 +07:00
Andreas ZerbstandGitHub 62947b2862 V15 QA Added external login provider tests and split pipeline into templates (#20049)
* Added setup for external login

* Started on yaml

* Added test file

* Updated pipeline

* Use env vars

* Added env variables and commented out test we don't need to run

* Removed list from matrix

* Updated condition

* Updated package path

* Updated testFolder

* double slash

* Updated condition

* Updated condition again

* Added port

* Removed redundant values

* Set as env vars

* Added env vars beneath matrix

* Get env

* Updated naming

* Updated usage of values

* Added a check for client id, to see if value set

* Moved env out of pool

* Tried moving env

* Trying to fix the env being empty

* Removed env

* Updated name of variable

* Fixed A cyclical reference

* Updated typo

* More logging

* Reverted change

* Added env

* Added env to tests

* Cleaned up

* Added yaml template files

* Updated nighly pipepline to use templates

* Updated sln

* Split yaml into templates for e2e setup

* Updated pipeline

* Updated solution file

* Set value

* Added if statement

* Added variables

* Set default values

* Updated values

* Updated condition

* Run multiple tests

* Added env

* Updated from parameter to variable

* Fixed condition

* Fixed condition to use actual value

* Updated npx wait on command

* Updated pwsh

* Updated port again

* Updated port value

* Updated wait on

* Updated condition

* Restructured

* Updated var

* Updated run application steps

* Added echo

* Updated to boolean

* Updated conditions

* Updated test template usage

* Added databaseType

* Added another databaseType

* Split up templates

* Fixed indentation

* Updated condition

* updated path

* removed build from path

* Updated conditions for azureAd

* Fixed indentation

* Updated to single qoutes

* Cleaned up

* Removed unused file

* Clarified namin

* Moved

* Updated pipeline, not done

* Updated locator

* Updated pipelines

* Updated test helpers package

* Skipped build stage for default app settings tests

* Updated password var

* Updated locators

* Updated defaultconfig build setup

* Split E2E stage in two

* Added parameter for skipping integration tests

* Cleaned up

* Added ASPNETCORE_URLS
2025-09-05 08:42:58 +02:00
Andreas ZerbstandGitHub 1748a08f7b V15 QA updated failing acceptance tests on the nightly pipeline (#19882)
* Updated tests

* Bumped test helpers
2025-08-11 09:05:17 +02:00
Andreas ZerbstandGitHub 3264d58f13 V15 QA acceptance tests with appsettings (#19550)
* Start of appsetting

* Updated setup of playwright

* Adjusted the pipeline

* Updated appsetting

* Added install test

* Added comments

* Updated pipeline

* Updated development app settings

* Commented tests out

* comment

* Added if statement

* Updated pipeline

* Fixed condition

* Changed to production

* Added a log

* Updated copy item

* Added

* Updated app settings

* Updated pipeline

* Moved playwright login

* Updated pipeline

* Updated app setting

* Updated nightly

* Updated appsettings

* Updated get

* Updated wait on

* Updated appsettings

* Updated connection string

* Updates

* Skips code

* Updated variable

* Updated pipeline

* We want to always retain the trace, to see if the test runs as expected on the pipeline

* Added a temporary wait till port is open

* Fixed condition

* Added missing tcp for wait on

* Updated URL env

* Updated setup

* Fixed string

* Updated locator

* Split tests into SQLite and SQLServer

* Updated pipeline to run all tests

* Retain trace on failure

* Added testFolder var

* Added appsettings and program for delivery api tests

* Updated playwright config

* Split test runners into defaultconfig and different app settings

* Added delivery api tests

* Cleaned up tests

* Bumped version

* Updated pipeline

* Small fixes

* Added password

* Updated connection string

* Fixed

* Removed quotes

* Removed unnecessary connection string

* Added missing password

* Cleaned up

* Cleaned up

* Cleaned up

* Updated to use helpers

* Bumped version

* Updated helper usage

* Added password to variables and a condition

* Added check

* Indented value

* Fixed condition

* More updates

* Updated variable
2025-08-05 11:22:03 +02:00
Andy Butland b6be468b3a Merge branch 'v15/dev' of https://github.com/umbraco/Umbraco-CMS into v15/dev 2025-07-29 07:18:37 +02:00
Andy Butland 3ec88aab65 Merge branch 'release/15.4.4' into v15/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-07-29 07:18:24 +02:00
da43086017 Merge commit from fork
Co-authored-by: kjac <kja@umbraco.dk>
2025-07-29 05:10:52 +02:00
d62fe5e315 Fix issue with use of EF Core scopes within notification handlers (take 2 - handling scopes with a base parent) (#19797)
* Add integration tests that shows the problem

* Fix the problem and add explenation

* Improved comments slightly to help when we come back here!
Moved tests alongside existing ones related to scopes.
Removed long running attribute from tests (they are quite fast).

* Fixed casing in comment.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-07-25 13:10:44 +02:00
Andy Butland 852192a5d2 Bumped package.json version to 15.4.4. 2025-07-24 09:27:21 +02:00
Andy Butland f4498c3d05 Bumped version to 15.4.4. 2025-07-21 11:56:22 +02:00
Nhu DinhandGitHub b045b33049 V15 QA Removed unused API acceptance tests (#19680)
Removed unused API acceptance tests
2025-07-09 10:11:37 +07:00
LiliandGitHub 32a4fbebd5 V15 Bugfix: Horizontal scrollbar in Media section #19479 (#19541)
fix to stop the scroll bar appearing in the media section.
2025-06-30 14:52:35 +02:00
Andy Butland cc2f1df78e Merge branch 'release/15.4.3' into v15/dev
# Conflicts:
#	version.json
2025-06-10 11:43:50 +02:00
Andy ButlandandCopilot 9b2fd1253b Optimize initialization of document URLs on start-up (#19498)
* Optimize initialization of document URLs on startup.

* Apply suggestions from code review

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-10 10:31:26 +02:00
Andy Butland 024697f3bf Bumped version to 15.4.3 2025-06-10 10:28:55 +02:00
Andy Butland d8b76575ca Merge branch 'v15/dev' of https://github.com/umbraco/Umbraco-CMS into v15/dev 2025-06-03 07:04:56 +02:00
Andy Butland 5b00312158 Merge branch 'release/15.4.2' into v15/dev
# Conflicts:
#	version.json
2025-06-03 07:04:43 +02:00
Andy ButlandandGitHub d920e93d1e Merge commit from fork 2025-06-03 05:21:11 +02:00
MoleandGitHub feba51a49d Use correct service name in obsoletion method (#19398)
Use correct service in obsoletion method
2025-05-23 09:46:16 +00:00
Andy Butland 853c1acf7e Bumped version to 15.4.2 2025-05-20 13:10:02 +01:00
3f10bd8c21 Added logging and try/catch around retrieval of references, so we don't block critical operations following an incompatible data type change (#18576)
* Added logging and try/catch around retrieval of references, so we don't block critical operations following an incompatible data type change.

* Added a little more detail to the log message.

* Added a little more detail to the log message.

* Fix unittest mock dependency

---------

Co-authored-by: Migaroez <geusens@gmail.com>
2025-05-19 10:54:22 +02:00
Andreas ZerbstandGitHub b13eb8aaf8 V15 Added second level block acceptance tests (#19264) 2025-05-12 09:23:55 +00:00
Andreas ZerbstandGitHub 275478066e V15 QA complex block grid test (#18347) 2025-05-08 11:12:37 +02:00
Andreas ZerbstandGitHub b01def0872 V15 QA added a fix for flaky integration tests run on SQL Server Linux (#18965) 2025-05-08 08:53:29 +02:00
Nhu DinhandGitHub b106305167 V15 QA Added acceptance tests for tiptap style select (#19234) 2025-05-06 07:15:30 +00:00
Nhu DinhandGitHub 103631de15 V15 QA Updated the build stage to align with the azure pipelines yml (#19235) 2025-05-06 06:59:32 +00:00
Andy Butland 1b19ca7cdb Merge branch 'release/15.4.1' into v15/dev
# Conflicts:
#	version.json
2025-05-05 10:31:48 +02:00
Andy ButlandandGitHub 0ad020f0ce Restored interface methods on obsolete IBackgroundTaskQueue (#19223)
* Restore interface methods on obsolete IBackgroundTaskQueue.

* Fixed typos in comment.
2025-05-05 07:38:31 +02:00
Andy Butland fc1455e0d8 Bumped version to 15.4.1. 2025-05-03 17:28:44 +02:00
Andy Butland 793534c627 Merge branch 'release/15.4' into v15/dev
# Conflicts:
#	version.json
2025-05-01 07:38:58 +02:00
Andy Butland 15a8b4066a Bumped version to 15.4.0. 2025-04-30 15:04:08 +02:00
Andy ButlandandGitHub 38f6e2b0da Updates Examine to latest patch release. (#19193) 2025-04-30 09:57:55 +02:00
Andy Butland 14f60a108a V13: Clear Member Username Cache in Load Balanced Environments (#19191)
* Clear usernamekey

* Odd explaining comment

* Update src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs

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

* Make UserNameCachePrefix readonly for better immutabilityly

* Move prefix to CacheKeys constants

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
# Conflicts:
#	src/Umbraco.Core/Cache/CacheKeys.cs
2025-04-29 18:10:44 +02:00
Jacob Overgaard 10518ed454 Merge branch 'release/15.4' into v15/dev 2025-04-29 15:51:45 +02:00
Jacob Overgaard 375a0b4388 bump to 15.4.0-rc3 2025-04-29 15:34:59 +02:00
Jacob OvergaardandGitHub 6300ccc211 fix: The server path is not appended to stylesheets before load in Backoffice (#19192)
* build(deps): bump tiptap from 2.11.5 to 2.11.7

* fix: prepends the system `/css` folder to stylesheets before attempting to load them

* fix: adds more safety around path assumptions

* chore: eslint fix

* fix: prepend only the system path to picked stylesheets
2025-04-29 13:33:26 +00:00
Jacob Overgaard 5b6f544d2a build: restores some of the behavior from V13 in relation to StaticAssets (#19189)
In v13, the StaticAssets build was only triggered based on the existence of either the output folder or a preserve.* marker file. Here, we also additionally check for the node_modules/.package-lock.json file before reinstalling npm dependencies. We also now only run `npm install` rather than `npm ci` to optimise the build.
2025-04-29 13:10:29 +02:00
Jacob Overgaard a1379f13a0 Merge branch 'contrib' into v15/dev 2025-04-29 13:05:06 +02:00
Jacob OvergaardandGitHub 38e9781fc5 build: restores some of the behavior from V13 in relation to StaticAssets (#19189)
In v13, the StaticAssets build was only triggered based on the existence of either the output folder or a preserve.* marker file. Here, we also additionally check for the node_modules/.package-lock.json file before reinstalling npm dependencies. We also now only run `npm install` rather than `npm ci` to optimise the build.
2025-04-29 13:04:24 +02:00
Nhu DinhandGitHub dfc6ead6d5 V15 Added acceptance tests for tiptap statusbar (#19131)
* Updated tests for tiptap RTE

* Moved tests for titptap toolbar to another class

* Added tests for titptap toolbar

* Added tests for tiptap statusbar

* Bumped version

* Make tiptap tests run in the pipeline

* Bumped version

* Reverted npm command
2025-04-29 07:39:56 +00:00
Andy Butland 30a431f870 Merge branch 'release/15.4' into v15/dev 2025-04-28 14:37:26 +02:00
e932fa5404 Webhook log authorization and file system path checks (#19177)
* Add authorization for webhooks to item and log endpoints.

* Remove full path details from exception when requesting a path outside of the physical file system's root.

* Added missing usings.

* Revert changes to the webhook items API

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-04-28 12:10:43 +00:00
Lotte PitcherandSebastiaan Janssen 3579c4ac9e readme shield for forum 2025-04-28 12:57:51 +02:00
Sebastiaan Janssen 671751b618 Merge branch 'v15/dev' into contrib 2025-04-28 12:55:17 +02:00
Nhu DinhandGitHub 3b6e4a96f1 V15 QA Added acceptance tests for bulk trash dialog (#19125)
* Added tests for bulk trash content dialog

* Updated tests for trash content dialog

* Added tests for trash and bulk trash media dialog

* Moved trash content tests into a folder

* Bumped version

* Make trash tests run in the pipeline

* Make trash tests run in the pipeline

* Fixed comments

* Reverted npm command
2025-04-26 09:59:03 +07:00
mole 83e580c3a7 Fix nuget publish stage 2025-04-25 13:31:34 +02:00
Andy Butlandandmole 31ee35e721 Fixed error with reflection on integration test configure builder attributes, so integration tests can be created outside of the Umbraco integration test project (#19077)
* Fixed error with reflection on integration test configure builder attributes, so integration tests can be created outside of the Umbraco integration test project.

* Fix nullability

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-04-24 13:18:57 +02:00
MoleandKenn Jacobsen cf0f3f1380 V15: Ensure elements cache is cleared on subscribers in load balanced scenarios (#19128)
* Clear elementscache from cache refreshers

* Add very simple test ensuring the elements cache is cleared

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2025-04-24 13:18:53 +02:00
b4528cf963 Fixed error with reflection on integration test configure builder attributes, so integration tests can be created outside of the Umbraco integration test project (#19077)
* Fixed error with reflection on integration test configure builder attributes, so integration tests can be created outside of the Umbraco integration test project.

* Fix nullability

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-04-24 13:18:29 +02:00
cb1ec988ce V15: Ensure elements cache is cleared on subscribers in load balanced scenarios (#19128)
* Clear elementscache from cache refreshers

* Add very simple test ensuring the elements cache is cleared

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2025-04-24 13:15:30 +02:00
Sven Geusensandkjac 31c3f5ae0c Remove fake null checks as they are no longer needed after merge into v16 (#19109) 2025-04-24 12:28:54 +02:00
Warren BuckleyandGitHub 15643135ba Add 'ManifestWithDynamicConditions' to ManifestHeaderApp so Header Apps can be conditionally shown/loaded (#19124) 2025-04-24 12:25:58 +02:00
Sven Geusensandkjac 297ceaf0aa Remove fake null checks as they are no longer needed after merge into v16 (#19109) 2025-04-24 11:46:41 +02:00
Andy Butland 8e87f2260c Bumped version in dev branch to 15.5.0-rc. 2025-04-22 09:52:10 +02:00
Andy Butland 927e4d8cad Merge branch 'release/15.4' into v15/dev 2025-04-22 09:45:15 +02:00
Lotte PitcherandGitHub cd71e5666a Incorrect forum and security urls when raising issue (#19080) 2025-04-22 06:51:08 +02:00
Sven GeusensandMigaroez c38faec74b V15/fix/sub variant block deletion (#18802)
* Fix

* Editors with access should be able to clear a blocklist value

* Writeup around block element level variation

* Dissallow values to be removed a limited language user does not have permissions to

* Remove commented out code

* improved comments

* Improve expose list for limited language access sub variant block lists
2025-04-21 10:37:14 +02:00
Sven GeusensandGitHub c86a6fa8e5 V15/fix/sub variant block deletion (#18802)
* Fix

* Editors with access should be able to clear a blocklist value

* Writeup around block element level variation

* Dissallow values to be removed a limited language user does not have permissions to

* Remove commented out code

* improved comments

* Improve expose list for limited language access sub variant block lists
2025-04-21 10:30:12 +02:00
Andy Butlandandleekelleher 1e521760df Removes tree actions from tree selection in duplicate document dialog. (#19053)
(cherry picked from commit 1ccb5cc09e)
2025-04-16 18:23:26 +01:00
Andy ButlandandGitHub 1ccb5cc09e Removes tree actions from tree selection in duplicate document dialog. (#19053) 2025-04-16 18:21:39 +01:00
Jacob Overgaard 12ca71558a Merge branch 'release/15.4' into v15/dev 2025-04-16 14:48:09 +02:00
Jacob Overgaard 8b759cf3a4 bump version to 15.4.0-rc2 2025-04-16 14:47:55 +02:00
Jacob Overgaard 25fffd4173 Merge remote-tracking branch 'origin/release/15.4' into v15/dev 2025-04-16 14:47:02 +02:00
29c0151460 Scaffold content for content templates serverside (#19054)
* Scaffold content for content templates serverside

* Generated client types and methods from API.

* Retrieve scaffolded blueprint when creating documents from a blueprint.

* Use introduced helper method on existing read.

* Cleaned up imports.

* feat: moves scaffold service logic to data source and make shallow repository method

* feat: follows UmbDataSourceResponse interface and reorders public/private methods

* Bumped version to 15.4.0-r2.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2025-04-16 12:39:07 +00:00
Migaroez beb60065bb Merge branch 'release/15.4' into v15/dev 2025-04-15 14:40:19 +02:00
Jacob Overgaard cc9c33bfe6 fix: adds missing export 2025-04-15 12:57:57 +02:00
Jacob Overgaard 6edffd9f08 docs: fix import path 2025-04-15 12:23:37 +02:00
Jacob Overgaard 7e57396960 Merge branch 'contrib' into v15/dev 2025-04-15 09:21:11 +02:00
Andy Butland 981f173a79 Clarified "too many" entries validation message. (#19040) 2025-04-15 08:35:49 +02:00
Andy ButlandandGitHub 83fe87ca88 Clarified "too many" entries validation message. (#19040) 2025-04-15 06:34:58 +00:00
Andy Butland 432dda8c47 Adds some missing mime types to ensure uploaded audio and video displays with preview. (#19039) 2025-04-15 08:04:11 +02:00
Andy ButlandandGitHub ae078bf96b Adds some missing mime types to ensure uploaded audio and video displays with preview. (#19039) 2025-04-15 08:03:10 +02:00
6455f96e51 CONTRIBUTING.md - fix incorrect link to contributing-first-issue.md (#19038)
Co-authored-by: Andrew Lacey <AndrewL@if.co.nz>
2025-04-15 06:35:10 +02:00
Andy Butlandandkjac a9fc88d7e6 Make sure not to early return when verifying ancestor path is published without completing scope (#19029)
* Make sure not to early return when verifying ancestor path is published without completing scope.

* Added comment.
2025-04-14 17:46:57 +02:00
Andy ButlandandGitHub 76e2103551 Make sure not to early return when verifying ancestor path is published without completing scope (#19029)
* Make sure not to early return when verifying ancestor path is published without completing scope.

* Added comment.
2025-04-14 17:45:57 +02:00
340ae6c474 Document 3rd Party Licenses (#19027)
* specifies the license for base64-js

* specifies the license for router-slot

* specifies the license for contentment/bytes

* adds a general third-party notices file for all direct dependency libraries

* merge separate licenses for icon packs into NOTICES.txt

* updates lee kelleher permission

* updates the name of DOMPurify

* adds Element Internals Polyfill

* capitalize names

* Update src/Umbraco.Web.UI.Client/src/packages/core/utils/bytes/bytes.function.ts

Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com>

* adds babel

* adds eslint

* adds hey-api/openapi-ts

* adds open web components

* adds playwright

* adds rollup

* adds storybook

* adds cross-env

* adds glob

* adds globals

* adds madge

* adds msw

* adds playwright-msw

* adds prettier

* adds remark-gfm

* adds svgo

* adds tiny-glob

* adds tsc-alias

* adds typedoc

* adds typescript

* adds typescript-eslint

* adds typescript-json-schema

* adds vite-plugin-static-copy

* adds vite-tsconfig-paths

* adds web component analyzer

* adds rollup-plugin-esbuild

* adds rollup-plugin-import-css

* adds rollup plugins as general license

---------

Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com>
2025-04-14 16:01:14 +02:00
Kenn JacobsenandGitHub 7495c3c7b2 Treat content schedule dates as UTC (#19028) 2025-04-14 14:50:02 +02:00
Sebastiaan Janssen 449c94f08d Merge branch 'v15/dev' into contrib 2025-04-14 14:22:36 +02:00
83b92666ac Use maximum available value for JSON serialization depth (#19001)
* Use maximum available value for JSON serialization depth.

* Updated unittests with best practices

---------

Co-authored-by: Migaroez <geusens@gmail.com>
2025-04-14 13:50:19 +02:00
Henrik GedionsenandSebastiaan Janssen b7bf30744a Use GetIdsFromPathReversed to avoid allocating the string values of the integers parsed 2025-04-14 11:19:08 +02:00
dependabot[bot]andJacob Overgaard 9e97f6028b Bump vite from 6.2.5 to 6.2.6 in /src/Umbraco.Web.UI.Client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.5 to 6.2.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.6/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 11:01:59 +02:00
f9496e8067 Ensure dates read from the database are treated as local when constructing entities (2) (#19013)
* Revert "Ensure dates read from the database are treated as local when constructing entities (#18989)"

This reverts commit 7b10d39d66.

* Avoid system dates stored with local server time being defaulted to UTC on database read.

* ContentScheduleDto.Date is UTC

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-04-14 10:55:36 +02:00
dependabot[bot]andJacob Overgaard 8ba3a4d926 Bump vite from 6.2.5 to 6.2.6 in /src/Umbraco.Web.UI.Login
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.5 to 6.2.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.6/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 10:22:22 +02:00
Nhu DinhandGitHub 7fe6971a61 V15 Updated acceptance tests for unsupported invariant content item with variant blocks (#18919)
* Updated tests for invariant block grid with variant blocks

* Updated tests for invariant block list with variant blocks

* Updated tests for invariant document with variant blocks

* Bumped version

* Changed npm command

* Updated isValidationMessageVisible

* Fixed name

* Fixed comments

* Reverted npm command
2025-04-12 08:59:31 +00:00
Andy Butlandandmole 7b10d39d66 Ensure dates read from the database are treated as local when constructing entities (#18989)
* Ensure dates read from the database are treated as local when constructing entities.

* Fixed typos in comments.
2025-04-11 13:19:13 +02:00
Andy ButlandandGitHub 9ef2b37be4 Ensure dates read from the database are treated as local when constructing entities (#18989)
* Ensure dates read from the database are treated as local when constructing entities.

* Fixed typos in comments.
2025-04-11 09:32:05 +02:00
Nhu DinhandGitHub 516d62a348 V15 Added acceptance tests for the mandatory property editor in a content or a block (#18920)
* Added tests for mandatory checkboxlist in a content

* Added tests for mandatory dropdown in a content

* Added tests for mandatory media picker in a content

* Added tests for mandatory radiobox in a content

* Added tests for block grid with mandatory property editors

* Added tests for block list with mandatory property editors

* Bumped version

* Cleaned up

* Changed npm command

* Fixed comments

* Fixed comments

* Reverted
2025-04-11 03:13:36 +00:00
Niels LyngsøandGitHub 97b3023e14 make sure only to prepend relative URLs (#18998) 2025-04-10 14:56:14 +01:00
Niels Lyngsø ff30b4f686 Merge branch 'contrib' into v15/dev 2025-04-09 16:56:48 +02:00
dependabot[bot]andJacob Overgaard 6bca91a52f Bump koa from 2.15.3 to 2.16.1 in /src/Umbraco.Web.UI.Client
Bumps [koa](https://github.com/koajs/koa) from 2.15.3 to 2.16.1.
- [Release notes](https://github.com/koajs/koa/releases)
- [Changelog](https://github.com/koajs/koa/blob/master/History.md)
- [Commits](https://github.com/koajs/koa/compare/2.15.3...v2.16.1)

---
updated-dependencies:
- dependency-name: koa
  dependency-version: 2.16.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 15:57:36 +02:00
164b80af0e Display content type names on dynamic node query steps (#18742)
* Display content type names on dynamic node query steps.

* Refactored to use `UmbRepositoryItemsManager` observable

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2025-04-09 11:47:25 +00:00
Niels LyngsøandGitHub ba0028c03d Feature: Use uui invalid color (Awaiting new UUI version) (#18273)
* display validation on save + use uui-color-invalid

* update css vars

* use standalone color for property layout

* remove color from label

* fix badge

* fix create button color

* clean up

* correct badge colors
2025-04-09 10:14:26 +00:00
6f38a57c8a Document permission inheritance in UI (#18935)
* check the full path for permissions

* fix race condition

* wip update permission when variants change

* Populate ancestor keys on document tree response items.

* Populate ancestor keys on document collection response items.

* Update OpenApi.json

* generate server models

* update types

* map data

* add ancestor context

* set ancestors in context

* use ancestor context in tree

* clean up

* provide ancestor context from a collection item

* provide ancestor context from structure context

* Use array of objects rather than Ids for the ancestor collection.

* Update OpenApi.json.

* add ancestor data to mocks

* set ancestors ids in mocks

* omit ancestors for recycle bin item

* use correct models for document blueprint mock data

* remove constructor

* mock documents for testing

* add user group permission test data

* wip document user permission condition tests

* generate new server models

* update data efter server models update

* clean up

* Update entity-actions-table-column-view.element.ts

* longer time for not found to appear

* use arg

* observe alias

* set new the right place

* remove const

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-04-09 11:08:28 +02:00
Andy ButlandandGitHub a0e3ca601e Aggregate document permissions for current user in API response (#18721)
* Create integration test verifying existing behaviour.

* Aggregate permissions per document for the current user response.

* Refactoring following Codescene warnings.
2025-04-09 10:57:50 +02:00
1a9d8e3a5d Fixes label of enable/prevent clean up button after update (#18946)
* Update enable cleanup button text on toggle.

* Handle console error visible on cancel of rollback dialog.

* Return value from `umb-rollback-modal`

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
2025-04-09 08:15:00 +00:00
76f27726c5 V15: User password resetting notification (#18679)
* Introduce UserPasswordResettingNotification

* Removed changes to IUserService interface.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-04-09 09:28:12 +02:00
d9d66465f6 Allow save of empty translations for dictionary items (#18980)
* Allow save of empty translations for dictionary items.

* Updated Open API schema to match request model update

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-04-09 09:19:48 +02:00
947afdbc1e Amend root content routing and ensure trailing slashes as configured (#18958)
* Amend root content routing and ensure trailing slashes as configured

* Fix false positives at root + add more tests

* Awaited async method and resolved warning around readonly.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-04-09 07:46:24 +02:00
Lee KelleherandGitHub 134c8006c0 umb-localize encode HTML arguments (#18960)
* Moves `escapeHTML` call from localization controller to `umb-localize` element

* Adds supporting unit-test

* Removed unit-test

as it is now expected that the localization
controller will return literal HTML markup.

* Updated import path

* Removed extra call to `text()`
2025-04-08 12:32:36 +00:00
Andreas ZerbstandGitHub 86e7343f99 V15 QA added clipboard test for not being able to copy to root when block is not allowed at root (#18937)
* Added clipboard test

* Bumped version

* Updated to use the name

* Run all tests on the pipeline

* Reverted command
2025-04-08 09:53:07 +02:00
dependabot[bot]andJacob Overgaard 10ecc99d74 Bump vite from 6.2.4 to 6.2.5 in /src/Umbraco.Web.UI.Login
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.4 to 6.2.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.5/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 09:23:23 +02:00
84d0ae3ea2 V15 QA Fixing the failing media acceptance tests (#18881)
* Fixed the function name due to test helper changes

* Updated assertion steps due to UI changes

* Added more waits

* Bumped version

* Increase timeout

* Reverted

---------

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2025-04-08 12:41:32 +07:00
Andy Butland aa76f782ef Merge branch 'release/15.3.1' into v15/dev 2025-04-08 07:17:01 +02:00
Andy ButlandandGitHub 06a2a500b3 Merge commit from fork
* Prevent path traveral vulnerability with upload of temporary files.

* Used BadRequest instead of NotFound for invalid file name response.
2025-04-08 05:03:40 +02:00
Niels LyngsøandGitHub 17a5218a58 fix 18367 (#18956) 2025-04-07 18:45:22 +02:00
Niels LyngsøandGitHub 4c9084238c Feature: discard changes for block workspace (#18930)
* make getHasUnpersistedChanges public

* Discard changes impl for Block Workspace
2025-04-07 18:42:31 +02:00
9c03422222 Data type References UI: Workspace + Delete (#18914)
* Updated management API endpoint and model for data type references to align with that used for documents, media etc.

* Refactoring.

* Update src/Umbraco.Core/Constants-ReferenceTypes.cs

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

* Fixed typos.

* generate server models

* add extension slot

* register data type reference info app

* add reference data mappers

* Added id to tracked reference content type response.

* Updated OpenApi.json.

* Added missing updates.

* generate new models

* update models

* register ref item

* remove debugger

* render types

* register member type property type ref

* register media type property type ref

* Renamed model and constants from code review feedback.

* register reference workspace info app kind

* use kind for document references

* use kind for media references

* use kind for member references

* use deleteWithRelation kind when deleting data types

* fix manifest types

* fix types

* Update types.gen.ts

* update code to fit new server models

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-07 16:24:29 +00:00
Sebastiaan Janssen b61b9dbd5e Merge branch 'v15/dev' into contrib 2025-04-07 16:58:22 +02:00
HenrikandGitHub d9a31d337a Avoid allocating strings for parsing comma separated int values (#18199) 2025-04-07 16:56:59 +02:00
Henrik GedionsenandSebastiaan Janssen 822c8374ef Avoid allocating new empty arrays, reuse existing empty array 2025-04-07 16:31:31 +02:00
Henrik GedionsenandSebastiaan Janssen 4239f0fc06 Remove unneeded double seek 2025-04-07 16:24:31 +02:00
Henrik GedionsenandSebastiaan Janssen db43799d0f Avoid some heap allocations 2025-04-07 16:23:45 +02:00
HenrikandGitHub 2723e4f77c Avoid unneeded Dictionary operations (#18890) 2025-04-07 15:02:28 +02:00
Jacob Welander JensenandGitHub f926316751 V15/fix/18595 (#18925)
* fix for #18595

* updates the en.ts
2025-04-07 14:53:01 +02:00
Mads RasmussenandGitHub b991976fd3 Fix modal route registration circular import (#18953)
* fix modal route registration circular import

* Update modal-route-registration.controller.ts
2025-04-07 11:39:09 +00:00
Mads RasmussenandGitHub c0f820a05c remove segment toggle for elements (#18949) 2025-04-07 11:41:27 +02:00
Mads RasmussenandGitHub f6d93ef0ba fix circular icon import (#18952) 2025-04-07 11:39:51 +02:00
Andy ButlandandGitHub 3d1e17b07f Integration tests for content publishing with ancestor unpublished (#18941)
* Resolved warnings in test class.

* Refactor regions into partial classes.

* Aligned test names.

* Variable name refactoring.

* Added tests for unpublished paths.

* Adjust tests to verify current behaviour.

* Cleaned up project file.
2025-04-07 10:04:11 +02:00
ae423decc3 Add raw value validation to multiple text strings property editor (#18936)
* Add raw value validation to multiple text strings property editor

* Added additional assert on unit test and comment on validation logic.

* Don't remove items to obtain a valid value

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-04-07 09:40:17 +02:00
dependabot[bot]andJacob Overgaard 1d9a031c1c Bump vite from 6.2.4 to 6.2.5 in /src/Umbraco.Web.UI.Client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.4 to 6.2.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.5/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 08:56:42 +02:00
Andy ButlandandGitHub 4078e83634 Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined (#18763)
* Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined.

* Added tests to verify functionality.

* Added reference to previous PR.

* Referenced second PR.

* Assemble URLs for all cultures, not just the default.

* Revert previous update.

* Display an original URL if we have one.
2025-04-07 08:18:06 +02:00
Nikolaj Geisle 4b016317f9 Skip lock tests 2025-04-04 17:34:08 +02:00
1f4c19d484 Updated management API endpoint and model for data type references to align with that used for documents, media etc. (#18905)
* Updated management API endpoint and model for data type references to align with that used for documents, media etc.

* Refactoring.

* Update src/Umbraco.Core/Constants-ReferenceTypes.cs

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

* Fixed typos.

* Added id to tracked reference content type response.

* Updated OpenApi.json.

* Added missing updates.

* Renamed model and constants from code review feedback.

* Fix typo

* Fix multiple enumeration

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-04-04 15:10:06 +02:00
fd77074d57 Added management API endpoint, service and repository for retrieval of references from the recycle bin (#18882)
* Added management API endpoint, service and repository for retrieval of references from the recycle bin.

* Update src/Umbraco.Cms.Api.Management/Controllers/Document/RecycleBin/ReferencedByDocumentRecycleBinController.cs

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

* Removed unused code.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-04 12:52:39 +00:00
Niels LyngsøandGitHub bf74636f26 make getHasUnpersistedChanges public (#18929) 2025-04-04 12:42:35 +00:00
Jacob Overgaard 1aff4c69f0 Merge remote-tracking branch 'origin/contrib' into v15/dev 2025-04-04 14:21:38 +02:00
Jacob Overgaard c139015019 removes autogenerated workflows 2025-04-04 14:10:45 +02:00
dependabot[bot]andJacob Overgaard 90a34e17ca Bump vite from 6.2.3 to 6.2.4 in /src/Umbraco.Web.UI.Client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 14:05:44 +02:00
Niels LyngsøandGitHub 806f10e52a V15/bugfix/fix route issue from 18859 (#18931)
* unique check

* unique for workspace empty path

* more unique routes
2025-04-04 11:20:29 +02:00
Niels LyngsøandGitHub 788e5cd678 remove unnecessary code (#18927) 2025-04-04 11:18:30 +02:00
Andy ButlandandGitHub ebd5fb4d0c Fixes save of empty, invariant block list on variant content. (#18932) 2025-04-04 10:14:24 +02:00
Andy ButlandandGitHub 5f3ce28032 Clear roots before rebuilding navigation dictionary (#18766)
* Clear roots before rebuilding navigation dictionary.

* Added tests to verify fix.

* Correct test implementation.

* Convert integration tests with method overloads into test cases.

* Integration test compatibility supressions.
2025-04-04 09:14:48 +02:00
ad7053af36 Move publish with descendants to a background task with polling (#18497)
* Use background queue for database cache rebuild and track rebuilding status.

* Updated OpenApi.json and client-side types.

* Updated client to poll for completion of database rebuild.

* Move IBackgroundTaskQueue to core and prepare publish branch to run as background task.

* Endpoints for retrieval of status and result from branch publish operations.

* Poll and retrieve result for publish with descendants.

* Handled issues from testing.

* Rework to single controller for status and result.

* Updated client side sdk.

* OpenApi post dev merge gen

---------

Co-authored-by: Migaroez <geusens@gmail.com>
2025-04-04 07:42:26 +02:00
Andy ButlandandGitHub 6247f54976 Adds ancestor ID details on document tree and collection responses (#18909)
* Populate ancestor keys on document tree response items.

* Populate ancestor keys on document collection response items.

* Update OpenApi.json

* Use array of objects rather than Ids for the ancestor collection.

* Update OpenApi.json.
2025-04-04 06:51:29 +02:00
Andy ButlandandGitHub 3e6b9313e5 Only apply validation on content update to variant cultures where the editor has permission for the culture (#18778)
* Only apply validation on content update to variant cultures where the editor has permission for the culture.

* Remove inadvertent comment updates.

* Fixed failing integration test.
2025-04-03 22:09:40 +02:00
7d41791543 Ensure has children reflects only items with folder children when folders only are queried. (#18790)
* Ensure has children reflects only items with folder children when folders only are queried.

* Added supression for change to integration test public code.

---------

Co-authored-by: Migaroez <geusens@gmail.com>
2025-04-03 21:55:34 +02:00
Niels Lyngsø 157b7d14eb Merge branch 'v15/dev' into contrib 2025-04-03 21:32:22 +02:00
Lee KelleherandGitHub f3658bf356 Tiptap RTE: Style Menu extension kind (#18918)
* Adds 'styleMenu' Tiptap toolbar extension kind

* Adds icons for `<h4>` and `<p>` tags

* Adds commands to HTML Global Attributes extension

for setting the `class` and `id` attributes.

* Renamed "default-tiptap-toolbar-element.api.ts" file

The "element" part was confusing.

* Toolbar Menu: uses correct `item` value

* Cascading Menu: adds localization for the label

* Adds `label` attribute to UUI components

for accessibility.

* Toolbar Menu: uses correct `appearance` value

* Removed unrequired `api` from Style Select

* Destructs the `item.data` object
2025-04-03 11:44:57 +02:00
Andy ButlandandGitHub e4b3104399 Remove admin permission on user configuration, allowing users with user section access only to manaage users and groups. (#18848) 2025-04-03 10:51:45 +02:00
Jacob Overgaard 9db62a2c0c ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
2025-04-03 09:07:16 +02:00
Jacob Overgaard 3cd6fcfe67 ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
2025-04-03 09:03:45 +02:00
Jacob Overgaard 3b736d8c73 ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
2025-04-03 09:01:41 +02:00
bbfe40d733 Fix issue preventing blueprint derived values from being scaffolded (#18917)
* Fix issue preventing blueprint derived values from being scaffolded.

* fix manipulating frooen array

* compare with variantId as well

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-04-03 06:45:14 +02:00
Kenn JacobsenandGitHub 9d30d5b11c Fix pagination for users restricted by start nodes (#18907)
* Fix pagination for users restricted by start nodes

* Default implementation to avoid breakage

* Review comments

* Fix failing test

* Add media start node tests
2025-04-02 09:34:52 +02:00
Niels LyngsøandGitHub 11c19847cf Feature: highlight invariant doc with variant blocks is unsupported (#18806)
* mark variant blocks in invariant docs as invalid

* implement RTE Blocks
2025-04-02 11:25:34 +07:00
8e0912cbf1 Only prevent the unpublish or delete of a related item when configured to do so if it is related as a child, not as a parent (#18886)
* Only prevent the unpubkish or delete of a related item when configured to do so if it is related as a child, not as a parent.

* Fixed incorect parameter names.

* Fixed failing integration tests.

* Use using variable instead to reduce nesting

* Applied suggestions from code review.

* Used simple using statement throughout RelationService for consistency.

* Applied XML header comments consistently.

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-04-01 15:49:49 +02:00
Jacob OvergaardandGitHub bf89eae07f V15: Block package should not import from itself (#18902)
* fix: avoids circular dependencies by realising the 'block' package was importing from itself

* chore: lowers requirement to 9 after fixes

* chore: removes unused file
2025-04-01 12:02:35 +00:00
Jacob OvergaardandGitHub 19e873c7ad V15: File upload should use better Image & SVG preview (#18900)
* feat: uses `<uui-card-media />` to preview svg's

* fix: svg preview should support potentially very large images

* fix: adds alt attribute

* fix: adds `<uui-card-media />` for image previews as well

* fix: ensures all previews have at least a "title" attribute
2025-04-01 11:13:41 +00:00
Jacob OvergaardandGitHub d4acd53fd5 fix: fixes an issue where the serverUrl was shown twice and changes the component to use <uui-card-media /> instead (#18899) 2025-04-01 11:36:27 +01:00
Sebastiaan Janssen 85fc189ee8 Merge branch 'v15/dev' into contrib 2025-04-01 12:27:28 +02:00
Jeavon LeopoldandSebastiaan Janssen a48a70595a Check for NRT directory when populating LuceneIndexFolder 2025-04-01 11:57:40 +02:00
Sebastiaan Janssen 0f539088fa Amend PR 18741 2025-04-01 11:52:32 +02:00
Warren BuckleyandGitHub 80233a7850 Add in a VSCode .code-worskpace file (#18741) 2025-04-01 11:50:55 +02:00
Markus JohanssonandGitHub 1dd10eabd4 Use StringComparison.Ordinal in hot paths (#18893) 2025-04-01 11:39:22 +02:00
Warren BuckleyandGitHub e7a52de097 Updates telemetry dashboard UI (#18743)
* Update temeltry dashboard UI

* Uses headline prop/attr to set the header
* Uses a normal h3 rather than a h2 with H3 uui css class
* Updates the UUI slider to not show the value of 0,1,2 as not that useful to see when changing the slider

* Updates translation as it had a weirdly places br mid sentance
2025-04-01 11:29:57 +02:00
Jacob Overgaard 40a2d75282 Merge branch 'release/15.3.1' into v15/dev 2025-04-01 10:34:15 +02:00
Sebastiaan Janssen 7fb9df9b19 Merge branch 'v15/dev' into contrib 2025-04-01 10:15:54 +02:00
Andy ButlandandJacob Overgaard 1720692d3d Ensures date comparisons in schedule integration tests are made only on the datetime part to the second (#18894)
* Ensures date comparisons in schedule integration tests are made only on the date part.

* Include time part to the second.

* Ensure Kind is retained when truncating a date.

* Retain Kind for all truncation levels.
2025-04-01 09:45:35 +02:00
Andy ButlandandGitHub b798925ddb Adds webp to mime type map to support preview on upload file property editor. (#18863) 2025-04-01 09:18:31 +02:00
dependabot[bot]andJacob Overgaard f4caabaa98 Bump vite from 6.2.3 to 6.2.4 in /src/Umbraco.Web.UI.Login
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 09:17:56 +02:00
Andy ButlandandGitHub b1dbd80a07 Adds allow-same-origin to preview IFRAME. (#18895) 2025-04-01 09:07:08 +02:00
Andy ButlandandGitHub 882a0427e4 Ensures date comparisons in schedule integration tests are made only on the datetime part to the second (#18894)
* Ensures date comparisons in schedule integration tests are made only on the date part.

* Include time part to the second.

* Ensure Kind is retained when truncating a date.

* Retain Kind for all truncation levels.
2025-04-01 09:06:24 +02:00
d978a10b61 Fix: #18707 (#18859)
* observe condition changes

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.context.ts

* enable extension to be updated by late appended conditions

* update route when routes change

* remove commented code

* Update src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts

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

* Revert "Update src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts"

This reverts commit b8d1dd793d.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-03-31 20:41:36 +02:00
Niels LyngsøandGitHub 7200b7b623 Bugfix: #18473 (#18765)
* add more icons

* add group id for inspection

* make outline style to make it not look disabled

* ensure that inherited has entries

* ensure current route is updated accordingly

* data marks

* shared across cultures tag

* fix sidebar group headline size
2025-03-31 14:18:47 +00:00
Andy ButlandandGitHub f200da48d8 Revert rather than prevent updates to sensitive properties on members without sensitive data access (#18794)
* Revert rather than prevent updates to sensitive properties on members without sensitive data access.

* Added suppression for integration test updates.
2025-03-31 15:16:41 +02:00
Jacob Overgaard fc815db80b bump version to 15.3.1 2025-03-31 14:48:54 +02:00
c5a3778ce7 Fix issue text overflow when user name is too long (#18587)
* Fix issue text overflow when user name is too long

* add ellipsis at the end of the text

* Bumped version of test helper

* Fixed document type design tab tests due to test helper changes

* Amends based on Niels' feedback

+ other code formatting tweaks.

* chore: remove unknown attribute on uui-tag

* fix: adds fallback text and uses `<umb-localize />` where applicable

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Nhu Dinh <hnd@umbraco.dk>
Co-authored-by: leekelleher <leekelleher@gmail.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2025-03-31 12:41:32 +00:00
Jacob Overgaard 8c1dfe5431 chore: fix lint errors 2025-03-31 14:07:32 +02:00
Jacob Overgaard 4cd43b264d fix: adds localization for "Unnamed" 2025-03-31 14:06:45 +02:00
Niels LyngsøandGitHub 3e835cd949 set circular dependency limit to 9 (#18786) 2025-03-31 13:55:26 +02:00
88b1b9de13 V15: bump @umbraco-ui/uui from 1.12.2 to 1.13.0
Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2025-03-31 13:50:23 +02:00
98e0615338 V15: Revert "Fix: RTE markup props not up to date issue" (#18879)
* Revert "simplifying the use of props (#18430)"

This reverts commit 347e898190.

* do not set value if identical check

cherry-picked from c03a8afab5

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2025-03-31 13:13:00 +02:00
Nhu DinhandGitHub e18cdab185 V15 QA Added acceptance tests for inline editing mode (#18874)
* Added tests for block list with inline editing mode

* Added tests to create a document type witha block with inline editing mode

* Added tests for block grid inline editing mode

* Bumped version

* Changed npm commands

* Reverted npm command
2025-03-31 09:33:17 +00:00
26907f202f hotfix: context provider should not destroy instance (#18864)
* do not destroy instance

* Update src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-03-31 10:51:45 +02:00
Jacob OvergaardandGitHub 13d95f31f2 test: removes two tests that fails because of a non-fixated runtime (#18876)
i.e. the tests depend on a specific time interval, but the test runner does not guarantee that, and the tests are not strictly necessary anyway
2025-03-31 08:14:50 +00:00
Niels LyngsøandGitHub c422c82f21 await setup method (#18831) 2025-03-31 10:09:10 +02:00
Niels LyngsøandGitHub ae87f9ccd4 Implement context base for a few last contexts (#18865)
* do not destroy instance

* UmbDashboardHealthCheckElement

* correct last contexts to use context-base

* informing user that Umb.Condition.MenuAlias does not work

* parse host to Section Context
2025-03-31 10:02:25 +02:00
Niels LyngsøandGitHub 3359562e32 make the deprecation discoverable from TSC (#18856) 2025-03-31 09:56:42 +02:00
0e3c8dab8f Unpublish Document: Align UX of referenced items with trash and delete (#18860)
* Added member reference type model.

* Updated client-side types and sdk.

* Render member relations on the member info view.

* Add relation type for related member with migration.

* Extend tests for track relations to include member relations.

* Extend tests for relation repository.

* Extend tests for relation service.

* Addressed comments from Copilot review.

* Add relation notification to member deletion.

* Removed unused import.

* Updates from code review.

* make ref element globally available

* align naming

* use new reference list

* add interfaces for config

* export const

* Fixed failing integration tests.

* apply interface

* deprecate interface with wrong name

* fix import

* disable unpublish button when item or descendants are referenced

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-03-31 09:52:46 +02:00
Lan Nguyen ThuyandJacob Overgaard 4d8406bb64 fix fix select all checkbox in scheduled publishing 2025-03-31 09:24:47 +02:00
dependabot[bot]andJacob Overgaard 2bc549bbc4 Bump vite from 6.2.2 to 6.2.3 in /src/Umbraco.Web.UI.Client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.2 to 6.2.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.3/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 09:19:08 +02:00
Mads RasmussenandGitHub e8b7d9daee use uui-dialog-layout for all save + publishing dialogs (#18871) 2025-03-31 09:08:07 +02:00
Niels Lyngsø 48ac4b546e enable changing data-path on an existing context 2025-03-28 20:27:26 +01:00
Niels Lyngsø eb5c57fb94 do not destroy instance 2025-03-28 19:45:44 +01:00
468ca9ae94 Member relation tracking (#18852)
* Added member reference type model.

* Updated client-side types and sdk.

* Render member relations on the member info view.

* Add relation type for related member with migration.

* Extend tests for track relations to include member relations.

* Extend tests for relation repository.

* Extend tests for relation service.

* Addressed comments from Copilot review.

* Add relation notification to member deletion.

* Removed unused import.

* Updates from code review.

* export const

* Fixed failing integration tests.

* deprecate interface with wrong name

* fix import

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
2025-03-28 15:34:25 +00:00
Niels Lyngsø 95cd6797cc Merge branch 'contrib' into v15/dev 2025-03-28 12:57:55 +01:00
Niels LyngsøandGitHub 6f63837dbc make load more 100% wide (#18821) 2025-03-28 11:49:07 +01:00
Andy ButlandandGitHub 6dc4eca607 Fixes null reference exception triggered when configuring to hide disabled users in the backoffice (#18823)
* Fixes null reference exception triggered when configuring to hide disabled users in the backoffice.

* Updated integration test suppressions.
2025-03-28 11:24:52 +01:00
Andy Butland aca908c549 Adds MemberTwoFactorLoginService. (#18810) 2025-03-28 11:20:38 +01:00
Andy ButlandandGitHub 685a05827e Adds MemberTwoFactorLoginService. (#18810) 2025-03-28 11:16:03 +01:00
Mads RasmussenandGitHub 07080604b9 Render Property "vary by segment"-toggle (#18813)
* render toggle for vary by segment

* use property-layout for appearance

* simplify labels

* remove duplicates

* render tag for vary by segment
2025-03-28 08:49:38 +01:00
Lee KelleherandGitHub 85176d1bf6 Fixes localization culture case-sensitive check (#18849)
Fixes #18801.
2025-03-27 15:24:45 +00:00
Jacob OvergaardandGitHub 52bf6bc412 feat: disables the internal dropzone if multiple=false and an upload is in progress (#18847)
you need to clear the files/queue before trying to upload something else, unless multiple=true
2025-03-27 14:20:36 +00:00
Lee KelleherandGitHub 47e09efec6 Fixes Dropdown property-editor validation (#18845) 2025-03-27 12:39:09 +00:00
9761ef899b Populate parent key on move and copy notifications (#18837)
* Populate parent key on move and copy notifications.

* Remove forgotten fixme

---------

Co-authored-by: nikolajlauridsen <nikolajlauridsen@protonmail.ch>
2025-03-27 10:33:01 +00:00
Jacob OvergaardandGitHub 164868f32a V15: Improve the dropzone for Upload Field (#18840)
* feat: maps up the CANCELLED status

* feat: uses the new dropzone input to render the dropzone

* feat: adds support for differing server urls

* chore: avoids a breaking change by storing the temporary file
2025-03-27 10:22:03 +00:00
Jacob OvergaardandGitHub a71ebe1902 V15: Improve the dropzone for Image Cropper (#18838)
* feat: uses the umb-dropzone-input to render the dropzone

* feat: loads in the blob url rather than reading the file into memory AND appends the server url

* chore: lit 3 compat

* feat: uses the umb-dropzone-input to render the dropzone

* Revert "feat: uses the umb-dropzone-input to render the dropzone"

This reverts commit bc1a6ae7df.

* feat: creates an object url directly from the File rather than the Blob

* feat: revokes the file data url from object storage

* feat: revokes object url on disconnect
2025-03-27 10:11:37 +00:00
Nhu DinhandGitHub 94f0add4d9 V15 Added acceptance tests for data type default configuration (#18740)
* Added tests for Approved Color default configuration

* Updated tests for Approved Color configuration

* Added tests for ChecklboxList configuration

* Added tests for Data type default configuration - part 1

* Added tests for data type configuration and updated tests due to test helper changes

* Added more steps to verify the default configuration

* Added tests for the default configuration and refactoring code

* Added steps to verify the TinyMCE default configuration

* Bumped version

* Fixed tests due to test helper changes

* Make all Data Type tests run in the pipeline

* Updated assertion steps

* Fixed format

* Bumped version

* Bumped version

* Comment failing tests

* Reverted npm command
2025-03-27 03:42:13 +00:00
Niels LyngsøandGitHub 9c88326681 lint corrections (#18833) 2025-03-26 19:36:17 +01:00
95e89f8481 Avoid a hash key generation and lookup when inserting in the LockingMechanism (#18243)
* Avoid a hash key generation and lookup when inserting in the LockingMechanism

* Added comments for CollectionsMarshal.GetValueRefOrAddDefault

* Added further comments and tests.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-03-26 16:55:03 +01:00
b25bcdc436 Fix bug uploading an image via the Media Picker is no longer automatically selected (#18667)
* Fix bug uploading an image via the Media Picker is no longer automatically selected

* Update src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts

Co-authored-by: Bjarne Fyrstenborg <bjarne_fyrstenborg@hotmail.com>

* Update src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts

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

* remove submit modal

* fix: avoids overriding the dropzone manager as that does not work with inheritance

* fix: set disabling of folders correctly

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Bjarne Fyrstenborg <bjarne_fyrstenborg@hotmail.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2025-03-26 15:04:19 +00:00
028da4545e Reduce CPU time when initiating RepositoryCacheKeys (#18267)
* Avoid an unneeded lookups in the Keys dictionary when initiating key cache

* Add further comments and unit tests around updated code.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-03-26 16:01:53 +01:00
Kenn JacobsenandGitHub 37035e6e7f Clean up leftover block item data when changing element variance (#18804) 2025-03-26 11:27:23 +01:00
Nhu DinhandGitHub f8c35bb7e9 V15 QA Added acceptance tests for user group default configuration (#18800)
* Updated userGroup tests due to test helper changes

* Added tests for user group default configuration

* Bumped version

* Fixed due to test helper changes

* Reverted npm command
2025-03-26 09:55:22 +00:00
Mads RasmussenandGitHub 056434fa27 fix preset logic (#18815) 2025-03-26 10:32:57 +01:00
Kenn JacobsenandGitHub e32eb06ddf Obsolete a few unused things. (#18814) 2025-03-26 10:24:09 +01:00
Niels LyngsøandGitHub 76dd141d86 implement validation context (#18808) 2025-03-26 10:12:25 +01:00
dependabot[bot]andJacob Overgaard 54601d42e2 Bump vite from 6.2.2 to 6.2.3 in /src/Umbraco.Web.UI.Login
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.2 to 6.2.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.3/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 09:25:04 +01:00
Andy Butland 619e025e7a Fixed issue where siblings of type at route are omitted from the result. (#18796) 2025-03-26 08:00:24 +01:00
Andy ButlandandGitHub 609e9f43e1 Fixed issue where siblings of type at route are omitted from the result. (#18796) 2025-03-26 07:37:04 +01:00
Mathias HelsengrenandGitHub ccc70fc786 Changes for the debug tag that makes it look better in dark mode (#18791) 2025-03-25 17:28:53 +01:00
Mathias HelsengrenandGitHub b5bffdfada Adjusting some of the colors for the dark theme so the ui looks a bit better when using dark theme (#18792) 2025-03-25 17:28:24 +01:00
Niels LyngsøandGitHub 0dd4443b75 Feature: validation synchronization as opt in (#18798)
* allow for this word

* getMessages

* split inherit and sync method

* sync feature

* rename sync report

* auto report + impl

* remove log

* double inheritance test

* one more test
2025-03-25 15:48:07 +01:00
Jacob OvergaardandGitHub 10f37494b6 V15: umb-dropzone extends umb-input-dropzone (#18784)
* feat: umb-dropzone should extend umb-input-dropzone

moves the umb-dropzone back into the media package and extends the umb-input-dropzone for common logic

* feat: adds a UmbDropzoneMediaManager class to handle media specifically

* chore: sort imports

* feat: adds a browse() method

* removes unused export

* use correct import

* fix: document and media type import should use other dropzone

* docs(storybook): remove old argument

* feat: adds umb-dropzone-media element and deprecates umb-dropzone element

* feat: use umb-dropzone-media instead

* adds export for dropzone

* feat: adds a slot to show an additional text above the dropzone graphics

* feat: the dropzone should fill out its host component

* feat: adds back a text to describe where to drop files

* remove unused import

* fix: removes overflow to allow full border
2025-03-25 14:09:41 +00:00
Kenn JacobsenandGitHub 2711ac07ac Only validate invariant properties when strictly necessary (#18729) 2025-03-25 12:58:01 +01:00
Lee KelleherandGitHub 80e092069b Tiptap RTE: Statusbar extension type (#18789) 2025-03-25 12:52:27 +01:00
Sebastiaan Janssen c23e2eb6f2 Merge branch 'v15/dev' into contrib 2025-03-25 10:44:16 +01:00
Mads RasmussenandJacob Overgaard 25837e66fc fix dropzone circular 2025-03-24 16:35:41 +01:00
Mads Rasmussen 3be766fe2a fix build 2025-03-24 16:04:44 +01:00
Mads RasmussenandGitHub 34649db5c9 Block Grid: Fix circular dependencies (#18782)
* Update input-rich-media.element.ts

* fix circular

* fixes circular dependencies in the validation module

* Update bind-to-validation.lit-directive.ts

* fix workspace circular

* Update error-viewer-modal.token.ts

* move contexts next to component

* fix import errors

* import local components from one file

* lint

* import in property editor file

* fix import

* remove duplicate import
2025-03-24 14:46:34 +00:00
4c679a5f4c Update input-rich-media.element.ts (#18772)
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-03-24 14:10:13 +00:00
Niels LyngsøandGitHub 0331cd154b Update 01_bug_report.yml (#18513) 2025-03-24 14:59:10 +01:00
36c66177fc Feature: Tree expansion state (#18227)
* implement tree expansion logic

* wip test example

* support complex expansion

* extend entity

* extend with model

* Update tree-item-context.interface.ts

* use expansion model to observe open state

* clean up

* fall back to tree context

* Update default-tree.context.ts

* Update default-tree.context.ts

* Update default-tree.context.ts

* clean up

* simplify model and state

* refactor to manager

* remove test data

* Update default-tree.context.ts

* rename

* add get method

* rename to collapse

* all collapse all method

* fix collapse logic

* add js docs

* add tests for expansion manager

* do not load children if the item is already open

* Update tree-item-element-base.ts

* config to expand tree root in pickers

* expand tree root for duplicate to

* Update tree-expansion-manager.test.ts

* make methods async

* use array state

* add isExpanded helper

* refactor to use isExpanded helper

* fix type issues

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2025-03-24 14:49:09 +01:00
Mads RasmussenandGitHub ad443c7b13 fix circular (#18773) 2025-03-24 14:47:04 +01:00
Mads RasmussenandGitHub 0b6fe91fd3 Validation: Fixes circular dependencies in the validation module (#18774)
* fixes circular dependencies in the validation module

* Update bind-to-validation.lit-directive.ts
2025-03-24 14:39:18 +01:00
Mads RasmussenandGitHub ee496d2730 fix workspace circular (#18775) 2025-03-24 14:32:00 +01:00
Mads RasmussenandGitHub 384b9bdb65 Update error-viewer-modal.token.ts (#18776) 2025-03-24 14:31:40 +01:00
Jacob OvergaardandGitHub 63113c4552 V15: New dropzone component available for the Backoffice (#18753)
* create a symlink between local Client .vscode snippets and global snippets for ease of use

* fix: no need to specify `Element` in the snippet as that is pulled from the filename

Because of our convention with `x.element.ts` you would have ended up with `UmbXElementElement`

* feat: adds new component `umb-input-dropzone`

* docs(storybook): more stories

* feat: construct the temporary files centrally along with an `AbortController` and use its signal

* feat: makes UmbInputDropzone form aware

* feat: introduces a change event

* chore: temporary changes before changing upload field

* feat: adds default slot

* docs: adds jsdocs

* feat: adds more properties

* feat: adds dashed styling

* feat: adds multiple support

* feat: allows to cancel file

* feat: separate **cancel** and **remove**

* fix stylibg

* move dropzone element

* move input-dropzone into dropzone package

* feat: introduces a 'dropzone' package

* import for backward compatibility

* remove ambigious export

* reexport everything from dropzone

* fix import

* cleanup test files

* use correct import paths

* test: make sure folder exists before writing to it

* adds export for modals

* adds entrypoint for dropzone package

* use the AbortController directly on the temporary file object

* uses correct icon name

* feat: adds ability to remove all files and cancel the request

* feat: adds styling for the uploader

and enables it to work in multiple mode with classes over id's

* do not let the content exceed its boundaries

* feat: formats progress with 2 decimals

* feat: formats with 0 decimals

* fix: returns cancel error

* fix: maps cancel errors back to the uploadable item

* fix: do not proceed with media items if the request was cancelled

* chore: mark exports from media <- dropzone as deprecated

* fix: use correct attribute and remove a todo with localizations

* fix: use correct attribute and remove a todo with localizations

* fix: allow to specify parent through attribute

* feat: align attribute `disableFolderUpload` between dropzone components
2025-03-24 13:17:48 +00:00
Sven GeusensandGitHub 39cad5b2ea Add variancy information to reference response model (#18645)
* Made variant info available on DocumentReferenceResponseModel

* Fix scope issue

* PR Feedback + correct scoping
2025-03-24 12:10:30 +01:00
Andy ButlandandGitHub 30633fe728 Only validate for duplicate member email address when configured to do so (#18747)
* Only validate for duplicate member email address when configured to do so.

* Lookup member after creation by user name rather than email, as only the former is guaranteed to be unique.
2025-03-24 12:02:50 +01:00
Andy ButlandandGitHub 394210a8f7 Uses correct preview cookie same site and secure settings to allow preview mode to flow between links in the preview frame (#18640)
* Uses correct preview cookie same site and secure settings to allow preview mode to flow between links in the preview frame.

* Fixed comment.
2025-03-21 15:20:08 +01:00
Niels LyngsøandGitHub 45b0e43b89 Feature: validation variant filtering + up-front validation path translation (#18561)
* implement console.warn

* variant specific validation contexts

* getByType method

* test for getByType

* corrected test

* no need for fallback here

* revert

* support path starting with array query

* draft

* scoped json path mapper

* JSDocs correction

* rename function

* umbQueryMapperForJsonPaths

* JSDocs

* Validation Path Translation manager

* Validation Property Path Translation Manager

* rename

* rename folder

* rename folder correction

* impl for server validation context

* rename

* .

* clean up

* re introduce queryConstructor

* correct mappers

* correct imports

* implement path translator

* content detail path translator

* test with extension type propertyValidationPathTranslator

* implement translator system for block editors

* validation fixes to prevent issues
2025-03-21 12:59:06 +01:00
Niels LyngsøandGitHub c21da38c32 rename icon and regenerate icons (#18754) 2025-03-21 11:36:46 +01:00
Niels LyngsøandGitHub ef751ca699 style adjustments (#18757) 2025-03-21 11:35:37 +01:00
Niels LyngsøandGitHub 52debc3010 make numbers go right side (#18759) 2025-03-21 11:34:36 +01:00
Niels LyngsøandGitHub a38a9b29a3 addd var(--umb-card-medium-min-width) (#18760) 2025-03-21 11:34:03 +01:00
Jacob Overgaard e346ac2bee build: add emptyOutDir to base vite config to ensure modules are emptied before build
this does not have any effect on the release build since it always starts from fresh - it is mostly to ensure no dev files are left behind locally
2025-03-21 11:05:57 +01:00
Andreas ZerbstandGitHub 425901636e V15 QA updated nightly pipeline tests (#18746)
* Removed tests for removed features

* Fixed failing tests

* Bumped version
2025-03-21 08:03:28 +00:00
Niels Lyngsø d9f17b36c6 Merge branch 'release/15.3' into v15/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
#	src/Umbraco.Web.UI.Client/package.json
#	src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts
#	src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts
#	version.json
2025-03-20 18:53:11 +01:00
Lee KelleherandGitHub 5fe5a16158 Tiptap RTE: Table Properties toolbar menu + modal (#18751)
* Tiptap Table Properties modal

* Attempting to reduce the cyclomatic complexity
2025-03-20 17:36:28 +01:00
131c9cda6f hotfix #18735 (#18750)
* fix multiple text string validation

* notify about messages

* cherry picked fix

* protection again unnecessary calls

* json path cherry pick + tests

* validation message change lock

* cherry pick from control lifecycle

* optimization

* propagate errors

* cherry picked sync

* query umb-input-multiple-text-string

* remove unused import

* remove optional chain expression

* use !

* outcomment the error handling

* outcomment more promise rejection error

* Fixed issue with multi URL picker.

* remove unesecary warning

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-03-20 16:22:05 +00:00
Niels LyngsøandGitHub e999d3977c change button look (#18678) 2025-03-20 12:55:29 +01:00
Sven Geusens f7854b8c95 Version bump 2025-03-20 11:08:38 +01:00
Jacob Overgaard 0f98b8342c Update branches (v16)
Removed v12, added v15 and v16
2025-03-20 10:49:02 +01:00
dependabot[bot]andJacob Overgaard b13f90046d Bump esbuild and vite in /src/Umbraco.Web.UI.Client
Bumps [esbuild](https://github.com/evanw/esbuild) to 0.25.1 and updates ancestor dependency [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). These dependencies need to be updated together.


Updates `esbuild` from 0.24.2 to 0.25.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.24.2...v0.25.1)

Updates `vite` from 6.1.0 to 6.2.2
- [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/v6.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: indirect
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-20 10:15:54 +01:00
Jacob Overgaard 304db8c28a Merge branch 'v15/dev' into contrib 2025-03-20 09:42:56 +01:00
dependabot[bot]andJacob Overgaard 723d3be905 Bump esbuild and vite in /src/Umbraco.Web.UI.Login
Bumps [esbuild](https://github.com/evanw/esbuild) to 0.25.1 and updates ancestor dependency [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). These dependencies need to be updated together.


Updates `esbuild` from 0.24.2 to 0.25.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.24.2...v0.25.1)

Updates `vite` from 6.1.0 to 6.2.2
- [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/v6.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: indirect
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-20 09:35:07 +01:00
Andy Butlandandkjac 5f37cd3d20 Make preview check for delivery API content case insensitive. (#18731) 2025-03-20 06:56:43 +01:00
Warren BuckleyandGitHub 203cc0e380 Updates dashboard to use uui-box headline property/attribute & place button in header-actions (#18736) 2025-03-19 23:29:56 +00:00
442de9a803 remove open config (#18592)
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-03-19 20:24:25 +00:00
Mads RasmussenandGitHub eea6b9b2f1 Feature: Language Item Ref (#18521)
* add entity type to interface

* use item-ref in language input

* register ref item
2025-03-19 20:51:25 +01:00
b3b1ce2dcd Bugfix: block culture permissions (#18665)
* always allow copy

* add back readonly check

* render pencil icon if the property is read only

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2025-03-19 19:42:04 +00:00
69b69a5fc1 V15: Change drag and drop icon (#18519)
* register new drag icons

* use drag icon in sort-children-of modal

* update drag icons

* only use the vertical icon style

* Update property-editor-ui-image-crops.element.ts

* rename to grib

* reset formatting for merge conflict

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-03-19 19:39:18 +00:00
Mads RasmussenandGitHub 605cda9224 Bugfix: Document granular user permission preset (#18651)
* pass fallback permissions to document granular permissions

* set as preset

* clean up
2025-03-19 19:36:12 +00:00
b65ad4e234 Fix focal point drag not working on firefox (#18674)
* fix focal point drag not working on firefox

* prevent reacting to left click

* only left click for drag

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
2025-03-19 16:15:34 +00:00
Jacob OvergaardandGitHub 4e680c88bb build(deps): bump @umbraco-ui/uui from 1.13.0-rc.1 to 1.13.0-rc.2 (#18677) 2025-03-19 15:20:09 +00:00
Jacob OvergaardandGitHub e8f991f485 V15: Adds accepted image types for image cropper (#18715)
* fix: adds accepted image types for image cropper

* chore: cleanup imports and types

* feat: adds a loader until all prerequisites have been resolved for image cropper

* feat: adds method to share temp file configuration
2025-03-19 12:36:51 +00:00
leekelleherandJacob Overgaard 66891333a8 Refactored the Tiptap Table toolbar button
to only show the table actions when the table is active,
otherwise show the Insert Table feature.
2025-03-19 13:26:04 +01:00
Lee KelleherandGitHub 044c9d2c87 Tiptap RTE: Table column/row bubble menu (#18708)
* Code formatting

* Icons for Tiptap table row/column actions

* Combined Tiptap exports

Removed extra constant

* Added Table Column Menu

* Added Table Row Menu

Made the bubble menu reusable
2025-03-19 13:25:13 +01:00
Andy ButlandandGitHub 7b0c419ace Adds detail of how many characters you are over when validating text fields for length. (#18722) 2025-03-19 12:02:40 +01:00
Jacob Overgaard 6ec1b9f1e4 build(deps-dev): bump storybook from 8.5.7 to 8.6.7 and run necessary migrations 2025-03-19 10:14:59 +01:00
Andy ButlandandGitHub 18069633b2 Used documented settings for the media seed cache. (#18723) 2025-03-19 09:47:12 +01:00
b24c29c647 V15: Extend search endpoints (#18634)
* Extend content type search endpoint

* Refactor to be able to specify trashed or not

* Simplify and extract into own method

* Fix breaking ctor

* Make non actions in controllers

* Fox up search service

* Add third ctor to avoid errors

* Update query to filter by is element

* Also implement for media

* Minor formatting and clean-up

* Re-introduce (and obsolete) the previous constructor

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-03-19 09:12:44 +01:00
Andreas ZerbstandGitHub f3f7fcc051 V15 QA added acceptance tests for block list variants (#18654)
* Moved tests

* Added variant tests

* Updated usage of helper

* Added tests

* Cleaned up

* Bumped version and added test command
2025-03-18 11:17:07 +00:00
Andreas ZerbstandGitHub b6fa93edac V15 QA added Tip Tap block variant acceptance tests (#18668)
* Moved tests

* Added variant tests for tiptap

* Updated file name

* Added test command

* Bumped package lock file

* removed npm command

* Bumped test helpers
2025-03-18 10:37:11 +00:00
Andy ButlandandGitHub b07a24ba55 Added clarifying comments to the logic for granular permissions. (#18705) 2025-03-18 11:08:30 +01:00
Andy ButlandandGitHub 42cb9b5c1e Removes one of the two duplicate ContentPublishingNotification publishings. (#18702) 2025-03-18 10:52:32 +01:00
Andreas ZerbstandGitHub 88657c1bb0 V15 QA added acceptance tests for block grid variants (#18658)
* Added block grid variant tests

* Bumped version

* Updated command to run tests on pipeline

* Removed test command

* Fixed comment
2025-03-18 09:35:28 +00:00
Andy ButlandandGitHub e91a25dcb6 Restrict valid API user client IDs to 100 characters. (#18688) 2025-03-18 06:33:24 +01:00
Lee KelleherandGitHub 81e63e7fa9 Tiptap RTE: Added data-mark attributes (#18689)
Tiptap: adds `data-mark` attributes to RTE, toolbar and toolbar items.
2025-03-18 11:22:20 +07:00
Andy ButlandandGitHub 2f9d58fc68 Fixes link for access to composition from document type property outside of group. (#18686) 2025-03-17 11:16:35 +01:00
Niels Lyngsø e9ba06929f Merge branch 'release/15.3' into v15/dev
# Conflicts:
#	version.json
2025-03-17 11:09:18 +01:00
Andy Butland a5da305093 Merge remote-tracking branch 'origin/v13/dev' into v15/dev
# Conflicts:
#	Directory.Packages.props
#	build/azure-pipelines.yml
#	src/Umbraco.Core/EmbeddedResources/Lang/da.xml
#	src/Umbraco.Core/EmbeddedResources/Lang/en.xml
#	src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
#	src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs
#	src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs
#	src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs
#	src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MediaController.cs
#	src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs
#	src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
#	src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
#	src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less
#	src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js
#	src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html
#	src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html
#	tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs
#	tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs
#	tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs
2025-03-17 09:20:40 +01:00
Andy ButlandandGitHub e1e5f527e4 Updates dependency on Examine to 3.7. (#18676) 2025-03-17 09:13:09 +01:00
Niels Lyngsø f4e0e38824 Merge branch 'contrib' into v15/dev 2025-03-14 10:21:27 +01:00
Niels Lyngsø fa09ce6214 justify tag 2025-03-14 10:20:23 +01:00
Lee KelleherandGitHub dd8fb66300 Content Picker: Filter out invalid entity types (#18660)
* Reverted `elementName` constant

+ code tidy-up

* Filter out values that don't match the configured entity-type

Fixes #18476

* Notifies the user of invalid data/configuration

with an option to remove invalid data.
2025-03-14 09:55:45 +01:00
Lee KelleherandGitHub a02db287ce Tiptap RTE: Text Indent extension + toolbar items (#18672)
* Tiptap: Text Indent extension

* Updates indent manifest icons
2025-03-13 14:52:58 +00:00
e9c97f8c9b Allow multiple URL segments per document (#18603)
* Code tidy - XML header comments, method ordering, warning resolution.

* Add extension method for retrieving all URL segments for a document.

* Cache and persist multiple URL segments per document.

* Allowed segment providers to terminate or allow additional segments.
Set up currently failing integration test for expected routes.

* Resolved cache issue to ensure passing new integration tests.

* Fixed failing integration test.

* Test class naming tidy up.

* Added resolution and persistance of a primary segment, to retain previous behaviour when a single segment is retrieved.

* Further integration tests.

* Resolved backward compatibility of interface.

* Supress amends made to integration tests.

* Aligned naming of integration tests.

* Removed unused using, added XML header comment.

* Throw on missing table in migration.

* Code clean-up.

* Fix multiple enumeration

* Used default on migrated column.

* Use 1 over true for default value.

* Remove unused logger

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-03-13 12:47:46 +00:00
Lan Nguyen ThuyandJacob Overgaard f016dbd223 Fix issue text overflow when user name is too long 2025-03-13 11:07:57 +01:00
Mathias HelsengrenandJacob Overgaard da5669d9fc Remove the hardcoded color for the text in error message 2025-03-13 11:04:30 +01:00
Jacob Overgaard 74aa3d9676 bump version to 15.4.0-rc 2025-03-13 10:46:43 +01:00
Niels LyngsøandGitHub c671668e45 remove min height and max height configuration options (#18648) 2025-03-12 21:33:37 +01:00
Niels LyngsøandGitHub e337821d0e align search icon vertical (#18657) 2025-03-12 21:23:41 +01:00
leekelleherandJacob Overgaard 95fcf645dc Awaits super.openPicker() 2025-03-12 13:35:35 +01:00
Andy Butland b2aa04ae1a Merge branch 'release/13.7.2' into v13/dev
# Conflicts:
#	version.json
2025-03-12 12:18:45 +01:00
Andy ButlandandGitHub e270adc50f Reverts UI updates from publish with desdendants dialog. (#18647) 2025-03-12 08:10:34 +01:00
Andy Butlandandkjac de282d0fbf Reverts UI updates from publish with descendants dialog (15) (#18646)
* Reverts UI updates from publish with descendants dialog.

* Removed unused variable.
2025-03-12 08:01:00 +01:00
Andy Butland fd5b8de533 Bumped version to 13.7.2 2025-03-12 07:25:35 +01:00
Andy Butland e3d9b042c2 Avoids collection was modified issue when flowing identities to the authenticated user's principal. (#18527) 2025-03-12 07:24:50 +01:00
Niels LyngsøandGitHub 3586f137fc avoid indifferences on property-entry property order (#18530) 2025-03-11 12:30:14 +01:00
PhyxionNLandGitHub ced3db8542 Add NuGet badge (#18583) 2025-03-11 10:09:35 +01:00
Andy Butland 1f2afbaa1d Bumped version to 15.3.0-rc2. 2025-03-11 09:45:50 +01:00
Andy Butland e8e1b6ef33 Merge commit from fork
* Tighten management API endpoint access rules.

* Bumped version to 15.2.1.
2025-03-11 09:42:58 +01:00
Andy Butland 4803b4303d bumped imagesharp to prevent CVE-2025-27598 (#18602)
# Conflicts:
#	Directory.Packages.props
2025-03-11 09:42:47 +01:00
Andy Butland f4ae796b30 Merge branch 'v13/dev' of https://github.com/umbraco/Umbraco-CMS into v13/dev 2025-03-11 09:18:25 +01:00
Andy Butland 367d0386b6 Merge branch 'release/13.7.1' into v13/dev
# Conflicts:
#	version.json
2025-03-11 09:18:12 +01:00
5b54bed406 Merge commit from fork
* Fixed parsing of node if in content and media permission querystring handlers to retrieve expected value when multiple are provided in the querystring.

* Add HttpPost attributes to backoffice endpoints that should only accept post requests.

* Bumped version to 13.6.1.

* Narrow PermissionQueryString parsing to the releveant UmbracoObjectType

* Add missed update from v10

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
2025-03-11 05:11:08 +01:00
Anders BjernerandMichael Latouche d3b60277d7 Fixed typo in TinyMCE's da.js 2025-03-10 15:38:50 +01:00
twoday-rolandkockandAndy Butland 2ec6ff4ebc bumped imagesharp to prevent CVE-2025-27598 (#18602) 2025-03-09 08:43:52 +01:00
Markus JohanssonandGitHub 900938edd0 V15: Added uui-box around ungrouped properties during block editing (#18571) (#18572) 2025-03-05 08:44:49 +01:00
Andy Butland b9250c20e9 Merge branch 'v15/dev' into contrib 2025-03-04 07:43:10 +01:00
Andy Butland 451b5f96e7 Use windows for build agent to avoid hanging issues with Linux. 2025-02-28 06:57:35 +01:00
Andy Butland 15ebae5025 Bumped version to 13.7.1. 2025-02-28 06:57:00 +01:00
Chris HoustonandGitHub ac3b17b390 Fixes the Fatal icon text so it's readable in Dark Mode. (#18375) 2025-02-18 22:00:19 +01:00
1241 changed files with 43308 additions and 12089 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ This guide describes each step to make your first contribution:
## Further contribution guides
- [Before you start](contributing-before-you-start.md)
- [Finding your first issue: Up for grabs](contributing-before-you-start.md)
- [Finding your first issue: Up for grabs](contributing-first-issue.md)
- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/)
- [Unwanted changes](contributing-unwanted-changes.md)
- [Other ways to contribute](contributing-other-ways-to-contribute.md)
+2 -2
View File
@@ -6,8 +6,8 @@ body:
- type: input
id: "version"
attributes:
label: "Which Umbraco version are you using? (Please write the *exact* version, example: 10.1.0)"
description: "Use the help icon in the Umbraco backoffice to find the version you're using"
label: "Which Umbraco version are you using?"
description: "Please write the *exact* version, example: `10.1.0`. Use the help icon in the Umbraco backoffice to find the version you're using"
validations:
required: true
- type: textarea
+2 -2
View File
@@ -4,11 +4,11 @@ contact_links:
url: https://github.com/umbraco/Umbraco-CMS/discussions/new?category=features-and-ideas
about: Start a new discussion when you have ideas or feature requests, eventually discussions can turn into plans
- name: ⁉️ Support Question
url: https://our.umbraco.com
url: https://forum.umbraco.com
about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the forum.
- name: 📖 Documentation Issue
url: https://github.com/umbraco/UmbracoDocs/issues
about: Documentation issues should be reported on the Umbraco documentation repository.
- name: 🔐 Security Issue
url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
url: https://umbraco.com/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
about: Discovered a Security Issue in Umbraco?
+5 -4
View File
@@ -1,11 +1,12 @@
# [Umbraco CMS](https://umbraco.com)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
![Mastodon Follow](https://img.shields.io/mastodon/follow/110661369750014952?domain=https%3A%2F%2Fumbracocommunity.social)
[![Chat about Umbraco on Discord](https://img.shields.io/discord/869656431308189746?logo=discord&logoColor=fff)](https://discord.gg/umbraco)
[![Read what's going on in the Umbraco Discord chat now](https://img.shields.io/badge/read-discord-blue)](https://discord-chats.umbraco.com)
[![NuGet Version](https://img.shields.io/nuget/v/Umbraco.Cms)](https://www.nuget.org/packages/Umbraco.Cms)
[![Build status](https://img.shields.io/azure-devops/build/umbraco/Umbraco%2520Cms/301?logo=azurepipelines&label=Azure%20Pipelines)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
[![Forum](https://img.shields.io/badge/help-forum-blue)](https://forum.umbraco.com)
[![Chat about Umbraco on Discord](https://img.shields.io/discord/869656431308189746?logo=discord&logoColor=fff)](https://discord.gg/umbraco)
![Mastodon Follow](https://img.shields.io/mastodon/follow/110661369750014952?domain=https%3A%2F%2Fumbracocommunity.social)
### Umbraco is a free and open source .NET content management system. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
+1
View File
@@ -0,0 +1 @@
../src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets
+5
View File
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"unprovide"
]
}
+3 -3
View File
@@ -45,8 +45,8 @@
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
<PackageVersion Include="Examine" Version="3.6.0" />
<PackageVersion Include="Examine.Core" Version="3.6.0" />
<PackageVersion Include="Examine" Version="3.7.1" />
<PackageVersion Include="Examine.Core" Version="3.7.1" />
<PackageVersion Include="HtmlAgilityPack" Version="1.11.74" />
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
@@ -100,4 +100,4 @@
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
</ItemGroup>
</Project>
</Project>
+579
View File
@@ -0,0 +1,579 @@
Third-Party Notices
===================
This file contains notices and attributions for third-party software used in the Umbraco CMS project.
It is not a license and does not grant any rights to use the third-party software.
Umbraco CMS is licensed under the MIT License, which can be found in the LICENSE file.
---
@openid/AppAuth-JS: An OpenID Connect and OAuth 2.0 client library for JavaScript
URL: https://github.com/openid/AppAuth-JS
License: Apache License, Version 2.0
Copyright: 2017 Google Inc.
---
AutoFixture: Write maintainable unit tests, faster
URL: https://github.com/AutoFixture/AutoFixture
License: MIT License
Copyright: 2013 Mark Seemann
---
Asp.Versioning.Mvc: A library for ASP.NET Core versioning
URL: https://github.com/dotnet/aspnet-api-versioning
License: MIT License
Copyright: .NET Foundation and contributors
---
Babel: A JavaScript compiler
URL: https://babeljs.io/
License: MIT License
Copyright: 2014-present Sebastian McKenzie and other contributors
---
BenchmarkDotNet: Powerful .NET library for benchmarking
URL: https://github.com/dotnet/BenchmarkDotNet
License: MIT License
Copyright: .NET Foundation and Contributors
---
Bogus: A simple and sane data generator for populating objects that supports different locales.
URL: https://github.com/bchavez/Bogus
License: MIT License
Copyright: 2015 Brian Chavez
---
CommandLineParser: Terse syntax C# command line parser for .NET
URL: https://github.com/commandlineparser/commandline
License: MIT License
Copyright: 2005-2015 Giacomo Stelluti Scala & Contributors
---
cross-env: A CLI tool to set environment variables across platforms
URL: https://github.com/kentcdodds/cross-env
License: MIT License
Copyright: 2017 Kent C. Dodds
---
Dazinator.Extensions.FileProviders: A library for file provider extensions
URL: https://github.com/dazinator/Dazinator.Extensions.FileProviders
License: MIT License
Copyright: 2016 Darrell
---
DOMPurify: A DOM-only XSS sanitizer for HTML, MathML and SVG
URL: https://github.com/cure53/DOMPurify
License: Apache License, Version 2.0
Copyright: 2025 Dr.-Ing. Mario Heiderich, Cure53
---
Element Internals Polyfill: A polyfill for the Element Internals API
URL: https://github.com/calebdwilliams/element-internals-polyfill
License: MIT License
Copyright: 2021 Caleb Williams
---
Eslint: A tool for identifying and reporting on patterns in JavaScript
URL: https://eslint.org/
License: MIT License
Copyright: OpenJS Foundation and other contributors
---
Examine: A search and indexing library for .NET
URL: https://github.com/Shazwazza/Examine
License: Microsoft Public License (Ms-PL)
Copyright: 2023 Shannon Deminick
---
Glob: A library for matching file paths using glob patterns
URL: https://github.com/isaacs/node-glob
License: ISC License
Copyright: 2009-2023 Isaac Z. Schlueter and Contributors
---
Globals: A library for managing global variables in JavaScript
URL: https://github.com/sindresorhus/globals
License: MIT License
Copyright: Sindre Sorhus
---
Html Agility Pack: An HTML parser for .NET
URL: https://html-agility-pack.net/
License: MIT License
Copyright: ZZZ Projects Inc.
---
ImageSharp: A cross-platform library for processing images in .NET
URL: https://github.com/SixLabors/ImageSharp
License: Apache License, Version 2.0 under the Six Labors Split License
Copyright: Six Labors
---
jsdiff: A JavaScript text differencing implementation
URL: https://github.com/kpdecker/jsdiff
License: BSD 3-Clause License
Copyright: 2009-2015 Kevin Decker <kpdecker@gmail.com>
---
JsonPatch.Net: A library for JSON Patch (RFC 6902) in .NET
URL: https://github.com/json-everything/json-everything
License: MIT License
Copyright: .NET Foundation and Contributors
---
K4os.Compression.LZ4: A fast LZ4 compression library for .NET
URL: https://github.com/MiloszKrajewski/K4os.Compression.LZ4
License: MIT License
Copyright: 2017 Milosz Krajewski
---
Lit: A simple library for building fast, lightweight web components
URL: https://lit.dev
License: BSD 3-Clause License
Copyright: 2020 Google LLC. All rights reserved.
---
Lucide: Beautiful & consistent icons for the web
URL: https://lucide.dev/
License: ISC License
Copyright: 2013-2022 Cole Bemis
Copyright: 2022 Lucide Contributors
---
Madge: A dependency graph generator for JavaScript
URL: https://github.com/pahen/madge
License: MIT License
Copyright: 2017 Patrik Henningsson
---
MailKit: A library for sending email in .NET
URL: https://github.com/jstedfast/MailKit
License: MIT License
Copyright: 2013-2024 .NET Foundation and Contributors
---
Markdown: A library for parsing and compiling Markdown
URL: https://github.com/hey-red/Markdown
License: MIT License
Copyright: 2018 red
---
marked: A markdown parser and compiler
URL: https://marked.js.org/
License: MIT License
Copyright: 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
Copyright: 2018+, MarkedJS (https://github.com/markedjs/)
---
Message Pack: The extremely fast MessagePack serializer for C#
URL: https://github.com/MessagePack-CSharp/MessagePack-CSharp
License: MIT License
Copyright: 2017 Yoshifumi Kawai and contributors
---
Miniprofiler: A mini profiler for .NET
URL: https://github.com/MiniProfiler/dotnet
License: MIT License
Copyright: .NET MiniProfiler Contributors
---
Monaco Editor: A browser-based code editor
URL: https://microsoft.github.io/monaco-editor/
License: MIT License
Copyright: 2016-present Microsoft Corporation
---
Moq: A mocking library for .NET
URL: https://github.com/moq/moq
License: BSD 3-Clause License
Copyright: 2007 Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors.
---
Mock Service Worker (MSW): A library for mocking API requests in JavaScript
URL: https://mswjs.io/
License: MIT License
Copyright: 2018present Artem Zakharchenko
---
NCrontab: A cron schedule parser for .NET
URL: https://github.com/atifaziz/NCrontab
License: Apache License, Version 2.0
Copyright: 2001 The OpenSymphony Group
Copyright: 2008 Atif Aziz
---
Nerdbank.GitVersioning: A library for versioning .NET projects
URL: https://github.com/dotnet/Nerdbank.GitVersioning
License: MIT License
Copyright: .NET Foundation and Contributors
---
NJsonSchema: A JSON schema validator for .NET
URL: https://github.com/RicoSuter/NJsonSchema
License: MIT License
Copyright: 2022 Rico Suter
---
NPoco: A micro ORM for .NET
URL: https://github.com/schotime/NPoco
License: Apache License, Version 2.0
Copyright: Schotime
---
NUnit: A unit testing framework for .NET
URL: https://github.com/nunit/nunit
License: MIT License
Copyright: Charlie Poole, Rob Prouse and Contributors
---
Open Web Components: A set of standards and libraries for building web components
URL: https://open-wc.org/
License: MIT License
Copyright: 2018 open-wc
---
Openapi-ts: The OpenAPI to TypeScript codegen
URL: https://github.com/hey-api/openapi-ts
License: MIT License
Copyright: Hey API
---
OpenIddict: A simple and flexible OpenID Connect server for ASP.NET Core
URL: https://github.com/openiddict/openiddict-core
License: Apache License, Version 2.0
Copyright: Kévin Chalet
---
Playwright: A Node.js library to automate browser testing
URL: https://playwright.dev/
License: Apache License, Version 2.0
Copyright: 2025 Microsoft Corporation
---
Playwright-msw: A library to wrap Mock Service Worker with Playwright
URL: https://github.com/valendres/playwright-msw
License: MIT License
Copyright: 2022 Peter Weller
---
Prettier: An opinionated code formatter
URL: https://prettier.io/
License: MIT License
Copyright: James Long and contributors
---
Remark-gfm: A GitHub Flavored Markdown plugin for Remark
URL: https://github.com/remarkjs/remark-gfm
License: MIT License
Copyright: Titus Wormer
---
Rollup: A module bundler for JavaScript
URL: https://rollupjs.org/
License: MIT License
Copyright: 2015-present Rollup contributors
---
Rollup Plugins: A collection of Rollup plugins
URL: https://github.com/rollup/plugins
License: MIT License
Copyright: 2019-present Rollup Plugins contributors
---
Rollup-plugin-esbuild: A Rollup plugin for using esbuild
URL: https://github.com/egoist/rollup-plugin-esbuild
License: MIT License
Copyright: 2020 EGOIST
---
Rollup-plugin-import-css: A Rollup plugin for importing CSS files
URL: https://github.com/jleeson/rollup-plugin-import-css
License: MIT License
Copyright: 2020 Jacob Leeson
---
rxjs: Reactive Extensions for JavaScript
URL: https://rxjs.dev/
License: Apache License, Version 2.0
Copyright: 2015-present Ben Lesh <ben@benlesh.com>, Google, Inc., Netflix, Inc., Microsoft Corp., and contributors
---
Serilog: A diagnostic logging library for .NET
URL: https://github.com/serilog/serilog
License: Apache License, Version 2.0
Copyright: Serilog Contributors
---
Simple Icons: A set of SVG icons for popular brands
URL: https://simpleicons.org/
License: CC0 1.0 Universal License
Copyright: Simple Icons Contributors
---
Storybook: A UI component explorer for Web Components
URL: https://storybook.js.org/
License: MIT License
Copyright: 2024 Storybook
---
StyleCop.Analyzers: Analyzers for StyleCop
URL: https://github.com/DotNetAnalyzers/StyleCopAnalyzers
License: MIT License
Copyright: Tunnel Vision Laboratories, LLC
---
SVGO: A tool for optimizing SVG files
URL: https://svgo.dev/
License: MIT License
Copyright: Kir Belevich
---
Swashbuckle.AspNetCore: A library for generating Swagger documentation for ASP.NET Core APIs
URL: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
License: MIT License
Copyright: 2016 Richard Morris
---
Tiny Glob: A tiny globbing library for Node.js
URL: https://github.com/terkelg/tiny-glob
License: MIT License
Copyright: 2018 Terkel
---
TinyMCE, version 6.x: A rich text editor for the web
URL: https://www.tiny.cloud/
License: MIT License
Copyright: 2022 Ephox Corporation DBA Tiny Technologies, Inc.
---
Tiptap: A renderless rich-text editor for the web
URL: https://tiptap.dev/
License: MIT License
Copyright: 2025 Tiptap GmbH
---
Tsc-alias: A TypeScript compiler plugin for aliasing module paths
URL: https://github.com/justkey007/tsc-alias
License: MIT License
Copyright: 2018 Justkey
---
Typedoc: A documentation generator for TypeScript projects
URL: https://typedoc.org/
License: Apache License, Version 2.0
Copyright: Gerrit Birkeland and Contributors
---
Typescript: A typed superset of JavaScript that compiles to plain JavaScript
URL: https://www.typescriptlang.org/
License: Apache License, Version 2.0
Copyright: 2012-present Microsoft Corporation
---
Typescript-eslint: A set of tools for linting TypeScript code
URL: https://github.com/typescript-eslint/typescript-eslint
License: MIT License
Copyright: 2019 typescript-eslint and other contributors
---
Typescript-json-schema: A library for generating JSON schema from TypeScript types
URL: https://github.com/YousefED/typescript-json-schema
License: BSD 3-Clause License
Copyright: 2016 typescript-json-schema contributors
---
Umbraco.Code: Provides code-level tools for Umbraco
URL: https://github.com/umbraco/Umbraco-Code
License: MIT License
Copyright: 2005-present Umbraco A/S
---
Umbraco.GitVersioning.Extensions: Utilities for Nerdbank.GitVersioning
URL: https://github.com/umbraco/Umbraco.GitVersioning.Extensions
License: MIT License
Copyright: 2005-present Umbraco A/S
---
Umbraco.JsonSchema.Extensions: Utilities for JSON schema generation
URL: https://github.com/umbraco/Umbraco.JsonSchema.Extensions
License: MIT License
Copyright: 2005-present Umbraco A/S
---
Umbraco UI Library: A set of UI components for building web applications
URL: https://uui.umbraco.com/
License: MIT License
Copyright: 2005-present Umbraco A/S
---
uuid: A library for generating unique identifiers
URL: https://github.com/uuidjs/uuid
License: MIT License
Copyright: 2010-2020 Robert Kieffer and other contributors
---
Vite: A fast build tool and development server for modern web projects
URL: https://vite.dev/
License: MIT License
Copyright: 2019-present VoidZero Inc. and Vite contributors
---
Vite-plugin-static-copy: A Vite plugin for copying static files
URL: https://github.com/sapphi-red/vite-plugin-static-copy
License: MIT License
Copyright: 2021 sapphi-red
---
Vite-tsconfig-paths: A Vite plugin for resolving TypeScript paths
URL: https://github.com/aleclarson/vite-tsconfig-paths
License: MIT License
Copyright: Alec Larson
---
Web Component Analyzer: A tool for analyzing web components
URL: https://github.com/runem/web-component-analyzer
License: MIT License
Copyright: 2019 Rune Mehlsen
+10
View File
@@ -0,0 +1,10 @@
{
"folders": [
{
"path": "src/Umbraco.Web.UI.Client"
},
{
"path": "src/Umbraco.Web.UI.Login"
}
]
}
+105 -254
View File
@@ -313,8 +313,8 @@ stages:
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
# 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)"
# 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)"
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 +325,8 @@ stages:
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
macOSPart3Of3:
vmImage: "macOS-latest"
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
# 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)"
pool:
vmImage: $(vmImage)
variables:
@@ -389,8 +389,8 @@ stages:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# 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)"
# 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)"
LinuxPart1Of3:
vmImage: "ubuntu-latest"
SA_PASSWORD: UmbracoIntegration123!
@@ -410,8 +410,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 namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
# 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)"
pool:
vmImage: $(vmImage)
steps:
@@ -432,6 +432,33 @@ stages:
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- powershell: |
$maxAttempts = 12
$attempt = 0
$status = ""
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
Start-Sleep -Seconds 5
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
$status = docker inspect -f '{{.State.Status}}' mssql
if ($status -ne 'running') {
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
$attempt++
}
}
if ($status -eq 'running') {
Write-Host "SQL Server container is running"
docker ps -a
} else {
Write-Host "SQL Server did not become ready in time. Last known status: $status"
docker logs mssql
exit 1
}
displayName: Wait for SQL Server to be ready (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
@@ -495,151 +522,70 @@ stages:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
DatabaseType: SQLite
strategy:
matrix:
LinuxPart1Of3:
vmImage: "ubuntu-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
LinuxPart2Of3:
vmImage: "ubuntu-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
WindowsPart1Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.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: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Run application
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
DatabaseType: ${{ variables.DatabaseType }}
buildConfiguration: ${{ variables.buildConfiguration }}
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install chromium
displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish test artifacts
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
- job:
displayName: E2E Tests (SQL Server)
@@ -647,175 +593,76 @@ stages:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
DatabaseType: SQLServer
strategy:
matrix:
${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }}:
LinuxPart1Of3:
testCommand: "npm run smokeTest -- --shard=1/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
testFolder: "DefaultConfig"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart2Of3:
testCommand: "npm run smokeTest -- --shard=2/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
testFolder: "DefaultConfig"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart3Of3:
testCommand: "npm run smokeTest -- --shard=3/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
testFolder: "DefaultConfig"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
WindowsPart1Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTest -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTest -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTest -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
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
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.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: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Start SQL Server
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
buildConfiguration: ${{ variables.buildConfiguration }}
DatabaseType: ${{ variables.DatabaseType }}
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Run application
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install chromium
displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: eq(variables['Agent.OS'], 'Linux')
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: eq(variables['Agent.OS'], 'Windows_NT')
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish test artifacts
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
###############################################
## Release
@@ -829,6 +676,8 @@ stages:
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
jobs:
- job:
pool:
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
displayName: Push to pre-release feed
steps:
- checkout: none
@@ -890,6 +739,8 @@ stages:
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}}))
jobs:
- job:
pool:
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
displayName: Push to NuGet
steps:
- checkout: none
+75
View File
@@ -0,0 +1,75 @@
parameters:
- name: testFolder
type: string
default: ''
- name: buildConfiguration
type: string
default: ''
- name: additionalEnvironmentVariables
type: boolean
default: False
steps:
- pwsh: |
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
# Update application to use necessary app settings
- pwsh: |
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
$destinationPath = "UmbracoProject"
$jsonFiles = Get-ChildItem -Path $sourcePath -Filter "*.json"
if ($jsonFiles) {
$jsonFiles | ForEach-Object {
Write-Host "Copying: $($_.FullName)"
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
}
} else {
Write-Host "No JSON files found."
}
displayName: Update application to use necessary app settings
workingDirectory: $(Agent.BuildDirectory)/app
# Update application to use necessary App_Plugins
- pwsh: |
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
$destinationPath = "UmbracoProject"
$appPluginsFolders = Get-ChildItem -Path $sourcePath -Directory -Filter "App_Plugins"
if ($appPluginsFolders) {
foreach ($folder in $appPluginsFolders) {
Write-Host "Copying folder: $($folder.FullName)"
Copy-Item -Path $folder.FullName -Destination $destinationPath -Recurse -Force
}
} else {
Write-Host "No App_Plugins found."
}
displayName: Update application to use necessary app plugins
workingDirectory: $(Agent.BuildDirectory)/app
# Update application to use necessary classes
- pwsh: |
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
$destinationPath = "UmbracoProject"
$csharpFiles = Get-ChildItem -Path $sourcePath -Filter "*.cs"
if ($csharpFiles) {
$csharpFiles | ForEach-Object {
Write-Host "Copying: $($_.FullName)"
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
}
} else {
Write-Host "No C# files found."
}
displayName: Update application to use necessary classes
workingDirectory: $(Agent.BuildDirectory)/app
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
- pwsh: |
dotnet build UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
@@ -0,0 +1,47 @@
parameters:
- name: SA_PASSWORD
type: string
default: ''
- name: buildConfiguration
type: string
default: ''
- name: additionalEnvironmentVariables
type: boolean
default: False
- name: DatabaseType
type: string
default: ''
steps:
# Skips the SQLServer setup if the databaseType does not match
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
# Start SQL Server Linux
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=${{ parameters.SA_PASSWORD }}" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
# Start SQL Server LocalDB Windows
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# If we want to add additional environment variables to the run step, then we will skip these
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
# Run application for Linux
- bash: |
nohup dotnet run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
# Run application for Windows
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
+105
View File
@@ -0,0 +1,105 @@
parameters:
- name: ASPNETCORE_URLS
type: string
default: ''
- name: testCommand
type: string
default: ''
- name: port
type: string
default: ''
- name: AZUREB2CTESTUSEREMAIL
type: string
default: ''
- name: AZUREB2CTESTUSERPASSWORD
type: string
default: ''
- name: DatabaseType
type: string
default: ''
steps:
# Ensures we have the package wait-on installed
- pwsh: npm install wait-on
displayName: Install wait-on package
# Wait for either the port of the aspnetcore url
- pwsh: |
$Port = "${{ parameters.port }}"
$Url = "${{ parameters.ASPNETCORE_URLS }}"
if ($Port -ne "") {
Write-Host "Waiting on TCP port $Port"
npx wait-on -v --interval 1000 --timeout 120000 "tcp:$Port"
} else {
Write-Host "Waiting on URL $Url"
npx wait-on -v --interval 1000 --timeout 120000 "$Url"
}
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install chromium
displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: ${{ parameters.testCommand }}
displayName: Run Playwright tests
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
AZUREADB2CTESTUSEREMAIL: ${{ parameters.AZUREB2CTESTUSEREMAIL }}
AZUREADB2CTESTUSERPASSWORD: ${{ parameters.AZUREB2CTESTUSERPASSWORD }}
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
+69
View File
@@ -0,0 +1,69 @@
parameters:
- name: nodeVersion
type: string
default: ''
- name: PlaywrightUserEmail
type: string
default: ''
- name: PlaywrightPassword
type: string
default: ''
- name: ASPNETCORE_URLS
type: string
default: ''
- name: npm_config_cache
type: string
default: ''
steps:
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
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" | 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
# Install Template
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
displayName: Install Template
workingDirectory: $(Agent.BuildDirectory)/app
+471 -282
View File
@@ -3,13 +3,29 @@ name: Nightly_E2E_Test_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName
pr: none
trigger: none
schedules:
- cron: '0 0 * * *'
displayName: Daily midnight build
branches:
include:
- v14/dev
- v15/dev
# schedules:
# - cron: '0 0 * * *'
# displayName: Daily midnight build
# branches:
# include:
# - v14/dev
# - v15/dev
parameters:
- name: differentAppSettingsAcceptanceTests
displayName: Run acceptance tests with different app settings
type: boolean
default: false
- name: skipDefaultConfigAcceptanceTests
displayName: Skip tests with DefaultConfig
type: boolean
default: false
- name: skipIntegrationTests
displayName: Skip integration tests
type: boolean
default: false
variables:
nodeVersion: 20
@@ -32,25 +48,17 @@ stages:
- job: A
displayName: Build Umbraco CMS
pool:
vmImage: 'ubuntu-latest'
vmImage: "windows-latest"
steps:
- checkout: self
fetchDepth: 0
submodules: true
submodules: false
lfs: false,
fetchDepth: 500
- template: templates/backoffice-install.yml
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
- template: templates/backoffice-install.yml
- script: npm run build:for:cms
displayName: Run build (Bellissima)
workingDirectory: src/Umbraco.Web.UI.Client
- script: npm ci --no-fund --no-audit --prefer-offline
displayName: Run npm ci (Login)
workingDirectory: src/Umbraco.Web.UI.Login
- script: npm run build
displayName: Run npm build (Login)
workingDirectory: src/Umbraco.Web.UI.Login
- task: DotNetCoreCLI@2
displayName: Run dotnet restore
inputs:
@@ -62,7 +70,7 @@ stages:
inputs:
command: build
projects: $(solution)
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
- task: PublishPipelineArtifact@1
displayName: Publish nupkg
inputs:
@@ -74,8 +82,237 @@ stages:
targetPath: $(Build.SourcesDirectory)
artifactName: build_output
- stage: E2E
displayName: E2E Tests
- job: B
displayName: Build Bellissima Package
pool:
vmImage: "ubuntu-latest"
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 500
- template: templates/backoffice-install.yml
- script: npm run build:for:npm
displayName: Run build:for:npm
workingDirectory: src/Umbraco.Web.UI.Client
- bash: |
echo "##[command]Running npm pack"
echo "##[debug]Output directory: $(Build.ArtifactStagingDirectory)"
mkdir $(Build.ArtifactStagingDirectory)/npm
npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm
mv .npmrc $(Build.ArtifactStagingDirectory)/npm/
displayName: Run npm pack
workingDirectory: src/Umbraco.Web.UI.Client
- task: PublishPipelineArtifact@1
displayName: Publish Bellissima npm artifact
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/npm
artifactName: npm
- stage: Integration
displayName: Integration Tests
dependsOn: Build
condition: ${{ eq(parameters.skipIntegrationTests, false) }}
jobs:
# Integration Tests (SQLite)
- job:
timeoutInMinutes: 180
displayName: Integration Tests (SQLite)
strategy:
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:
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:
vmImage: "ubuntu-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of4:
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:
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:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
macOSPart3Of4:
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)"
pool:
vmImage: $(vmImage)
variables:
Tests__Database__DatabaseType: "Sqlite"
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 1
fetchFilter: tree:0
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
inputs:
artifact: build_output
path: $(Build.SourcesDirectory)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Test
- task: DotNetCoreCLI@2
displayName: Run dotnet test
inputs:
command: test
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
testRunTitle: Integration Tests SQLite - $(Agent.OS)
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
# Integration Tests (SQL Server)
- job:
timeoutInMinutes: 180
displayName: Integration Tests (SQL Server)
variables:
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:
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:
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:
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:
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:
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:
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)"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
inputs:
artifact: build_output
path: $(Build.SourcesDirectory)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Start SQL Server
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- powershell: |
$maxAttempts = 12
$attempt = 0
$status = ""
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
Start-Sleep -Seconds 5
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
$status = docker inspect -f '{{.State.Status}}' mssql
if ($status -ne 'running') {
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
$attempt++
}
}
if ($status -eq 'running') {
Write-Host "SQL Server container is running"
docker ps -a
} else {
Write-Host "SQL Server did not become ready in time. Last known status: $status"
docker logs mssql
exit 1
}
displayName: Wait for SQL Server to be ready (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Test
- task: DotNetCoreCLI@2
displayName: Run dotnet test
inputs:
command: test
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
- stage: DefaultConfigE2E
displayName: Default Config E2E Tests
dependsOn: Build
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
@@ -103,338 +340,290 @@ stages:
- job:
displayName: E2E Tests (SQLite)
timeoutInMinutes: 180
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
variables:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
DatabaseType: SQLite
strategy:
matrix:
LinuxPart1Of3:
vmImage: "ubuntu-latest"
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=1/3"
LinuxPart2Of3:
vmImage: "ubuntu-latest"
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=2/3"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=3/3"
WindowsPart1Of3:
vmImage: "windows-latest"
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.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: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Run application
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
DatabaseType: ${{ variables.DatabaseType }}
buildConfiguration: ${{ variables.buildConfiguration }}
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
# Ensures we have the package wait-on installed
- pwsh: npm install wait-on
displayName: Install wait-on package
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install --with-deps
displayName: Install Playwright
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
- job:
displayName: E2E Tests (SQL Server)
timeoutInMinutes: 180
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
variables:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
DatabaseType: SQLServer
SA_PASSWORD: UmbracoAcceptance123!
strategy:
matrix:
LinuxPart1Of3:
testCommand: "npm run testSqlite -- --shard=1/3"
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart2Of3:
testCommand: "npm run testSqlite -- --shard=2/3"
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart3Of3:
testCommand: "npm run testSqlite -- --shard=3/3"
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
WindowsPart1Of3:
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
vmImage: "windows-latest"
testCommand: "npm run testSqlite -- --shard=1/3"
WindowsPart2Of3:
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
vmImage: "windows-latest"
testCommand: "npm run testSqlite -- --shard=2/3"
WindowsPart3Of3:
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
vmImage: "windows-latest"
testCommand: "npm run testSqlite -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
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
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.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: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Start SQL Server
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
buildConfiguration: ${{ variables.buildConfiguration }}
DatabaseType: ${{ variables.DatabaseType }}
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
# Run application
- stage: AdditionalConfigE2E
displayName: Additional Config E2E Tests
dependsOn: Build
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
ASPNETCORE_URLS: https://localhost:44331
PlaywrightPassword: UmbracoAcceptance123!
PlaywrightUserEmail: playwright@umbraco.com
jobs:
- job:
displayName: E2E Tests with Different App settings (SQL Server)
condition: ${{ eq(parameters.differentAppSettingsAcceptanceTests, true) }}
timeoutInMinutes: 180
variables:
SA_PASSWORD: UmbracoAcceptance123!
DatabaseType: SQLServer
strategy:
matrix:
# UnattendedInstallConfig
WindowsUnattendedInstallConfig:
vmImage: "windows-latest"
testFolder: "UnattendedInstallConfig"
testCommand: "npx playwright test --project=unattendedInstallConfig --grep=InstallSQLServer"
port: 44331
additionalEnvironmentVariables: false
# DeliveryApiConfig
WindowsDeliveryApiConfig:
vmImage: "windows-latest"
testFolder: "DeliveryApi"
port: ''
testCommand: "npx playwright test --project=deliveryApi"
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: false
LinuxDeliveryApiConfig:
vmImage: "ubuntu-latest"
testFolder: "DeliveryApi"
port: ''
testCommand: "npx playwright test --project=deliveryApi"
CONNECTIONSTRINGS__UMBRACODBDSN: Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: false
# ExternalLogin AzureADB2C
WindowsExternalLoginAzureADB2C:
vmImage: "windows-latest"
testFolder: "ExternalLogin\\AzureADB2C"
testCommand: "npx playwright test --project=externalLoginAzureADB2C"
port: 44331
packageName: "Microsoft.AspNetCore.Authentication.OpenIdConnect"
packageVersion: "9.0.8"
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: true
pool:
vmImage: $(vmImage)
steps:
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.PlaywrightUserEmail }}
PlaywrightPassword: ${{ variables.PlaywrightPassword }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
# Install NuGet package if specified in the matrix
- pwsh: |
Write-Host "Installing package $(packageName) version $(packageVersion)"
dotnet add package $(packageName) --version $(packageVersion)
displayName: "Install NuGet package: $(packageName)"
workingDirectory: $(Agent.BuildDirectory)/app/UmbracoProject
condition: and(succeeded(), ne(variables['packageName'], ''), ne(variables['packageVersion'], ''))
# Build application Template
- template: nightly-E2E-build-template.yml
parameters:
testFolder: $(testFolder)
buildConfiguration: ${{ variables.buildConfiguration }}
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true) }}
# Build application for AzureADB2C
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application for AzureADB2C
workingDirectory: $(Agent.BuildDirectory)/app
env:
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
AZUREADB2CTENANT: $(AZUREB2CTENANT)
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
condition: and(succeeded(), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true ) }}
buildConfiguration: ${{ variables.buildConfiguration }}
DatabaseType: ${{ variables.DatabaseType }}
# Run application for Linux with additional Environment Variables for Azure AD
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
nohup dotnet run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
workingDirectory: $(Agent.BuildDirectory)/app
env:
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
AZUREADB2CTENANT: $(AZUREB2CTENANT)
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
# Run application for Windows with additional Environment Variables for Azure AD
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
workingDirectory: $(Agent.BuildDirectory)/app
# Ensures we have the package wait-on installed
- pwsh: npm install wait-on
displayName: Install wait-on package
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install --with-deps
displayName: Install Playwright
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
AZUREADB2CTENANT: $(AZUREB2CTENANT)
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
port: $(port)
AZUREB2CTESTUSEREMAIL: $(AZUREB2CTESTUSEREMAIL)
AZUREB2CTESTUSERPASSWORD: $(AZUREB2CTESTUSERPASSWORD)
DatabaseType: ${{ variables.DatabaseType }}
+2 -1
View File
@@ -9,9 +9,10 @@ schedules:
branches:
include:
- v10/dev
- v12/dev
- v13/dev
- v14/dev
- v15/dev
- v16/dev
steps:
- checkout: none
@@ -18,7 +18,12 @@ internal sealed class DeliveryApiOutputCachePolicy : IOutputCachePolicy
.RequestServices
.GetRequiredService<IRequestPreviewService>();
context.EnableOutputCaching = requestPreviewService.IsPreview() is false;
IApiAccessService apiAccessService = context
.HttpContext
.RequestServices
.GetRequiredService<IApiAccessService>();
context.EnableOutputCaching = requestPreviewService.IsPreview() is false && apiAccessService.HasPublicAccess();
context.ResponseExpirationTimeSpan = _duration;
return ValueTask.CompletedTask;
@@ -48,7 +48,7 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
Id = AuthSchemeName,
}
},
new string[] { }
[]
}
}
};
@@ -185,7 +185,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
}
private Attempt<PagedModel<Guid>, ApiMediaQueryOperationStatus> PagedResult(IEnumerable<IPublishedContent> children, int skip, int take)
private static Attempt<PagedModel<Guid>, ApiMediaQueryOperationStatus> PagedResult(IEnumerable<IPublishedContent> children, int skip, int take)
{
IPublishedContent[] childrenAsArray = children as IPublishedContent[] ?? children.ToArray();
var result = new PagedModel<Guid>
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Services;
@@ -11,5 +11,5 @@ internal sealed class RequestPreviewService : RequestHeaderHandler, IRequestPrev
}
/// <inheritdoc />
public bool IsPreview() => GetHeaderValue("Preview") == "true";
public bool IsPreview() => string.Equals(GetHeaderValue("Preview"), "true", StringComparison.OrdinalIgnoreCase);
}
@@ -0,0 +1,46 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.References;
[ApiVersion("1.0")]
public class ReferencedByDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
public ReferencedByDataTypeController(IDataTypeService dataTypeService, IRelationTypePresentationFactory relationTypePresentationFactory)
{
_dataTypeService = dataTypeService;
_relationTypePresentationFactory = relationTypePresentationFactory;
}
/// <summary>
/// Gets a paged list of references for the current data type, so you can see where it is being used.
/// </summary>
[HttpGet("{id:guid}/referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _dataTypeService.GetPagedRelationsAsync(id, skip, take);
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
{
Total = relationItems.Total,
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
return pagedViewModel;
}
}
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
@@ -10,6 +10,7 @@ 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;
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.Content;
@@ -140,6 +140,10 @@ public abstract class DocumentControllerBase : ContentControllerBase
.WithDetail(
"An unspecified error occurred while (un)publishing. Please check the logs for additional information.")
.Build()),
ContentPublishingOperationStatus.TaskResultNotFound => NotFound(problemDetailsBuilder
.WithTitle("The result of the submitted task could not be found")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown content operation status."),
});
@@ -30,14 +30,33 @@ public class SearchDocumentItemController : DocumentItemControllerBase
[NonAction]
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
=> await SearchWithTrashed(cancellationToken, query, null, skip, take, parentId);
[NonAction]
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> SearchFromParentWithAllowedTypes(
CancellationToken cancellationToken,
string query,
int skip = 0,
int take = 100,
Guid? parentId = null,
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null) =>
await SearchWithTrashed(cancellationToken, query, null, skip, take, parentId, allowedDocumentTypes);
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedDocumentTypes = null)
public async Task<IActionResult> SearchWithTrashed(
CancellationToken cancellationToken,
string query,
bool? trashed = null,
int skip = 0,
int take = 100,
Guid? parentId = null,
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null)
{
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, skip, take);
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, trashed, skip, take);
var result = new PagedModel<DocumentItemResponseModel>
{
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),
@@ -35,7 +35,7 @@ public class PublishDocumentWithDescendantsController : DocumentControllerBase
[HttpPut("{id:guid}/publish-with-descendants")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(PublishWithDescendantsResultModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> PublishWithDescendants(CancellationToken cancellationToken, Guid id, PublishDocumentWithDescendantsRequestModel requestModel)
@@ -54,10 +54,15 @@ public class PublishDocumentWithDescendantsController : DocumentControllerBase
id,
requestModel.Cultures,
BuildPublishBranchFilter(requestModel),
CurrentUserKey(_backOfficeSecurityAccessor));
CurrentUserKey(_backOfficeSecurityAccessor),
true);
return attempt.Success
? Ok()
return attempt.Success && attempt.Result.AcceptedTaskId.HasValue
? Ok(new PublishWithDescendantsResultModel
{
TaskId = attempt.Result.AcceptedTaskId.Value,
IsComplete = false
})
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
}
@@ -0,0 +1,69 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models.ContentPublishing;
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;
[ApiVersion("1.0")]
public class PublishDocumentWithDescendantsResultController : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IContentPublishingService _contentPublishingService;
public PublishDocumentWithDescendantsResultController(
IAuthorizationService authorizationService,
IContentPublishingService contentPublishingService)
{
_authorizationService = authorizationService;
_contentPublishingService = contentPublishingService;
}
[HttpGet("{id:guid}/publish-with-descendants/result/{taskId:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PublishWithDescendantsResultModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> PublishWithDescendantsResult(CancellationToken cancellationToken, Guid id, Guid taskId)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.Branch(ActionPublish.ActionLetter, id),
AuthorizationPolicies.ContentPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
// Check if the publishing task has completed, if not, return the status.
var isPublishing = await _contentPublishingService.IsPublishingBranchAsync(taskId);
if (isPublishing)
{
return Ok(new PublishWithDescendantsResultModel
{
TaskId = taskId,
IsComplete = false
});
};
// If completed, get the result and return the status.
Attempt<ContentPublishingBranchResult, ContentPublishingOperationStatus> attempt = await _contentPublishingService.GetPublishBranchResultAsync(taskId);
return attempt.Success
? Ok(new PublishWithDescendantsResultModel
{
TaskId = taskId,
IsComplete = true
})
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
}
}
@@ -0,0 +1,50 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Document.RecycleBin;
[ApiVersion("1.0")]
public class ReferencedByDocumentRecycleBinController : DocumentRecycleBinControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
public ReferencedByDocumentRecycleBinController(
IEntityService entityService,
IDocumentPresentationFactory documentPresentationFactory,
ITrackedReferencesService trackedReferencesService,
IRelationTypePresentationFactory relationTypePresentationFactory)
: base(entityService, documentPresentationFactory)
{
_trackedReferencesService = trackedReferencesService;
_relationTypePresentationFactory = relationTypePresentationFactory;
}
/// <summary>
/// Gets a paged list of tracked references for all items in the document recycle bin, so you can see where an item is being used.
/// </summary>
[HttpGet("referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
CancellationToken cancellationToken,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _trackedReferencesService.GetPagedRelationsForRecycleBinAsync(UmbracoObjectTypes.Document, skip, take, true);
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
{
Total = relationItems.Total,
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
return pagedViewModel;
}
}
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -45,6 +45,6 @@ public class AreReferencedDocumentController : DocumentControllerBase
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
};
return await Task.FromResult(pagedViewModel);
return pagedViewModel;
}
}
@@ -45,6 +45,6 @@ public class ReferencedByDocumentController : DocumentControllerBase
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
return await Task.FromResult(pagedViewModel);
return pagedViewModel;
}
}
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -45,6 +45,6 @@ public class ReferencedDescendantsDocumentController : DocumentControllerBase
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
};
return await Task.FromResult(pagedViewModel);
return pagedViewModel;
}
}
@@ -4,6 +4,7 @@ using Umbraco.Cms.Api.Management.Controllers.Tree;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
@@ -33,7 +34,7 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
AppCaches appCaches,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, userStartNodeEntitiesService, dataTypeService)
: base(entityService, userStartNodeEntitiesService, dataTypeService)
{
_publicAccessService = publicAccessService;
_appCaches = appCaches;
@@ -52,6 +53,8 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
if (entity is IDocumentEntitySlim documentEntitySlim)
{
responseModel.IsProtected = _publicAccessService.IsProtected(entity.Path);
responseModel.Ancestors = EntityService.GetPathKeys(entity, omitSelf: true)
.Select(x => new ReferenceByIdModel(x));
responseModel.IsTrashed = entity.Trashed;
responseModel.Id = entity.Key;
responseModel.CreateDate = entity.CreateDate;
@@ -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.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -16,15 +19,32 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
{
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
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,
IContentEditingService contentEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_documentEditingPresentationFactory = documentEditingPresentationFactory;
_contentEditingService = contentEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPost("validate")]
@@ -36,7 +56,10 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
=> await HandleRequest(requestModel, async () =>
{
ContentCreateModel model = _documentEditingPresentationFactory.MapCreateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateCreateAsync(model);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
await _contentEditingService.ValidateCreateAsync(
model,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
@@ -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.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -17,15 +20,32 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
{
private readonly IContentEditingService _contentEditingService;
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,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_contentEditingService = contentEditingService;
_documentEditingPresentationFactory = documentEditingPresentationFactory;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPut("{id:guid}/validate")]
@@ -62,7 +82,11 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
=> await HandleRequest(id, requestModel, async () =>
{
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
await _contentEditingService.ValidateUpdateAsync(
id,
model,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
@@ -0,0 +1,37 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DocumentBlueprint;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
public class ScaffoldDocumentBlueprintController : DocumentBlueprintControllerBase
{
private readonly IContentBlueprintEditingService _contentBlueprintEditingService;
private readonly IUmbracoMapper _umbracoMapper;
public ScaffoldDocumentBlueprintController(IContentBlueprintEditingService contentBlueprintEditingService, IUmbracoMapper umbracoMapper)
{
_contentBlueprintEditingService = contentBlueprintEditingService;
_umbracoMapper = umbracoMapper;
}
[HttpGet("{id:guid}/scaffold")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(DocumentBlueprintResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Scaffold(CancellationToken cancellationToken, Guid id)
{
IContent? blueprint = await _contentBlueprintEditingService.GetScaffoldedAsync(id);
return blueprint is not null
? Ok(_umbracoMapper.Map<DocumentBlueprintResponseModel>(blueprint))
: DocumentBlueprintNotFound();
}
}
@@ -1,45 +1,60 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType.Item;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Item;
[ApiVersion("1.0")]
public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase
{
private readonly IEntitySearchService _entitySearchService;
private readonly IContentTypeService _contentTypeService;
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>())
{
_entitySearchService = entitySearchService;
_contentTypeService = contentTypeService;
_mapper = mapper;
}
[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)]
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
public async Task<IActionResult> SearchDocumentType(CancellationToken cancellationToken, string query, bool? isElement = null, int skip = 0, int take = 100)
{
PagedModel<IEntitySlim> searchResult = _entitySearchService.Search(UmbracoObjectTypes.DocumentType, query, skip, take);
if (searchResult.Items.Any() is false)
{
return await Task.FromResult(Ok(new PagedModel<DocumentTypeItemResponseModel> { Total = searchResult.Total }));
}
IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
PagedModel<IContentType> contentTypes = await _contentTypeSearchService.SearchAsync(query, isElement, cancellationToken, skip, take);
var result = new PagedModel<DocumentTypeItemResponseModel>
{
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes),
Total = searchResult.Total
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes.Items),
Total = contentTypes.Total
};
return Ok(result);
@@ -32,12 +32,17 @@ public class SearchMediaItemController : MediaItemControllerBase
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
[NonAction]
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, null, skip, take, parentId);
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, bool? trashed = null, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
{
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, skip, take);
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, trashed, skip, take);
var result = new PagedModel<MediaItemResponseModel>
{
Items = searchResult.Items.OfType<IMediaEntitySlim>().Select(_mediaPresentationFactory.CreateItemResponseModel),
@@ -0,0 +1,50 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Media.RecycleBin;
[ApiVersion("1.0")]
public class ReferencedByMediaRecycleBinController : MediaRecycleBinControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
public ReferencedByMediaRecycleBinController(
IEntityService entityService,
IMediaPresentationFactory mediaPresentationFactory,
ITrackedReferencesService trackedReferencesService,
IRelationTypePresentationFactory relationTypePresentationFactory)
: base(entityService, mediaPresentationFactory)
{
_trackedReferencesService = trackedReferencesService;
_relationTypePresentationFactory = relationTypePresentationFactory;
}
/// <summary>
/// Gets a paged list of tracked references for all items in the media recycle bin, so you can see where an item is being used.
/// </summary>
[HttpGet("referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
CancellationToken cancellationToken,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _trackedReferencesService.GetPagedRelationsForRecycleBinAsync(UmbracoObjectTypes.Media, skip, take, true);
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
{
Total = relationItems.Total,
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
return pagedViewModel;
}
}
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -46,6 +46,6 @@ public class AreReferencedMediaController : MediaControllerBase
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
};
return await Task.FromResult(pagedViewModel);
return pagedViewModel;
}
}
@@ -45,6 +45,6 @@ public class ReferencedByMediaController : MediaControllerBase
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
return await Task.FromResult(pagedViewModel);
return pagedViewModel;
}
}
@@ -1,4 +1,4 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -45,6 +45,6 @@ public class ReferencedDescendantsMediaController : MediaControllerBase
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
};
return await Task.FromResult(pagedViewModel);
return pagedViewModel;
}
}
@@ -0,0 +1,51 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
[ApiVersion("1.0")]
public class AreReferencedMemberController : MemberControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesSkipTakeService;
private readonly IUmbracoMapper _umbracoMapper;
public AreReferencedMemberController(ITrackedReferencesService trackedReferencesSkipTakeService, IUmbracoMapper umbracoMapper)
{
_trackedReferencesSkipTakeService = trackedReferencesSkipTakeService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a page list of the items used in any kind of relation from selected keys.
/// </summary>
/// <remarks>
/// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view).
/// This is basically finding children of relations.
/// </remarks>
// [HttpGet("item")]
[HttpGet("are-referenced")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ReferenceByIdModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> GetPagedReferencedItems(
CancellationToken cancellationToken,
[FromQuery(Name="id")] HashSet<Guid> ids,
int skip = 0,
int take = 20)
{
PagedModel<Guid> distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Member, skip, take);
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = distinctByKeyItemsWithReferencedRelations.Total,
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
};
return pagedViewModel;
}
}
@@ -0,0 +1,50 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
[ApiVersion("1.0")]
public class ReferencedByMemberController : MemberControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
public ReferencedByMemberController(ITrackedReferencesService trackedReferencesService, IRelationTypePresentationFactory relationTypePresentationFactory)
{
_trackedReferencesService = trackedReferencesService;
_relationTypePresentationFactory = relationTypePresentationFactory;
}
/// <summary>
/// Gets a page list of tracked references for the current item, so you can see where an item is being used.
/// </summary>
/// <remarks>
/// Used by info tabs on content, media etc. and for the delete and unpublish of single items.
/// This is basically finding parents of relations.
/// </remarks>
[HttpGet("{id:guid}/referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _trackedReferencesService.GetPagedRelationsForItemAsync(id, skip, take, true);
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
{
Total = relationItems.Total,
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
return pagedViewModel;
}
}
@@ -0,0 +1,50 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
[ApiVersion("1.0")]
public class ReferencedDescendantsMemberController : MemberControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesSkipTakeService;
private readonly IUmbracoMapper _umbracoMapper;
public ReferencedDescendantsMemberController(ITrackedReferencesService trackedReferencesSkipTakeService, IUmbracoMapper umbracoMapper)
{
_trackedReferencesSkipTakeService = trackedReferencesSkipTakeService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a page list of the child nodes of the current item used in any kind of relation.
/// </summary>
/// <remarks>
/// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any
/// kind of relation.
/// This is basically finding the descending items which are children in relations.
/// </remarks>
[HttpGet("{id:guid}/referenced-descendants")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ReferenceByIdModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> ReferencedDescendants(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(id, skip, take, true);
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = relationItems.Total,
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
};
return pagedViewModel;
}
}
@@ -21,7 +21,7 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase
{
var problemDetails = new ProblemDetails
{
Title = "Database cache can not be rebuilt",
Title = "Database cache cannot be rebuilt",
Detail = $"The database cache is in the process of rebuilding.",
Status = StatusCodes.Status400BadRequest,
Type = "Error",
@@ -21,6 +21,10 @@ public abstract class SecurityControllerBase : ManagementApiControllerBase
.WithTitle("The password reset token was invalid")
.WithDetail("The specified password reset token was either used already or wrong.")
.Build()),
UserOperationStatus.CancelledByNotification => BadRequest(problemDetailsBuilder
.WithTitle("Cancelled by notification")
.WithDetail("A notification handler prevented the user operation.")
.Build()),
UserOperationStatus.UnknownFailure => BadRequest(problemDetailsBuilder
.WithTitle("Unknown failure")
.WithDetail(errorMessageResult?.Error?.ErrorMessage ?? "The error was unknown")
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Management.Routing;
@@ -17,6 +17,9 @@ public abstract class TemporaryFileControllerBase : ManagementApiControllerBase
.WithTitle("File extension not allowed")
.WithDetail("The file extension is not allowed.")
.Build()),
TemporaryFileOperationStatus.InvalidFileName => BadRequest(problemDetailsBuilder
.WithTitle("The provided file name is not valid")
.Build()),
TemporaryFileOperationStatus.KeyAlreadyUsed => BadRequest(problemDetailsBuilder
.WithTitle("Key already used")
.WithDetail("The specified key is already used.")
@@ -42,11 +42,21 @@ public abstract class UserStartNodeTreeControllerBase<TItem> : EntityTreeControl
protected override IEntitySlim[] GetPagedChildEntities(Guid parentKey, int skip, int take, out long totalItems)
{
IEntitySlim[] children = base.GetPagedChildEntities(parentKey, skip, take, out totalItems);
return UserHasRootAccess() || IgnoreUserStartNodes()
? children
// Keeping the correct totalItems amount from GetPagedChildEntities
: CalculateAccessMap(() => _userStartNodeEntitiesService.ChildUserAccessEntities(children, UserStartNodePaths), out _);
if (UserHasRootAccess() || IgnoreUserStartNodes())
{
return base.GetPagedChildEntities(parentKey, skip, take, out totalItems);
}
IEnumerable<UserAccessEntity> userAccessEntities = _userStartNodeEntitiesService.ChildUserAccessEntities(
ItemObjectType,
UserStartNodePaths,
parentKey,
skip,
take,
ItemOrdering,
out totalItems);
return CalculateAccessMap(() => userAccessEntities, out _);
}
protected override TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Security.OperationStatus;
@@ -20,7 +20,7 @@ public abstract class ClientCredentialsUserControllerBase : UserControllerBase
.Build()),
BackOfficeUserClientCredentialsOperationStatus.InvalidClientId => BadRequest(problemDetailsBuilder
.WithTitle("Invalid client ID")
.WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 255 characters.")
.WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 100 characters.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
.WithTitle("Unknown client credentials operation status.")
@@ -1,15 +1,12 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.User;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.User;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.RequireAdminAccess)]
public class ConfigurationUserController : UserControllerBase
{
private readonly IUserPresentationFactory _userPresentationFactory;
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
@@ -5,11 +6,13 @@ using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.Webhook.Logs;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Webhook.Logs;
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Webhook}")]
[ApiExplorerSettings(GroupName = "Webhook")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
public class WebhookLogControllerBase : ManagementApiControllerBase
{
protected PagedViewModel<WebhookLogResponseModel> CreatePagedWebhookLogResponseModel(PagedModel<WebhookLog> logs, IWebhookPresentationFactory webhookPresentationFactory)
@@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.Document.Collection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
@@ -9,11 +12,23 @@ namespace Umbraco.Cms.Api.Management.Factories;
public class DocumentCollectionPresentationFactory : ContentCollectionPresentationFactory<IContent, DocumentCollectionResponseModel, DocumentValueResponseModel, DocumentVariantResponseModel>, IDocumentCollectionPresentationFactory
{
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)
: base(mapper)
: this(
mapper,
publicAccessService,
StaticServiceProvider.Instance.GetRequiredService<IEntityService>())
{
}
[ActivatorUtilitiesConstructor]
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService, IEntityService entityService)
: base(mapper)
{
_publicAccessService = publicAccessService;
_entityService = entityService;
}
protected override Task SetUnmappedProperties(ListViewPagedModel<IContent> contentCollection, List<DocumentCollectionResponseModel> collectionResponseModels)
@@ -27,6 +42,8 @@ public class DocumentCollectionPresentationFactory : ContentCollectionPresentati
}
item.IsProtected = _publicAccessService.IsProtected(matchingContentItem).Success;
item.Ancestors = _entityService.GetPathKeys(matchingContentItem, omitSelf: true)
.Select(x => new ReferenceByIdModel(x));
}
return Task.CompletedTask;
@@ -26,7 +26,7 @@ internal sealed class DocumentVersionPresentationFactory : IDocumentVersionPrese
new ReferenceByIdModel(_entityService.GetKey(contentVersion.ContentTypeId, UmbracoObjectTypes.DocumentType)
.Result),
new ReferenceByIdModel(await _userIdKeyResolver.GetAsync(contentVersion.UserId)),
new DateTimeOffset(contentVersion.VersionDate, TimeSpan.Zero), // todo align with datetime offset rework
new DateTimeOffset(contentVersion.VersionDate),
contentVersion.CurrentPublishedVersion,
contentVersion.CurrentDraftVersion,
contentVersion.PreventCleanup);
@@ -1,4 +1,4 @@
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.RedirectUrlManagement;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Routing;
@@ -22,6 +22,12 @@ public class RedirectUrlPresentationFactory : IRedirectUrlPresentationFactory
var originalUrl = _publishedUrlProvider.GetUrlFromRoute(source.ContentId, source.Url, source.Culture);
// Even if the URL could not be extracted from the route, if we have a path as a the route for the original URL, we should display it.
if (originalUrl == "#" && source.Url.StartsWith('/'))
{
originalUrl = source.Url;
}
return new RedirectUrlResponseModel
{
OriginalUrl = originalUrl,
@@ -1,7 +1,12 @@
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Factories;
@@ -9,21 +14,73 @@ namespace Umbraco.Cms.Api.Management.Factories;
public class RelationTypePresentationFactory : IRelationTypePresentationFactory
{
private readonly IUmbracoMapper _umbracoMapper;
private readonly IEntityRepository _entityRepository;
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>())
{
_umbracoMapper = umbracoMapper;
}
public async Task<IEnumerable<IReferenceResponseModel>> CreateReferenceResponseModelsAsync(IEnumerable<RelationItemModel> relationItemModels)
public RelationTypePresentationFactory(
IUmbracoMapper umbracoMapper,
IEntityRepository entityRepository,
IDocumentPresentationFactory documentPresentationFactory,
IScopeProvider scopeProvider)
{
IReferenceResponseModel[] result = relationItemModels.Select(relationItemModel => relationItemModel.NodeType switch
{
Constants.UdiEntityType.Document => _umbracoMapper.Map<DocumentReferenceResponseModel>(relationItemModel),
Constants.UdiEntityType.Media => _umbracoMapper.Map<MediaReferenceResponseModel>(relationItemModel),
_ => _umbracoMapper.Map<DefaultReferenceResponseModel>(relationItemModel) as IReferenceResponseModel,
}).WhereNotNull().ToArray();
_umbracoMapper = umbracoMapper;
_entityRepository = entityRepository;
_documentPresentationFactory = documentPresentationFactory;
_scopeProvider = scopeProvider;
}
public async Task<IEnumerable<IReferenceResponseModel>> CreateReferenceResponseModelsAsync(
IEnumerable<RelationItemModel> relationItemModels)
{
IReadOnlyCollection<RelationItemModel> relationItemModelsCollection = relationItemModels.ToArray();
Guid[] documentKeys = relationItemModelsCollection
.Where(item => item.NodeType is Constants.UdiEntityType.Document)
.Select(item => item.NodeKey).Distinct().ToArray();
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
var slimEntities = _entityRepository.GetAll(Constants.ObjectTypes.Document, documentKeys).ToList();
IReferenceResponseModel[] result = relationItemModelsCollection.Select(relationItemModel =>
relationItemModel.NodeType switch
{
Constants.ReferenceType.Document => MapDocumentReference(relationItemModel, slimEntities),
Constants.ReferenceType.Media => _umbracoMapper.Map<MediaReferenceResponseModel>(relationItemModel),
Constants.ReferenceType.Member => _umbracoMapper.Map<MemberReferenceResponseModel>(relationItemModel),
Constants.ReferenceType.DocumentTypePropertyType => _umbracoMapper.Map<DocumentTypePropertyTypeReferenceResponseModel>(relationItemModel),
Constants.ReferenceType.MediaTypePropertyType => _umbracoMapper.Map<MediaTypePropertyTypeReferenceResponseModel>(relationItemModel),
Constants.ReferenceType.MemberTypePropertyType => _umbracoMapper.Map<MemberTypePropertyTypeReferenceResponseModel>(relationItemModel),
_ => _umbracoMapper.Map<DefaultReferenceResponseModel>(relationItemModel),
}).WhereNotNull().ToArray();
return await Task.FromResult(result);
}
private IReferenceResponseModel? MapDocumentReference(RelationItemModel relationItemModel,
List<IEntitySlim> slimEntities)
{
DocumentReferenceResponseModel? documentReferenceResponseModel =
_umbracoMapper.Map<DocumentReferenceResponseModel>(relationItemModel);
if (documentReferenceResponseModel is not null
&& slimEntities.FirstOrDefault(e => e.Key == relationItemModel.NodeKey) is DocumentEntitySlim
matchingSlimDocument)
{
documentReferenceResponseModel.Variants =
_documentPresentationFactory.CreateVariantsItemResponseModels(matchingSlimDocument);
}
return documentReferenceResponseModel;
}
}
@@ -1,13 +1,17 @@
using Umbraco.Cms.Api.Management.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.User;
using Umbraco.Cms.Api.Management.ViewModels.User.Current;
using Umbraco.Cms.Core;
using Umbraco.Cms.Api.Management.ViewModels.User.Item;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Mail;
using Umbraco.Cms.Core.Media;
@@ -20,7 +24,6 @@ namespace Umbraco.Cms.Api.Management.Factories;
public class UserPresentationFactory : IUserPresentationFactory
{
private readonly IEntityService _entityService;
private readonly AppCaches _appCaches;
private readonly MediaFileManager _mediaFileManager;
@@ -31,7 +34,10 @@ public class UserPresentationFactory : IUserPresentationFactory
private readonly IPasswordConfigurationPresentationFactory _passwordConfigurationPresentationFactory;
private readonly IBackOfficeExternalLoginProviders _externalLoginProviders;
private readonly SecuritySettings _securitySettings;
private readonly IUserService _userService;
private readonly IContentService _contentService;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public UserPresentationFactory(
IEntityService entityService,
AppCaches appCaches,
@@ -43,6 +49,35 @@ public class UserPresentationFactory : IUserPresentationFactory
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>())
{
}
public UserPresentationFactory(
IEntityService entityService,
AppCaches appCaches,
MediaFileManager mediaFileManager,
IImageUrlGenerator imageUrlGenerator,
IUserGroupPresentationFactory userGroupPresentationFactory,
IAbsoluteUrlBuilder absoluteUrlBuilder,
IEmailSender emailSender,
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
IOptionsSnapshot<SecuritySettings> securitySettings,
IBackOfficeExternalLoginProviders externalLoginProviders,
IUserService userService,
IContentService contentService)
{
_entityService = entityService;
_appCaches = appCaches;
@@ -54,6 +89,8 @@ public class UserPresentationFactory : IUserPresentationFactory
_externalLoginProviders = externalLoginProviders;
_securitySettings = securitySettings.Value;
_absoluteUrlBuilder = absoluteUrlBuilder;
_userService = userService;
_contentService = contentService;
}
public UserResponseModel CreateResponseModel(IUser user)
@@ -195,7 +232,7 @@ public class UserPresentationFactory : IUserPresentationFactory
var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches);
var documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document);
var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet();
var permissions = GetAggregatedGranularPermissions(user, presentationGroups);
var fallbackPermissions = presentationGroups.SelectMany(x => x.FallbackPermissions).ToHashSet();
var hasAccessToAllLanguages = presentationGroups.Any(x => x.HasAccessToAllLanguages);
@@ -225,6 +262,42 @@ public class UserPresentationFactory : IUserPresentationFactory
});
}
private HashSet<IPermissionPresentationModel> GetAggregatedGranularPermissions(IUser user, IEnumerable<UserGroupResponseModel> presentationGroups)
{
var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet();
// The raw permission data consists of several permissions for each document. We want to aggregate this server-side so
// we return one set of aggregate permissions per document that the client will use.
// Get the unique document keys that have granular permissions.
IEnumerable<Guid> documentKeysWithGranularPermissions = permissions
.Where(x => x is DocumentPermissionPresentationModel)
.Cast<DocumentPermissionPresentationModel>()
.Select(x => x.Document.Id)
.Distinct();
var aggregatedPermissions = new HashSet<IPermissionPresentationModel>();
foreach (Guid documentKey in documentKeysWithGranularPermissions)
{
// Retrieve the path of the document.
var path = _contentService.GetById(documentKey)?.Path;
if (string.IsNullOrEmpty(path))
{
continue;
}
// With the path we can call the same logic as used server-side for authorizing access to resources.
EntityPermissionSet permissionsForPath = _userService.GetPermissionsForPath(user, path);
aggregatedPermissions.Add(new DocumentPermissionPresentationModel
{
Document = new ReferenceByIdModel(documentKey),
Verbs = permissionsForPath.GetAllPermissions()
});
}
return aggregatedPermissions;
}
public async Task<CalculatedUserStartNodesResponseModel> CreateCalculatedUserStartNodesResponseModelAsync(IUser user)
{
var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches);
@@ -67,7 +67,7 @@ public class DocumentMapDefinition : ContentMapDefinition<IContent, DocumentValu
target.IsTrashed = source.Trashed;
}
// Umbraco.Code.MapAll -IsProtected
// Umbraco.Code.MapAll -IsProtected -Ancestors
private void Map(IContent source, DocumentCollectionResponseModel target, MapperContext context)
{
target.Id = source.Key;
@@ -1,4 +1,4 @@
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
@@ -11,12 +11,16 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
{
mapper.Define<RelationItemModel, DocumentReferenceResponseModel>((source, context) => new DocumentReferenceResponseModel(), Map);
mapper.Define<RelationItemModel, MediaReferenceResponseModel>((source, context) => new MediaReferenceResponseModel(), Map);
mapper.Define<RelationItemModel, MemberReferenceResponseModel>((source, context) => new MemberReferenceResponseModel(), Map);
mapper.Define<RelationItemModel, DocumentTypePropertyTypeReferenceResponseModel>((source, context) => new DocumentTypePropertyTypeReferenceResponseModel(), Map);
mapper.Define<RelationItemModel, MediaTypePropertyTypeReferenceResponseModel>((source, context) => new MediaTypePropertyTypeReferenceResponseModel(), Map);
mapper.Define<RelationItemModel, MemberTypePropertyTypeReferenceResponseModel>((source, context) => new MemberTypePropertyTypeReferenceResponseModel(), Map);
mapper.Define<RelationItemModel, DefaultReferenceResponseModel>((source, context) => new DefaultReferenceResponseModel(), Map);
mapper.Define<RelationItemModel, ReferenceByIdModel>((source, context) => new ReferenceByIdModel(), Map);
mapper.Define<Guid, ReferenceByIdModel>((source, context) => new ReferenceByIdModel(), Map);
}
// Umbraco.Code.MapAll
// Umbraco.Code.MapAll -Variants
private void Map(RelationItemModel source, DocumentReferenceResponseModel target, MapperContext context)
{
target.Id = source.NodeKey;
@@ -24,6 +28,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
target.Published = source.NodePublished;
target.DocumentType = new TrackedReferenceDocumentType
{
Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
@@ -37,6 +42,66 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
target.Name = source.NodeName;
target.MediaType = new TrackedReferenceMediaType
{
Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
};
}
// Umbraco.Code.MapAll
private void Map(RelationItemModel source, MemberReferenceResponseModel target, MapperContext context)
{
target.Id = source.NodeKey;
target.Name = source.NodeName;
target.MemberType = new TrackedReferenceMemberType
{
Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
};
}
// Umbraco.Code.MapAll
private void Map(RelationItemModel source, DocumentTypePropertyTypeReferenceResponseModel target, MapperContext context)
{
target.Id = source.NodeKey;
target.Name = source.NodeName;
target.Alias = source.NodeAlias;
target.DocumentType = new TrackedReferenceDocumentType
{
Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
};
}
// Umbraco.Code.MapAll
private void Map(RelationItemModel source, MediaTypePropertyTypeReferenceResponseModel target, MapperContext context)
{
target.Id = source.NodeKey;
target.Name = source.NodeName;
target.Alias = source.NodeAlias;
target.MediaType = new TrackedReferenceMediaType
{
Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
};
}
// Umbraco.Code.MapAll
private void Map(RelationItemModel source, MemberTypePropertyTypeReferenceResponseModel target, MapperContext context)
{
target.Id = source.NodeKey;
target.Name = source.NodeName;
target.Alias = source.NodeAlias;
target.MemberType = new TrackedReferenceMemberType
{
Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
+799 -2
View File
@@ -816,6 +816,70 @@
]
}
},
"/umbraco/management/api/v1/data-type/{id}/referenced-by": {
"get": {
"tags": [
"Data Type"
],
"operationId": "GetDataTypeByIdReferencedBy",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedIReferenceResponseModel"
}
]
}
}
}
},
"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/data-type/{id}/references": {
"get": {
"tags": [
@@ -872,6 +936,7 @@
"description": "The authenticated user does not have access to this resource"
}
},
"deprecated": true,
"security": [
{
"Backoffice User": [ ]
@@ -3420,6 +3485,66 @@
]
}
},
"/umbraco/management/api/v1/document-blueprint/{id}/scaffold": {
"get": {
"tags": [
"Document Blueprint"
],
"operationId": "GetDocumentBlueprintByIdScaffold",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/DocumentBlueprintResponseModel"
}
]
}
}
}
},
"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-blueprint/folder": {
"post": {
"tags": [
@@ -6245,6 +6370,13 @@
"type": "string"
}
},
{
"name": "isElement",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "skip",
"in": "query",
@@ -8895,6 +9027,17 @@
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PublishWithDescendantsResultModel"
}
]
}
}
}
},
"400": {
@@ -8975,6 +9118,89 @@
]
}
},
"/umbraco/management/api/v1/document/{id}/publish-with-descendants/result/{taskId}": {
"get": {
"tags": [
"Document"
],
"operationId": "GetDocumentByIdPublishWithDescendantsResultByTaskId",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "taskId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PublishWithDescendantsResultModel"
}
]
}
}
}
},
"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}/published": {
"get": {
"tags": [
@@ -10069,6 +10295,13 @@
"type": "string"
}
},
{
"name": "trashed",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "skip",
"in": "query",
@@ -10597,6 +10830,61 @@
]
}
},
"/umbraco/management/api/v1/recycle-bin/document/referenced-by": {
"get": {
"tags": [
"Document"
],
"operationId": "GetRecycleBinDocumentReferencedBy",
"parameters": [
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedIReferenceResponseModel"
}
]
}
}
}
},
"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/recycle-bin/document/root": {
"get": {
"tags": [
@@ -15766,6 +16054,13 @@
"type": "string"
}
},
{
"name": "trashed",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "skip",
"in": "query",
@@ -17741,6 +18036,61 @@
]
}
},
"/umbraco/management/api/v1/recycle-bin/media/referenced-by": {
"get": {
"tags": [
"Media"
],
"operationId": "GetRecycleBinMediaReferencedBy",
"parameters": [
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedIReferenceResponseModel"
}
]
}
}
}
},
"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/recycle-bin/media/root": {
"get": {
"tags": [
@@ -20170,6 +20520,134 @@
]
}
},
"/umbraco/management/api/v1/member/{id}/referenced-by": {
"get": {
"tags": [
"Member"
],
"operationId": "GetMemberByIdReferencedBy",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedIReferenceResponseModel"
}
]
}
}
}
},
"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/member/{id}/referenced-descendants": {
"get": {
"tags": [
"Member"
],
"operationId": "GetMemberByIdReferencedDescendants",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedReferenceByIdModel"
}
]
}
}
}
},
"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/member/{id}/validate": {
"put": {
"tags": [
@@ -20312,6 +20790,73 @@
]
}
},
"/umbraco/management/api/v1/member/are-referenced": {
"get": {
"tags": [
"Member"
],
"operationId": "GetMemberAreReferenced",
"parameters": [
{
"name": "id",
"in": "query",
"schema": {
"uniqueItems": true,
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
},
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 20
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedReferenceByIdModel"
}
]
}
}
}
},
"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/member/configuration": {
"get": {
"tags": [
@@ -36696,7 +37241,6 @@
"type": "string"
},
"translation": {
"minLength": 1,
"type": "string"
}
},
@@ -36872,6 +37416,7 @@
},
"DocumentCollectionResponseModel": {
"required": [
"ancestors",
"documentType",
"id",
"isProtected",
@@ -36927,6 +37472,16 @@
"isProtected": {
"type": "boolean"
},
"ancestors": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
]
}
},
"updater": {
"type": "string",
"nullable": true
@@ -37126,7 +37681,8 @@
"required": [
"$type",
"documentType",
"id"
"id",
"variants"
],
"type": "object",
"properties": {
@@ -37151,6 +37707,16 @@
"$ref": "#/components/schemas/TrackedReferenceDocumentTypeModel"
}
]
},
"variants": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/DocumentVariantItemResponseModel"
}
]
}
}
},
"additionalProperties": false,
@@ -37229,6 +37795,7 @@
},
"DocumentTreeItemResponseModel": {
"required": [
"ancestors",
"createDate",
"documentType",
"hasChildren",
@@ -37268,6 +37835,16 @@
"isProtected": {
"type": "boolean"
},
"ancestors": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
]
}
},
"documentType": {
"oneOf": [
{
@@ -37513,6 +38090,45 @@
},
"additionalProperties": false
},
"DocumentTypePropertyTypeReferenceResponseModel": {
"required": [
"$type",
"documentType",
"id"
],
"type": "object",
"properties": {
"$type": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string",
"nullable": true
},
"alias": {
"type": "string",
"nullable": true
},
"documentType": {
"oneOf": [
{
"$ref": "#/components/schemas/TrackedReferenceDocumentTypeModel"
}
]
}
},
"additionalProperties": false,
"discriminator": {
"propertyName": "$type",
"mapping": {
"DocumentTypePropertyTypeReferenceResponseModel": "#/components/schemas/DocumentTypePropertyTypeReferenceResponseModel"
}
}
},
"DocumentTypePropertyTypeResponseModel": {
"required": [
"alias",
@@ -39577,6 +40193,45 @@
},
"additionalProperties": false
},
"MediaTypePropertyTypeReferenceResponseModel": {
"required": [
"$type",
"id",
"mediaType"
],
"type": "object",
"properties": {
"$type": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string",
"nullable": true
},
"alias": {
"type": "string",
"nullable": true
},
"mediaType": {
"oneOf": [
{
"$ref": "#/components/schemas/TrackedReferenceMediaTypeModel"
}
]
}
},
"additionalProperties": false,
"discriminator": {
"propertyName": "$type",
"mapping": {
"MediaTypePropertyTypeReferenceResponseModel": "#/components/schemas/MediaTypePropertyTypeReferenceResponseModel"
}
}
},
"MediaTypePropertyTypeResponseModel": {
"required": [
"alias",
@@ -40084,6 +40739,41 @@
],
"type": "string"
},
"MemberReferenceResponseModel": {
"required": [
"$type",
"id",
"memberType"
],
"type": "object",
"properties": {
"$type": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string",
"nullable": true
},
"memberType": {
"oneOf": [
{
"$ref": "#/components/schemas/TrackedReferenceMemberTypeModel"
}
]
}
},
"additionalProperties": false,
"discriminator": {
"propertyName": "$type",
"mapping": {
"MemberReferenceResponseModel": "#/components/schemas/MemberReferenceResponseModel"
}
}
},
"MemberResponseModel": {
"required": [
"email",
@@ -40320,6 +41010,45 @@
},
"additionalProperties": false
},
"MemberTypePropertyTypeReferenceResponseModel": {
"required": [
"$type",
"id",
"memberType"
],
"type": "object",
"properties": {
"$type": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string",
"nullable": true
},
"alias": {
"type": "string",
"nullable": true
},
"memberType": {
"oneOf": [
{
"$ref": "#/components/schemas/TrackedReferenceMemberTypeModel"
}
]
}
},
"additionalProperties": false,
"discriminator": {
"propertyName": "$type",
"mapping": {
"MemberTypePropertyTypeReferenceResponseModel": "#/components/schemas/MemberTypePropertyTypeReferenceResponseModel"
}
}
},
"MemberTypePropertyTypeResponseModel": {
"required": [
"alias",
@@ -41492,8 +42221,20 @@
{
"$ref": "#/components/schemas/DocumentReferenceResponseModel"
},
{
"$ref": "#/components/schemas/DocumentTypePropertyTypeReferenceResponseModel"
},
{
"$ref": "#/components/schemas/MediaReferenceResponseModel"
},
{
"$ref": "#/components/schemas/MediaTypePropertyTypeReferenceResponseModel"
},
{
"$ref": "#/components/schemas/MemberReferenceResponseModel"
},
{
"$ref": "#/components/schemas/MemberTypePropertyTypeReferenceResponseModel"
}
]
}
@@ -42867,6 +43608,23 @@
},
"additionalProperties": false
},
"PublishWithDescendantsResultModel": {
"required": [
"isComplete",
"taskId"
],
"type": "object",
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"isComplete": {
"type": "boolean"
}
},
"additionalProperties": false
},
"PublishedDocumentResponseModel": {
"required": [
"documentType",
@@ -44131,8 +44889,15 @@
"additionalProperties": false
},
"TrackedReferenceDocumentTypeModel": {
"required": [
"id"
],
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"icon": {
"type": "string",
"nullable": true
@@ -44149,8 +44914,40 @@
"additionalProperties": false
},
"TrackedReferenceMediaTypeModel": {
"required": [
"id"
],
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"icon": {
"type": "string",
"nullable": true
},
"alias": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"TrackedReferenceMemberTypeModel": {
"required": [
"id"
],
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"icon": {
"type": "string",
"nullable": true
@@ -38,7 +38,7 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
Type = ReferenceType.SecurityScheme,
Id = ManagementApiConfiguration.ApiSecurityName
}
}, new string[] { }
}, []
}
}
};
@@ -36,5 +36,7 @@ public class ConfigureUmbracoBackofficeJsonOptions : IConfigureNamedOptions<Json
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());
options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
options.JsonSerializerOptions.MaxDepth = 64; // Ensures the maximum possible value is used, in particular to support handling as best we can levels of nested blocks.
}
}
@@ -16,9 +16,41 @@ public interface IUserStartNodeEntitiesService
/// <remarks>
/// The returned entities may include entities that outside of the user start node scope, but are needed to
/// for browsing to the actual user start nodes. These entities will be marked as "no access" entities.
///
/// This method does not support pagination, because it must load all entities explicitly in order to calculate
/// the correct result, given that user start nodes can be descendants of root nodes. Consumers need to apply
/// pagination to the result if applicable.
/// </remarks>
IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes umbracoObjectType, int[] userStartNodeIds);
/// <summary>
/// Calculates the applicable child entities for a given object type for users without root access.
/// </summary>
/// <param name="umbracoObjectType">The object type.</param>
/// <param name="userStartNodePaths">The calculated start node paths for the user.</param>
/// <param name="parentKey">The key of the parent.</param>
/// <param name="skip">The number of applicable children to skip.</param>
/// <param name="take">The number of applicable children to take.</param>
/// <param name="ordering">The ordering to apply when fetching and paginating the children.</param>
/// <param name="totalItems">The total number of applicable children available.</param>
/// <returns>A list of child entities applicable for the user.</returns>
/// <remarks>
/// The returned entities may include entities that outside of the user start node scope, but are needed to
/// for browsing to the actual user start nodes. These entities will be marked as "no access" entities.
/// </remarks>
IEnumerable<UserAccessEntity> ChildUserAccessEntities(
UmbracoObjectTypes umbracoObjectType,
string[] userStartNodePaths,
Guid parentKey,
int skip,
int take,
Ordering ordering,
out long totalItems)
{
totalItems = 0;
return [];
}
/// <summary>
/// Calculates the applicable child entities from a list of candidate child entities for users without root access.
/// </summary>
@@ -1,8 +1,12 @@
using Umbraco.Cms.Core;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Api.Management.Models.Entities;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Services.Entities;
@@ -10,8 +14,24 @@ namespace Umbraco.Cms.Api.Management.Services.Entities;
public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
{
private readonly IEntityService _entityService;
private readonly ICoreScopeProvider _scopeProvider;
private readonly IIdKeyMap _idKeyMap;
public UserStartNodeEntitiesService(IEntityService entityService) => _entityService = entityService;
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
public UserStartNodeEntitiesService(IEntityService entityService)
: this(
entityService,
StaticServiceProvider.Instance.GetRequiredService<ICoreScopeProvider>(),
StaticServiceProvider.Instance.GetRequiredService<IIdKeyMap>())
{
}
public UserStartNodeEntitiesService(IEntityService entityService, ICoreScopeProvider scopeProvider, IIdKeyMap idKeyMap)
{
_entityService = entityService;
_scopeProvider = scopeProvider;
_idKeyMap = idKeyMap;
}
/// <inheritdoc />
public IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes umbracoObjectType, int[] userStartNodeIds)
@@ -43,6 +63,54 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
.ToArray();
}
public IEnumerable<UserAccessEntity> ChildUserAccessEntities(UmbracoObjectTypes umbracoObjectType, string[] userStartNodePaths, Guid parentKey, int skip, int take, Ordering ordering, out long totalItems)
{
Attempt<int> parentIdAttempt = _idKeyMap.GetIdForKey(parentKey, umbracoObjectType);
if (parentIdAttempt.Success is false)
{
totalItems = 0;
return [];
}
var parentId = parentIdAttempt.Result;
IEntitySlim? parent = _entityService.Get(parentId);
if (parent is null)
{
totalItems = 0;
return [];
}
IEntitySlim[] children;
if (userStartNodePaths.Any(path => $"{parent.Path},".StartsWith($"{path},")))
{
// the requested parent is one of the user start nodes (or a descendant of one), all children are by definition allowed
children = _entityService.GetPagedChildren(parentKey, umbracoObjectType, skip, take, out totalItems, ordering: ordering).ToArray();
return ChildUserAccessEntities(children, userStartNodePaths);
}
// if one or more of the user start nodes are descendants of the requested parent, find the "next child IDs" in those user start node paths
// - e.g. given the user start node path "-1,2,3,4,5", if the requested parent ID is 3, the "next child ID" is 4.
var userStartNodePathIds = userStartNodePaths.Select(path => path.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray()).ToArray();
var allowedChildIds = userStartNodePathIds
.Where(ids => ids.Contains(parentId))
// given the previous checks, the parent ID can never be the last in the user start node path, so this is safe
.Select(ids => ids[ids.IndexOf(parentId) + 1])
.Distinct()
.ToArray();
totalItems = allowedChildIds.Length;
if (allowedChildIds.Length == 0)
{
// the requested parent is outside the scope of any user start nodes
return [];
}
// even though we know the IDs of the allowed child entities to fetch, we still use a Query to yield correctly sorted children
IQuery<IUmbracoEntity> query = _scopeProvider.CreateQuery<IUmbracoEntity>().Where(x => allowedChildIds.Contains(x.Id));
children = _entityService.GetPagedChildren(parentKey, umbracoObjectType, skip, take, out totalItems, query, ordering).ToArray();
return ChildUserAccessEntities(children, userStartNodePaths);
}
/// <inheritdoc />
public IEnumerable<UserAccessEntity> ChildUserAccessEntities(IEnumerable<IEntitySlim> candidateChildren, string[] userStartNodePaths)
// child entities for users without root access should include:
@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
@@ -7,6 +7,5 @@ public class DictionaryItemTranslationModel
[Required]
public string IsoCode { get; set; } = string.Empty;
[Required]
public string Translation { get; set; } = string.Empty;
}
@@ -11,5 +11,7 @@ public class DocumentCollectionResponseModel : ContentCollectionResponseModelBas
public bool IsProtected { get; set; }
public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];
public string? Updater { get; set; }
}
@@ -0,0 +1,8 @@
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
public class PublishWithDescendantsResultModel
{
public Guid TaskId { get; set; }
public bool IsComplete { get; set; }
}
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public abstract class ContentTypePropertyTypeReferenceResponseModel : ReferenceResponseModel
{
public string? Alias { get; set; }
}
@@ -1,11 +1,7 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class DefaultReferenceResponseModel : IReferenceResponseModel
public class DefaultReferenceResponseModel : ReferenceResponseModel
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Type { get; set; }
public string? Icon { get; set; }
@@ -1,12 +1,12 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Api.Management.ViewModels.Document;
public class DocumentReferenceResponseModel : IReferenceResponseModel
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class DocumentReferenceResponseModel : ReferenceResponseModel
{
public Guid Id { get; set; }
public string? Name { get; set; }
public bool? Published { get; set; }
public TrackedReferenceDocumentType DocumentType { get; set; } = new();
public IEnumerable<DocumentVariantItemResponseModel> Variants { get; set; } = Enumerable.Empty<DocumentVariantItemResponseModel>();
}
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class DocumentTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
{
public TrackedReferenceDocumentType DocumentType { get; set; } = new();
}
@@ -1,10 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class MediaReferenceResponseModel : IReferenceResponseModel
public class MediaReferenceResponseModel : ReferenceResponseModel
{
public Guid Id { get; set; }
public string? Name { get; set; }
public TrackedReferenceMediaType MediaType { get; set; } = new();
}
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class MediaTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
{
public TrackedReferenceMediaType MediaType { get; set; } = new();
}
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class MemberReferenceResponseModel : ReferenceResponseModel
{
public TrackedReferenceMemberType MemberType { get; set; } = new();
}
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class MemberTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
{
public TrackedReferenceMemberType MemberType { get; set; } = new();
}
@@ -0,0 +1,8 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public abstract class ReferenceResponseModel : IReferenceResponseModel
{
public Guid Id { get; set; }
public string? Name { get; set; }
}
@@ -1,7 +1,9 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public abstract class TrackedReferenceContentType
{
public Guid Id { get; set; }
public string? Icon { get; set; }
public string? Alias { get; set; }
@@ -0,0 +1,5 @@
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public class TrackedReferenceMemberType : TrackedReferenceContentType
{
}
@@ -1,4 +1,3 @@
using Umbraco.Cms.Api.Management.ViewModels.Content;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
@@ -8,6 +7,8 @@ public class DocumentTreeItemResponseModel : ContentTreeItemResponseModel
{
public bool IsProtected { get; set; }
public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];
public DocumentTypeReferenceResponseModel DocumentType { get; set; } = new();
public IEnumerable<DocumentVariantItemResponseModel> Variants { get; set; } = Enumerable.Empty<DocumentVariantItemResponseModel>();
@@ -127,10 +127,16 @@ internal class EFCoreScope<TDbContext> : CoreScope, IEfCoreScope<TDbContext>
Locks.ClearLocks(InstanceId);
if (ParentScope is null)
// Since we can nest EFCoreScopes in other scopes derived from CoreScope, we should check whether our ParentScope OR the base ParentScope exists.
// Only if neither do do we take responsibility for ensuring the locks are cleared.
// Eventually the highest parent will clear the locks.
// Further, these locks are a reference to the locks of the highest parent anyway (see the constructor of CoreScope).
#pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists (justification: provides additional clarify here that this is defined on the base class).
if (ParentScope is null && base.HasParentScope is false)
{
Locks.EnsureLocksCleared(InstanceId);
}
#pragma warning restore SA1100 // Do not prefix calls with base unless local implementation exists
_efCoreScopeProvider.PopAmbientScope();
@@ -26,23 +26,36 @@
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<!-- Restore and build backoffice project -->
<!-- General ignored files -->
<ItemGroup>
<Content Remove="wwwroot\umbraco\assets\README.md" />
</ItemGroup>
<!-- BEGIN: Restore and build backoffice project -->
<PropertyGroup>
<BackofficeProjectDirectory Condition="'$(BackofficeProjectDirectory)' == ''">..\Umbraco.Web.UI.Client\</BackofficeProjectDirectory>
<BackofficeAssetsPath>wwwroot\umbraco\backoffice</BackofficeAssetsPath>
<BackofficeAssetsPath>$(ProjectDir)wwwroot\umbraco\backoffice</BackofficeAssetsPath>
</PropertyGroup>
<ItemGroup>
<BackofficeAssetsInputs Include="$(BackofficeProjectDirectory)package.json;$(BackofficeProjectDirectory)package-lock.json;$(BackofficeProjectDirectory)src\**" Exclude="$(DefaultItemExcludes)" />
<Content Remove="$(BackofficeAssetsPath)\**" />
</ItemGroup>
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(BackofficeProjectDirectory)" />
<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
<Message Text="Skip BuildBackoffice target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
<Message Text="Skip BuildBackoffice target because '$(BackofficeAssetsPath)' already exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)')" />
<Message Text="Call BuildBackoffice target because UmbracoBuild is empty (this is Visual Studio) and '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
<CallTarget Targets="BuildBackoffice" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
</Target>
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice" BeforeTargets="AssignTargetPaths" Inputs="@(BackofficeAssetsInputs)" Outputs="$(IntermediateOutputPath)backoffice.complete.txt">
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(BackofficeProjectDirectory)" />
</Target>
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice">
<Message Importance="high" Text="Executing Backoffice NPM build script..." />
<Exec Command="npm run build:for:cms" WorkingDirectory="$(BackofficeProjectDirectory)" />
<ItemGroup>
@@ -61,27 +74,46 @@
</DefineStaticWebAssets>
</Target>
<!-- Restore and build login project -->
<Target Name="CleanStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
<Message Text="Skip CleanBackoffice target because '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(BackofficeAssetsPath)')" />
<Message Text="Skip CleanBackoffice target because preserve.backoffice marker file exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and Exists('$(SolutionDir)preserve.backoffice')" />
<Message Text="Call CleanBackoffice target because '$(BackofficeAssetsPath)' exists and preserve.backoffice marker file doesn't exist" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
<CallTarget Targets="CleanBackoffice" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
</Target>
<Target Name="CleanBackoffice">
<ItemGroup>
<BackofficeDirectories Include="$(BackofficeAssetsPath)" />
</ItemGroup>
<RemoveDir Directories="@(BackofficeDirectories)" />
</Target>
<!-- END: Restore and build backoffice project -->
<!-- BEGIN: Restore and build login project -->
<PropertyGroup>
<LoginProjectDirectory Condition="'$(LoginProjectDirectory)' == ''">..\Umbraco.Web.UI.Login\</LoginProjectDirectory>
<LoginAssetsPath>wwwroot\umbraco\login</LoginAssetsPath>
<LoginAssetsPath>$(ProjectDir)wwwroot\umbraco\login</LoginAssetsPath>
</PropertyGroup>
<ItemGroup>
<LoginAssetsInputs Include="$(LoginProjectDirectory)**" Exclude="$(DefaultItemExcludes)" />
<Content Remove="$(LoginAssetsPath)\**" />
</ItemGroup>
<ItemGroup>
<Content Remove="wwwroot\umbraco\assets\README.md" />
</ItemGroup>
<Target Name="BuildLoginStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
<Message Text="Skip BuildLogin target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
<Message Text="Skip BuildLogin target because '$(LoginAssetsPath)' already exists" Importance="high" Condition="Exists('$(LoginAssetsPath)')" />
<Message Text="Call BuildLogin target because UmbracoBuild is empty (this is Visual Studio) and '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
<CallTarget Targets="BuildLogin" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
</Target>
<Target Name="RestoreLogin" Inputs="$(LoginProjectDirectory)package-lock.json" Outputs="$(LoginProjectDirectory)node_modules/.package-lock.json">
<Message Importance="high" Text="Restoring Login NPM packages..." />
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(LoginProjectDirectory)" />
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(LoginProjectDirectory)" />
</Target>
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin" BeforeTargets="AssignTargetPaths" Inputs="@(LoginAssetsInputs)" Outputs="$(IntermediateOutputPath)login.complete.txt">
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin">
<Message Importance="high" Text="Executing Login NPM build script..." />
<Exec Command="npm run build" WorkingDirectory="$(LoginProjectDirectory)" />
<ItemGroup>
@@ -99,4 +131,19 @@
<Output TaskParameter="Assets" ItemName="StaticWebAsset" />
</DefineStaticWebAssets>
</Target>
<Target Name="CleanLoginStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
<Message Text="Skip CleanLogin target because '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(LoginAssetsPath)')" />
<Message Text="Skip CleanLogin target because preserve.login marker file exists" Importance="high" Condition="Exists('$(LoginAssetsPath)') and Exists('$(SolutionDir)preserve.login')" />
<Message Text="Call CleanLogin target because '$(LoginAssetsPath)' exists and preserve.login marker file doesn't exist" Importance="high" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
<CallTarget Targets="CleanLogin" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
</Target>
<Target Name="CleanLogin">
<ItemGroup>
<LoginDirectories Include="$(LoginAssetsPath)" />
</ItemGroup>
<RemoveDir Directories="@(LoginDirectories)" />
</Target>
<!-- END: Restore and build login project -->
</Project>
+2
View File
@@ -37,6 +37,7 @@ public interface IAction : IDiscoverable
/// <summary>
/// Gets the icon to display for this action
/// </summary>
[Obsolete("No longer used. Scheduled for removal in V17.")]
string Icon { get; }
/// <summary>
@@ -51,5 +52,6 @@ public interface IAction : IDiscoverable
/// <remarks>
/// Used in the UI when assigning permissions
/// </remarks>
[Obsolete("No longer used. Scheduled for removal in V17.")]
string? Category { get; }
}
+2
View File
@@ -22,4 +22,6 @@ public static class CacheKeys
public const string PreviewPropertyCacheKeyPrefix = "Cache.Property.CacheValues[D:";
public const string PropertyCacheKeyPrefix = "Cache.Property.CacheValues[P:";
public const string MemberUserNameCachePrefix = "uRepo_userNameKey+";
}
@@ -1,3 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
@@ -21,9 +23,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
private readonly IDocumentNavigationManagementService _documentNavigationManagementService;
private readonly IContentService _contentService;
private readonly IDocumentCacheService _documentCacheService;
private readonly ICacheManager _cacheManager;
private readonly IPublishStatusManagementService _publishStatusManagementService;
private readonly IIdKeyMap _idKeyMap;
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
public ContentCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
@@ -38,6 +42,39 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
IContentService contentService,
IPublishStatusManagementService publishStatusManagementService,
IDocumentCacheService documentCacheService)
: this(
appCaches,
serializer,
idKeyMap,
domainService,
eventAggregator,
factory,
documentUrlService,
domainCacheService,
documentNavigationQueryService,
documentNavigationManagementService,
contentService,
publishStatusManagementService,
documentCacheService,
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
{
}
public ContentCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
IIdKeyMap idKeyMap,
IDomainService domainService,
IEventAggregator eventAggregator,
ICacheRefresherNotificationFactory factory,
IDocumentUrlService documentUrlService,
IDomainCacheService domainCacheService,
IDocumentNavigationQueryService documentNavigationQueryService,
IDocumentNavigationManagementService documentNavigationManagementService,
IContentService contentService,
IPublishStatusManagementService publishStatusManagementService,
IDocumentCacheService documentCacheService,
ICacheManager cacheManager)
: base(appCaches, serializer, eventAggregator, factory)
{
_idKeyMap = idKeyMap;
@@ -49,6 +86,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
_contentService = contentService;
_documentCacheService = documentCacheService;
_publishStatusManagementService = publishStatusManagementService;
// TODO: Ideally we should inject IElementsCache
// this interface is in infrastructure, and changing this is very breaking
// so as long as we have the cache manager, which casts the IElementsCache to a simple AppCache we might as well use that.
_cacheManager = cacheManager;
}
#region Indirect
@@ -83,6 +125,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
AppCaches.RuntimeCache.ClearOfType<PublicAccessEntry>();
AppCaches.RuntimeCache.ClearByKey(CacheKeys.ContentRecycleBinCacheKey);
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
_cacheManager.ElementsCache.Clear();
var idsRemoved = new HashSet<int>();
IAppPolicyCache isolatedCache = AppCaches.IsolatedCaches.GetOrCreate<IContent>();
@@ -1,3 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
@@ -18,7 +20,9 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
private readonly IMediaNavigationManagementService _mediaNavigationManagementService;
private readonly IMediaService _mediaService;
private readonly IMediaCacheService _mediaCacheService;
private readonly ICacheManager _cacheManager;
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
public MediaCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
@@ -29,6 +33,31 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
IMediaNavigationManagementService mediaNavigationManagementService,
IMediaService mediaService,
IMediaCacheService mediaCacheService)
: this(
appCaches,
serializer,
idKeyMap,
eventAggregator,
factory,
mediaNavigationQueryService,
mediaNavigationManagementService,
mediaService,
mediaCacheService,
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
{
}
public MediaCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
IIdKeyMap idKeyMap,
IEventAggregator eventAggregator,
ICacheRefresherNotificationFactory factory,
IMediaNavigationQueryService mediaNavigationQueryService,
IMediaNavigationManagementService mediaNavigationManagementService,
IMediaService mediaService,
IMediaCacheService mediaCacheService,
ICacheManager cacheManager)
: base(appCaches, serializer, eventAggregator, factory)
{
_idKeyMap = idKeyMap;
@@ -36,6 +65,9 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
_mediaNavigationManagementService = mediaNavigationManagementService;
_mediaService = mediaService;
_mediaCacheService = mediaCacheService;
// TODO: Use IElementsCache instead of ICacheManager, see ContentCacheRefresher for more information.
_cacheManager = cacheManager;
}
#region Indirect
@@ -87,6 +119,13 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey);
Attempt<IAppPolicyCache?> mediaCache = AppCaches.IsolatedCaches.Get<IMedia>();
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
_cacheManager.ElementsCache.Clear();
foreach (JsonPayload payload in payloads)
{
if (payload.ChangeTypes == TreeChangeTypes.Remove)
@@ -71,11 +71,22 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCache
foreach (JsonPayload p in payloads)
{
_idKeyMap.ClearCache(p.Id);
if (memberCache.Success)
if (memberCache.Success is false)
{
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
continue;
}
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
// This specific cache key was introduced to fix an issue where the member username could not be the same as the member id, because the cache keys collided.
// This is done in a bit of a hacky way, because the cache key is created internally in the repository, but we need to clear it here.
// Ideally, we want to use a shared way of generating the key between this and the repository.
// Additionally, the RepositoryCacheKeys actually caches the string to avoid re-allocating memory; we would like to also use this in the repository
// See:
// https://github.com/umbraco/Umbraco-CMS/pull/17350
// https://github.com/umbraco/Umbraco-CMS/pull/17815
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(CacheKeys.MemberUserNameCachePrefix + p.Username));
}
}
}
@@ -160,9 +160,9 @@ public class ObservableDictionary<TKey, TValue> : ObservableCollection<TValue>,
if (index != Count)
{
foreach (TKey k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList())
foreach (KeyValuePair<TKey, int> largerOrEqualToIndex in Indecies.Where(kvp => kvp.Value >= index))
{
Indecies[k]++;
Indecies[largerOrEqualToIndex.Key] = largerOrEqualToIndex.Value + 1;
}
}
@@ -185,9 +185,9 @@ public class ObservableDictionary<TKey, TValue> : ObservableCollection<TValue>,
Indecies.Remove(key);
foreach (TKey k in Indecies.Keys.Where(k => Indecies[k] > index).ToList())
foreach (KeyValuePair<TKey, int> largerThanIndex in Indecies.Where(kvp => kvp.Value > index))
{
Indecies[k]--;
Indecies[largerThanIndex.Key] = largerThanIndex.Value - 1;
}
}
+1 -4
View File
@@ -233,10 +233,7 @@ public class TypeFinder : ITypeFinder
excludeFromResults = new HashSet<Assembly>();
}
if (exclusionFilter == null)
{
exclusionFilter = new string[] { };
}
exclusionFilter ??= [];
return GetAllAssemblies()
.Where(x => excludeFromResults.Contains(x) == false
@@ -5,14 +5,17 @@ namespace Umbraco.Extensions;
public static class ContentSettingsExtensions
{
/// <summary>
/// Determines if file extension is allowed for upload based on (optional) white list and black list
/// held in settings.
/// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted.
/// Determines if file extension is allowed for upload based on (optional) allow list and deny list held in settings.
/// Disallowed file extensions are only considered if there are no allowed file extensions.
/// </summary>
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) =>
contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) ||
(contentSettings.AllowedUploadedFileExtensions.Any() == false &&
contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false);
/// <param name="contentSettings">The content settings.</param>
/// <param name="extension">The file extension.</param>
/// <returns>
/// <c>true</c> if the file extension is allowed for upload; otherwise, <c>false</c>.
/// </returns>
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension)
=> contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) ||
(contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) == false);
/// <summary>
/// Gets the auto-fill configuration for a specified property alias.
+13 -3
View File
@@ -233,17 +233,27 @@ public static partial class Constants
public const string RelatedMediaName = "Related Media";
/// <summary>
/// Alias for default relation type "Related Media"
/// Alias for default relation type "Related Media".
/// </summary>
public const string RelatedMediaAlias = "umbMedia";
/// <summary>
/// Name for default relation type "Related Member".
/// </summary>
public const string RelatedMemberName = "Related Member";
/// <summary>
/// Alias for default relation type "Related Member".
/// </summary>
public const string RelatedMemberAlias = "umbMember";
/// <summary>
/// Name for default relation type "Related Document".
/// </summary>
public const string RelatedDocumentName = "Related Document";
/// <summary>
/// Alias for default relation type "Related Document"
/// Alias for default relation type "Related Document".
/// </summary>
public const string RelatedDocumentAlias = "umbDocument";
@@ -284,7 +294,7 @@ public static partial class Constants
/// Developers should not manually use these relation types since they will all be cleared whenever an entity
/// (content, media or member) is saved since they are auto-populated based on property values.
/// </remarks>
public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedDocumentAlias };
public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedMemberAlias, RelatedDocumentAlias };
// TODO: return a list of built in types so we can use that to prevent deletion in the UI
}
@@ -0,0 +1,25 @@
namespace Umbraco.Cms.Core;
public static partial class Constants
{
/// <summary>
/// Defines reference types.
/// </summary>
/// <remarks>
/// Reference types are used to identify the type of entity that is being referenced when exposing references
/// between Umbraco entities.
/// These are used in the management API and backoffice to indicate and warn editors when working with an entity,
/// as to what other entities depend on it.
/// These consist of references managed by Umbraco relations (e.g. document, media and member).
/// But also references that come from schema (e.g. data type usage on content types).
/// </remarks>
public static class ReferenceType
{
public const string Document = UdiEntityType.Document;
public const string Media = UdiEntityType.Media;
public const string Member = UdiEntityType.Member;
public const string DocumentTypePropertyType = "document-type-property-type";
public const string MediaTypePropertyType = "media-type-property-type";
public const string MemberTypePropertyType = "member-type-property-type";
}
}
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
@@ -19,6 +20,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
private readonly IPublishedContentCache _contentCache;
private readonly IDocumentNavigationQueryService _navigationQueryService;
private readonly IPublishStatusQueryService _publishStatusQueryService;
private readonly IDocumentUrlService _documentUrlService;
private RequestHandlerSettings _requestSettings;
public ApiContentRouteBuilder(
@@ -29,7 +31,8 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
IOptionsMonitor<RequestHandlerSettings> requestSettings,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService,
IPublishStatusQueryService publishStatusQueryService)
IPublishStatusQueryService publishStatusQueryService,
IDocumentUrlService documentUrlService)
{
_apiContentPathProvider = apiContentPathProvider;
_variationContextAccessor = variationContextAccessor;
@@ -37,11 +40,35 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
_contentCache = contentCache;
_navigationQueryService = navigationQueryService;
_publishStatusQueryService = publishStatusQueryService;
_documentUrlService = documentUrlService;
_globalSettings = globalSettings.Value;
_requestSettings = requestSettings.CurrentValue;
requestSettings.OnChange(settings => _requestSettings = settings);
}
[Obsolete("Use the non-obsolete constructor, scheduled for removal in v17")]
public ApiContentRouteBuilder(
IApiContentPathProvider apiContentPathProvider,
IOptions<GlobalSettings> globalSettings,
IVariationContextAccessor variationContextAccessor,
IRequestPreviewService requestPreviewService,
IOptionsMonitor<RequestHandlerSettings> requestSettings,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService,
IPublishStatusQueryService publishStatusQueryService)
: this(
apiContentPathProvider,
globalSettings,
variationContextAccessor,
requestPreviewService,
requestSettings,
contentCache,
navigationQueryService,
publishStatusQueryService,
StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>())
{
}
[Obsolete("Use the non-obsolete constructor, scheduled for removal in v17")]
public ApiContentRouteBuilder(
IApiContentPathProvider apiContentPathProvider,
@@ -59,7 +86,8 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
requestSettings,
contentCache,
navigationQueryService,
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>(),
StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>())
{
}
@@ -113,7 +141,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
// we can perform fallback to the content route.
if (IsInvalidContentPath(contentPath))
{
contentPath = _contentCache.GetRouteById(content.Id, culture) ?? contentPath;
contentPath = _documentUrlService.GetLegacyRouteFormat(content.Key, culture ?? _variationContextAccessor.VariationContext?.Culture, isPreview);
}
// if the content path has still not been resolved as a valid path, the content is un-routable in this culture
@@ -125,7 +153,9 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
: null;
}
return contentPath;
return _requestSettings.AddTrailingSlash
? contentPath?.EnsureEndsWith('/')
: contentPath?.TrimEnd('/');
}
private string ContentPreviewPath(IPublishedContent content) => $"{Constants.DeliveryApi.Routing.PreviewContentPathPrefix}{content.Key:D}{(_requestSettings.AddTrailingSlash ? "/" : string.Empty)}";
@@ -86,10 +86,35 @@ public sealed class ApiPublishedContentCache : IApiPublishedContentCache
_variationContextAccessor.VariationContext?.Culture,
_requestPreviewService.IsPreview());
// in multi-root settings, we've historically resolved all but the first root by their ID + URL segment,
// e.g. "1234/second-root-url-segment". in V15+, IDocumentUrlService won't resolve this anymore; it will
// however resolve "1234/" correctly, so to remain backwards compatible, we need to perform this extra step.
var verifyUrlSegment = false;
if (documentKey is null && route.TrimEnd('/').CountOccurrences("/") is 1)
{
documentKey = _apiDocumentUrlService.GetDocumentKeyByRoute(
route[..(route.IndexOf('/') + 1)],
_variationContextAccessor.VariationContext?.Culture,
_requestPreviewService.IsPreview());
verifyUrlSegment = true;
}
IPublishedContent? content = documentKey.HasValue
? _publishedContentCache.GetById(isPreviewMode, documentKey.Value)
: null;
// the additional look-up above can result in false positives; if attempting to request a non-existing child to
// the currently contextualized request root (either by start item or by domain), the root content key might
// get resolved. to counter for this, we compare the requested URL segment with the resolved content URL segment.
if (content is not null && verifyUrlSegment)
{
var expectedUrlSegment = route[(route.IndexOf('/') + 1)..];
if (content.UrlSegment != expectedUrlSegment)
{
content = null;
}
}
return ContentOrNullIfDisallowed(content);
}
@@ -422,6 +422,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
// Two factor providers
Services.AddUnique<ITwoFactorLoginService, TwoFactorLoginService>();
Services.AddUnique<IUserTwoFactorLoginService, UserTwoFactorLoginService>();
Services.AddUnique<IMemberTwoFactorLoginService, MemberTwoFactorLoginService>();
// Add Query services
Services.AddUnique<IDocumentRecycleBinQueryService, DocumentRecycleBinQueryService>();
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language alias="da" intName="Danish" localName="dansk" lcid="6" culture="da-DK">
<creator>
<name>The Umbraco community</name>
@@ -125,7 +125,7 @@ Mange hilsner fra Umbraco robotten
<key alias="outOfRangeMultipleItemsMaximum">De %0% enheder givet er større end det tilladte minimum af %1%.</key>
<key alias="invalidStep">Værdien %0% passer ikke med den konfigureret trin værdi af %1% og mindste værdi af %2%.</key>
<key alias="unexpectedRange">Værdien %0% forventes ikke at indeholde et spænd.</key>
<key alias="stringLengthExceeded">Tekststrengen er længere end den tilladte længde på %0% tegn.</key>
<key alias="stringLengthExceeded">Tekststrengen er længere end den tilladte længde på %0% tegn, %1% for mange.</key>
<key alias="invalidRange">Værdien %0% forventes at have en værdi der er større end fra værdien.</key>
<key alias="invalidObjectType">Det valgte indhold er af den forkerte type.</key>
<key alias="notOneOfOptions">"Værdien '%0%' er ikke en af de tilgængelige valgmuligheder.</key>
@@ -377,8 +377,8 @@
<key alias="invalidEmpty">Value cannot be empty</key>
<key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key>
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters.</key>
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
<key alias="invalidMemberGroupName">Invalid member group name</key>
<key alias="invalidUserGroupName">Invalid user group name</key>
@@ -394,8 +394,8 @@
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters.</key>
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
<key alias="invalidMediaType">The chosen media type is invalid.</key>
<key alias="invalidContentType">The chosen content is of invalid type.</key>
@@ -35,7 +35,7 @@ public class InvalidCompositionException : Exception
/// <param name="addedCompositionAlias">The added composition alias.</param>
/// <param name="propertyTypeAliases">The property type aliases.</param>
public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, string[] propertyTypeAliases)
: this(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, new string[0])
: this(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, [])
{
}
@@ -245,13 +245,13 @@ public static class ContentExtensions
}
IEnumerable<ContentSchedule> expires = contentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date))
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.UtcNow > x.Date))
{
return ContentStatus.Expired;
}
IEnumerable<ContentSchedule> release = contentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now))
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.UtcNow))
{
return ContentStatus.AwaitingRelease;
}
@@ -7,6 +7,9 @@ namespace Umbraco.Extensions;
public static class DateTimeExtensions
{
/// <summary>
/// Defines the levels to truncate a date to.
/// </summary>
public enum DateTruncate
{
Year,
@@ -25,33 +28,39 @@ public static class DateTimeExtensions
public static string ToIsoString(this DateTime dt) =>
dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
/// <summary>
/// Truncates the date to the specified level, i.e. if you pass in DateTruncate.Hour it will truncate the date to the hour.
/// </summary>
/// <param name="dt">The date.</param>
/// <param name="truncateTo">The level to truncate the date to.</param>
/// <returns>The truncated date.</returns>
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
{
if (truncateTo == DateTruncate.Year)
{
return new DateTime(dt.Year, 1, 1);
return new DateTime(dt.Year, 1, 1, 0, 0, 0, dt.Kind);
}
if (truncateTo == DateTruncate.Month)
{
return new DateTime(dt.Year, dt.Month, 1);
return new DateTime(dt.Year, dt.Month, 1, 0, 0, 0, dt.Kind);
}
if (truncateTo == DateTruncate.Day)
{
return new DateTime(dt.Year, dt.Month, dt.Day);
return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0, dt.Kind);
}
if (truncateTo == DateTruncate.Hour)
{
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0);
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Kind);
}
if (truncateTo == DateTruncate.Minute)
{
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0);
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
}
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Kind);
}
}
+2 -2
View File
@@ -27,8 +27,8 @@ public static class IntExtensions
/// </returns>
public static Guid ToGuid(this int value)
{
var bytes = new byte[16];
BitConverter.GetBytes(value).CopyTo(bytes, 0);
Span<byte> bytes = stackalloc byte[16];
BitConverter.GetBytes(value).CopyTo(bytes);
return new Guid(bytes);
}
}
@@ -3850,9 +3850,8 @@ public static class PublishedContentExtensions
if (parentKey.HasValue)
{
IEnumerable<Guid> childrenKeys;
var foundChildrenKeys = contentTypeAlias is null
? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out childrenKeys)
? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable<Guid> childrenKeys)
: navigationQueryService.TryGetChildrenKeysOfType(parentKey.Value, contentTypeAlias, out childrenKeys);
return foundChildrenKeys
@@ -3860,19 +3859,12 @@ public static class PublishedContentExtensions
: [];
}
IEnumerable<Guid> rootKeys;
var foundRootKeys = contentTypeAlias is null
? navigationQueryService.TryGetRootKeys(out rootKeys)
? navigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys)
: navigationQueryService.TryGetRootKeysOfType(contentTypeAlias, out rootKeys);
if (foundRootKeys)
{
IEnumerable<Guid> rootKeysArray = rootKeys as Guid[] ?? rootKeys.ToArray();
return rootKeysArray.Contains(content.Key)
? publishedStatusFilteringService.FilterAvailable(rootKeysArray, culture)
: [];
}
return [];
return foundRootKeys
? publishedStatusFilteringService.FilterAvailable(rootKeys, culture)
: [];
}
}

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