Compare commits

...
132 Commits
Author SHA1 Message Date
Andy Butland c6ab394947 Merge branch 'release/10.8.11' into v10/dev
# Conflicts:
#	version.json
2025-07-02 06:32:18 +02:00
Kenn JacobsenandGitHub d8f68d2c40 Merge commit from fork 2025-06-24 08:39:17 +02:00
Andy Butland ff88617db0 Bumped version to 10.8.11. 2025-06-05 07:49:09 +02:00
Andy Butland 9f912aea0e Merge branch 'release/10.8.10' into v10/dev
# Conflicts:
#	version.json
2025-05-06 07:15:10 +02:00
Andy Butland ba95c12f09 Resolved failures in unit tests. 2025-05-06 05:37:30 +02:00
Andy ButlandandGitHub 14fbd20665 Merge commit from fork
* Backport user enumeration fix.

* Supress warning on use of .NET 6 with TimeProvider dependency.

* Replace TimeProvider with Stopwatch, as the former isn't tested against .NET 6.0 and generates warnings.

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

* Added randomness to login duration.

* Ensured against negative duration.
2025-05-06 05:11:03 +02:00
mole 2d8b5e8786 Use windows agent for nuget push 2025-04-28 10:48:26 +02:00
Andy Butland 747e095178 Bump version to 10.8.10. 2025-04-22 10:06:27 +02:00
Andy Butland 1cfa5a225e Merge branch 'release/10.8.9' into v10/dev
# Conflicts:
#	version.json
2025-03-11 09:20:51 +01:00
7888b9a4ce Merge commit from fork
* Bumped version to 10.8.9.

* Fixed parsing of node if in content and media permission querystring handlers to retrieve expected value when multiple are provided in the querystring.

# Conflicts:
#	tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs
#	tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs

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

* 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
Andy Butland e31582b297 Backport bumped imagesharp to prevent CVE-2025-27598 #18602 2025-03-09 08:58:25 +01:00
Nikolaj Geisle 75cc017a18 Merge branch 'release/10.8.8' into v10/dev
# Conflicts:
#	version.json
2025-01-23 11:08:09 +01:00
d60137e6da Merge commit from fork
* Ensure preview can only be requested with a valid culture code.

* Update src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs

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

* Restricted to predefined culture codes.

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2025-01-20 14:14:28 +01:00
Andy Butland 9f9c88781a Bumped version to 10.8.8. 2025-01-07 10:08:01 +01:00
c7014e159b Sort manifest file paths alphabetically (#14466)
* Sort manifest file paths alphabetically

* Update src/Umbraco.Infrastructure/Manifest/ManifestParser.cs

Co-authored-by: Ronald Barendse <ronald@barend.se>

---------

Co-authored-by: Ronald Barendse <ronald@barend.se>
2024-11-21 16:19:48 +01:00
Zeegaan 31e1acce67 Merge remote-tracking branch 'origin/release/10.8.7' into v10/dev
# Conflicts:
#	version.json
2024-10-23 08:48:08 +02:00
Sven GeusensandNikolaj Geisle 35c51a029a Prevents XSS when viewing an uploaded SVG
from the media-info and image-preview components.
2024-10-21 12:42:06 +02:00
ElitsaandNikolaj Geisle 8c1128c85b Update the HttpContext's user with the authenticated user's principal 2024-10-21 12:41:42 +02:00
ElitsaandNikolaj Geisle c9021ab2d2 Reduce the time when getRemainingTimeoutSeconds request is made from 30s to 20s, so fewer calls are made 2024-10-21 12:41:18 +02:00
ElitsaandNikolaj Geisle 67a71f8f82 Make sure that the client shows the login screen as close to the server's timout time as possible 2024-10-21 12:41:17 +02:00
Andreas ZerbstandAndreas Zerbst edd0a4a4a9 Updated message pack (#17320)
(cherry picked from commit 11270eaaf5)
2024-10-21 11:46:43 +02:00
Andreas ZerbstandGitHub 11270eaaf5 Updated message pack (#17320) 2024-10-21 11:00:47 +02:00
Bjarke BergandGitHub 2d71b5a63b Updated image sharp to a non vulnerable version (#17290) 2024-10-16 12:16:38 +02:00
Nikolaj Geisle 9bab74d30e Bump version.json 2024-10-07 11:55:34 +02:00
Bjarke Berg 0ee0db8071 Merge remote-tracking branch 'refs/remotes/origin/release/10.8' into v10/dev
# Conflicts:
#	version.json
2024-05-21 08:44:27 +02:00
Bjarke BergandGitHub c17d4e1a60 Merge pull request from GHSA-j74q-mv2c-rxmp 2024-05-17 08:37:51 +02:00
Bjarke Berg fee222daff Bump version 2024-05-03 09:29:37 +02:00
119fde2033 V10: Fix for fallback file upload (#14892) (#15868)
* Fix for fallback file upload (#14892)

* Added check for file type

* Removed unneeded null checks and fixed tabs

* Cleaning

* Cleanups, cleanups, and removal of unneeded null checks

* Reverted removal of relationshipservice

* Revert null check removals (too risky)

---------

Co-authored-by: Ambert van Unen <AvanUnen@ilionx.com>
Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

(cherry picked from commit 0b5d1f8aa6)

* Fix up formatting

---------

Co-authored-by: Ambert van Unen <ambertvu@gmail.com>
2024-04-24 13:44:20 +02:00
Andy ButlandandGitHub 52c21b0fca Updates JSON schema for Umbraco 10 with latest references for Forms and Deploy (#15918) 2024-03-20 13:20:40 +01:00
Bjarke Berg 99d5a7e609 Merge remote-tracking branch 'origin/release/10.8' into v10/dev 2024-03-19 08:43:59 +01:00
JeyandBjarke Berg 7e1d1a1968 Merge pull request from GHSA-552f-97wf-pmpq
Co-authored-by: jey <jey@umbraco.dk>
(cherry picked from commit b743f6a2df)
2024-03-18 08:28:25 +01:00
b743f6a2df Merge pull request from GHSA-552f-97wf-pmpq
Co-authored-by: jey <jey@umbraco.dk>
2024-03-18 08:27:41 +01:00
Bjarke Berg a2511ff09b Fixing locking issues for document type saves. (#15854)
* Added  ExecuteNonQuery(DbCommand command) on database to ensure we call OnExecutingCommand and OnExecutedCommand when executing DbCommands

* Added Cache Instructions lock, to avoid deadlocks

* Optimized read locks for nucache when only one content type is rebuilt

* Optimized the SqlServer locks, so only one command is executed (and thereby roundtrip) per lock instead of two

* Avoid breaking changes

* Cosmetic changes

* Take locks if everything is rebuild

* Use same lock in scopes, to avoid potential deadlocks between the two

* Use eager locks in PublishedSnapshotService.cs

* Added timeouts to some of the application locks

* Revert "Use eager locks in PublishedSnapshotService.cs"

This reverts commit 01873aae97.

* Revert "Added Cache Instructions lock, to avoid deadlocks"

This reverts commit e3fca7c12a.

* Use single readlock call to lock many

* Use eager locks for reads

* Eager write locks

* Ignore test of lazy locks

* Unique timeout exception messages

---------

Co-authored-by: kjac <kja@umbraco.dk>
(cherry picked from commit 2c23e67c65)
2024-03-15 14:55:20 +01:00
2c23e67c65 Fixing locking issues for document type saves. (#15854)
* Added  ExecuteNonQuery(DbCommand command) on database to ensure we call OnExecutingCommand and OnExecutedCommand when executing DbCommands

* Added Cache Instructions lock, to avoid deadlocks

* Optimized read locks for nucache when only one content type is rebuilt

* Optimized the SqlServer locks, so only one command is executed (and thereby roundtrip) per lock instead of two

* Avoid breaking changes

* Cosmetic changes

* Take locks if everything is rebuild

* Use same lock in scopes, to avoid potential deadlocks between the two

* Use eager locks in PublishedSnapshotService.cs

* Added timeouts to some of the application locks

* Revert "Use eager locks in PublishedSnapshotService.cs"

This reverts commit 01873aae97.

* Revert "Added Cache Instructions lock, to avoid deadlocks"

This reverts commit e3fca7c12a.

* Use single readlock call to lock many

* Use eager locks for reads

* Eager write locks

* Ignore test of lazy locks

* Unique timeout exception messages

---------

Co-authored-by: kjac <kja@umbraco.dk>
2024-03-15 10:56:02 +01:00
Bjarke Berg 8e837d387d Merge remote-tracking branch 'origin/v10/dev' into release/10.8
# Conflicts:
#	version.json
2024-03-14 14:53:42 +01:00
Bjarke Berg 801fb5f885 Bump version 2024-03-14 14:48:09 +01:00
Bjarke BergandGitHub ed517ecd86 Update Imagesharp (#15885) 2024-03-14 14:31:30 +01:00
Bjarke Berg b5e46ba880 Merge remote-tracking branch 'origin/release/10.8' into v10/dev 2024-02-06 11:07:16 +01:00
Nikolaj GeisleandGitHub cbf9f9bcd1 Merge pull request from GHSA-gvpc-3pj6-4m9w
* Add MarkDownPropertyValueEditor with html sanitizer

* Implement IMarkdownSanitizer.
2024-02-06 09:53:40 +01:00
Bjarke Berg 2735f17ed8 Merge remote-tracking branch 'origin/release/10.8' into v10/dev
# Conflicts:
#	version.json
2024-02-01 11:09:47 +01:00
Ronald BarendseandBjarke Berg e37cf30690 Skip cache refresher operations for content blueprints (#15633)
* Skip cache refresher operations for content blueprints

* Fix JsonPayload deserialization error by adding a default constructor and property initializers

* Obsolete JsonPayload constructor and update usages

(cherry picked from commit 3e28e10cdf)
2024-02-01 10:00:42 +01:00
Bjarke BergandGitHub 118ac8e230 V10+ version of https://github.com/umbraco/Umbraco-CMS/pull/15638 (#15664) 2024-02-01 09:58:42 +01:00
Ronald BarendseandGitHub 3e28e10cdf Skip cache refresher operations for content blueprints (#15633)
* Skip cache refresher operations for content blueprints

* Fix JsonPayload deserialization error by adding a default constructor and property initializers

* Obsolete JsonPayload constructor and update usages
2024-02-01 09:55:09 +01:00
AleksanderandBjarke Berg b18b6cc5e7 Pass cache level to properties when creating published content in nucache
(cherry picked from commit d9d2b66e85)

# Conflicts:
#	src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
(cherry picked from commit 040495f359)
2024-01-31 10:52:01 +01:00
Elitsa MarinovskaandBjarke Berg 7a04baf9ef V10: Pass in variation context to published cache (#15563)
* Make sure that we always have variation context

* Fix references

(cherry picked from commit 57b3a196bf)
2024-01-30 15:57:33 +01:00
Sven GeusensandBjarke Berg bc1ddaed8c Run the same cleanup with scaffolding content as when copying. (#15541)
* Run the same cleanup with scaffolding content as when copying.

- Added a new ContentScaffoldedNotification
- Published the notification when a new scaffold has been created from a blueprint (content template)
- Linked up the ComplextPEContent handler to do the same cleanup for the new notification as when copying.
- registered handlers to the event for blocklist, blockgrid and nested content

* PR pattern matching suggestion

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

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
(cherry picked from commit dff90c6ec0)
2024-01-30 15:54:33 +01:00
Bjarke Berg 860afb35f2 Bump version 2024-01-30 15:53:21 +01:00
Ronald BarendseandGitHub 9799c550f4 Create shadow file systems in configured LocalTempPath (#15637) 2024-01-29 08:48:45 +01:00
Ronald BarendseandGitHub b7e43a8def Get references from macro parameters using IDataValueReferenceFactory (#15625) 2024-01-23 16:08:17 +01:00
Ronald BarendseandGitHub cdbbd6a921 Optimize relation tracking for adding new and keeping existing relations (#15596)
* Include automatic relation type aliases from factory and fix SQL parameter overflow (#15141)

* Include automatic relation type aliases from factory

* Remove unnessecary distinct and fix SQL parameter overflow issue

* Fixed assertions and test distinct aliases

* Simplified collection assertions

* Improve logging of invalid reference relations (#15160)

* Include automatic relation type aliases from factory

* Remove unnessecary distinct and fix SQL parameter overflow issue

* Fixed assertions and test distinct aliases

* Simplified collection assertions

* Improve logging of invalid reference relations

* Always get all automatic relation type aliases

* Do not set relation type alias for unknown entity types

* Get references from recursive (nested/block) properties

* Optimize relation tracking for adding new and keeping existing relations

* Optimize getting references by grouping by property editor alias and avoiding duplicate parsing of the same value
2024-01-22 14:53:20 +01:00
Ronald BarendseandGitHub 5198e7c52d Backport relation tracking fixes and get references from recursive (nested/block) properties (#15593)
* Include automatic relation type aliases from factory and fix SQL parameter overflow (#15141)

* Include automatic relation type aliases from factory

* Remove unnessecary distinct and fix SQL parameter overflow issue

* Fixed assertions and test distinct aliases

* Simplified collection assertions

* Improve logging of invalid reference relations (#15160)

* Include automatic relation type aliases from factory

* Remove unnessecary distinct and fix SQL parameter overflow issue

* Fixed assertions and test distinct aliases

* Simplified collection assertions

* Improve logging of invalid reference relations

* Always get all automatic relation type aliases

* Do not set relation type alias for unknown entity types

* Get references from recursive (nested/block) properties
2024-01-19 20:02:57 +01:00
Andy ButlandandGitHub 49f5d2e2d4 Updates JSON schema for Umbraco 10 to include details of additional configuration introduced in Forms and Deploy. (#15566) 2024-01-12 13:13:20 +01:00
Elitsa MarinovskaandGitHub 57b3a196bf V10: Pass in variation context to published cache (#15563)
* Make sure that we always have variation context

* Fix references
2024-01-11 12:46:31 +01:00
dff90c6ec0 Run the same cleanup with scaffolding content as when copying. (#15541)
* Run the same cleanup with scaffolding content as when copying.

- Added a new ContentScaffoldedNotification
- Published the notification when a new scaffold has been created from a blueprint (content template)
- Linked up the ComplextPEContent handler to do the same cleanup for the new notification as when copying.
- registered handlers to the event for blocklist, blockgrid and nested content

* PR pattern matching suggestion

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

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2024-01-10 12:22:36 +01:00
AleksanderandSebastiaan Janssen 040495f359 Pass cache level to properties when creating published content in nucache
(cherry picked from commit d9d2b66e85)

# Conflicts:
#	src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
2024-01-08 14:42:17 +01:00
Bjarke Berg a2ad95d965 Change content 2023-12-22 13:49:57 +01:00
Bjarke Berg 64f2447c0e Added script to update default version 2023-12-22 13:15:13 +01:00
Bjarke Berg b648126d19 Use wildcard as default valid 2023-12-22 12:59:13 +01:00
812b414d96 Batched more update calls to avoid Sql paramater count error (#15487)
Co-authored-by: Sven Geusens <sge@umbraco.dk>
2023-12-21 09:43:37 +01:00
Bjarke Berg ec91c47158 Merge remote-tracking branch 'origin/release/10.8' into v10/dev
# Conflicts:
#	version.json
2023-12-20 09:51:14 +01:00
Kenn JacobsenandGitHub e04a41be45 Add policies for content template handling (#15482) 2023-12-19 14:18:48 +01:00
Chris Fitz-AvonandBjarke Berg d5156aced2 Remove content section access policy from GetAllLanguages endpoint. #15435 (#15450)
(cherry picked from commit cedfdcc9b2)
2023-12-18 08:25:00 +01:00
Bjarke Berg fa9b0e4718 Bump version 2023-12-18 08:23:34 +01:00
Bjarke Berg 8915064780 Merge remote-tracking branch 'origin/release/10.8' into v10/dev
# Conflicts:
#	version.json
2023-12-13 09:28:19 +01:00
ZeegaanandBjarke Berg 677ff59d04 Loosen up policies so editors can get what they need 2023-12-12 15:46:46 +01:00
Bjarke Berg 1fe2ec08be bump version 2023-12-12 15:35:15 +01:00
Bjarke Berg 5dfa76600a Post fix 2023-12-11 16:32:38 +01:00
Bjarke Berg 83bcd37250 Merge remote-tracking branch 'origin/release/10.8' into v10/dev 2023-12-11 14:37:10 +01:00
Kenn JacobsenandGitHub 4a7ad4a562 Merge pull request from GHSA-v98m-398x-269r 2023-12-11 14:00:23 +01:00
cdd4d2a000 Merge pull request from GHSA-cfr5-7p54-4qg8
* Bump version

* Apply authorization policies to controllers

* Return bad request if we urltracking is disabled

* Apply authorization policies to controllers

* Return bad request if we urltracking is disabled

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Zeegaan <nge@umbraco.dk>
2023-12-11 13:59:59 +01:00
be5a740c96 Merge pull request from GHSA-8qp8-9rpw-j46c
* Added Exception handling and replicated error and info message

* Update auth.resource.js

Fixed the message

* Changed Delay introduction to early phase to avoid repeating code.

---------

Co-authored-by: jey <jey@umbraco.dk>
Co-authored-by: Jey <cyaqublu@gmail.com>
2023-12-11 13:59:35 +01:00
13cc320f19 Merge pull request from GHSA-6324-52pr-h4p5
* Bump version

* Fix https://github.com/umbraco/Umbraco-CMS/security/advisories/GHSA-6324-52pr-h4p5

* Fix https://github.com/umbraco/Umbraco-CMS/security/advisories/GHSA-6324-52pr-h4p5

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Zeegaan <nge@umbraco.dk>
2023-12-11 13:59:03 +01:00
ZeegaanandBjarke Berg b5544aa520 Suppress flow when queueing background threads 2023-12-11 12:43:25 +01:00
Bjarke Berg 9e3f8c7d0c Merge remote-tracking branch 'origin/release/10.8' into v10/dev
# Conflicts:
#	version.json
2023-12-08 13:12:33 +01:00
Bjarke Berg cb76554455 Bugfix: Count existing media correct (#15206)
* https://github.com/umbraco/Umbraco-CMS/issues/15205
Count media correct

* Fix content should be document

(cherry picked from commit d2ff2ea46e)
2023-12-07 09:21:25 +01:00
Jacob Overgaard 37e4d80ce8 Update nightly build (#15359)
* cherry-pick templateParameters from v14/dev

* add isNightly parameter

* remove v9

* add parameter check for myget isNightly
2023-12-05 12:45:23 +01:00
Bjarke Berg d5d4edbe92 Bump Version 2023-11-30 21:51:54 +01:00
Bjarke Berg 8d9343b564 Merge branch 'release/10.8' into v10/dev
# Conflicts:
#	version.json
2023-11-27 18:10:40 +01:00
Bjarke Berg 1ee891c8d5 Merge remote-tracking branch 'origin/release/10.8' into release/10.8 2023-11-27 18:09:41 +01:00
Bjarke Berg bf1a5b9b28 Bump version 2023-11-27 18:09:30 +01:00
Bjarke Berg 696b74cdb6 Merge branch 'release/10.8' into v10/dev 2023-11-27 18:08:30 +01:00
Bjarke BergandGitHub bf8af4588e Centralised NuGet packages (#15293)
* Moved to centralized nuget packages

* Handle JsonSchema

* Change PackageReference to PackageVersion

* Opt out of central package management for mangement api (at least for versions less than 14) and the UI project

* ManagePackageVersionsCentrally = false

* Change folder acceptance tests are build, to avoid reading the test Directory.Packages.props

* Change folder acceptance tests are build, to avoid reading the test Directory.Packages.props

* change working directory

* workingDirectory

* Force the template to use local nuget packages

* Force the template to use local nuget packages

* clean up

* added wait on

* added wait on
2023-11-27 15:18:00 +01:00
Kenn JacobsenandBjarke Berg 4781fb1d8b Ensure content types are updated in ContentStore when a data type changes (#15239) 2023-11-17 10:37:23 +01:00
Bjarke Berg d714741bee Fixed build 2023-11-15 09:53:28 +01:00
Bjarke Berg ea33b73daa Added scope.complete in edge cases. (#15207)
(cherry picked from commit 28e8ac11f0)
2023-11-15 09:46:24 +01:00
Bjarke Berg ba64b46824 Merge remote-tracking branch 'origin/release/10.8' into release/10.8 2023-11-15 09:45:30 +01:00
Bjarke Berg 04e59e9a9d Fixed a couple of occurrences where scopes was auto-completed while modified db state (#14947)
* Fixed a couple of occurrences where scopes was auto-complated while actually modified the state of the database.

* Added temp ctor to fix boot issue

(cherry picked from commit 41f8f03c2c)
2023-11-15 09:45:23 +01:00
Jacob OvergaardandGitHub be39b9c19a V10: Update @umbraco-ui/uui, jQuery, ace-editor, nouislider, spectrum-colorpicker2 (#15197)
* V13: Add support for Node.js 20 (#15022)

* update versionSpec to nodejs 20 and avoid specifying minor and patch versions to improve Azure Pipelines stability

* update engines and package lock files

* update helpful displayNames for npm builds

* add retry to counter nodejs.org timeouts

add retryCountOnTaskFailure as suggested on https://developercommunity.visualstudio.com/t/NodeTool0-task-to-install-nodejs-fails/10455454

* update lockfile

* bump nvm support to latest node.js 20.9 LTS

* build(deps): bump @umbraco-ui/uui from 1.3.0 to 1.5.0

* build(deps): bump public dependencies to latest

* build(deps-dev): bump build dependencies to latest

* build(deps-dev): bump test tools to latest

* build: add script to lint and run --fix

* revert login merge from v13
2023-11-14 13:08:29 +01:00
Bjarke Berg 9a143c0850 Merge remote-tracking branch 'origin/release/10.8' into release/10.8 2023-11-14 11:46:26 +01:00
Bjarke Berg df4cd63962 https://github.com/umbraco/Umbraco-CMS/issues/15195 Fixed issue with media not cached correct (#15196) 2023-11-14 10:59:30 +01:00
Bjarke Berg a17e398f28 Merge remote-tracking branch 'origin/release/10.8' into v10/dev 2023-11-14 09:29:36 +01:00
Bjarke Berg 9af9dd2c02 Merge remote-tracking branch 'origin/release/10.8' into v10/dev 2023-11-14 09:29:09 +01:00
Bjarke BergandGitHub 2266a98fc6 https://github.com/umbraco/Umbraco-CMS/issues/15195 Fixed issue with media not cached correct (#15196) 2023-11-14 09:14:01 +01:00
151fccee97 Performance optimisations related to blocks (and multi url picker) (#15184)
* Added content types to property index value factory, because the deep cloning is too expensive to execute often

* Do not use entityservice in ToEditor, as it goes to the database. We need to use something that is cached.

* Small performance optimization for nested content too

* A little formatting and an obsoletion message

---------

Co-authored-by: kjac <kja@umbraco.dk>
2023-11-14 08:12:05 +01:00
Bjarke Berg abfa07367f Bump version 2023-11-13 18:06:41 +01:00
Bjarke BergandGitHub e5de8bf56b Updated nuget packages (#15192) 2023-11-13 14:59:52 +01:00
Bjarke Berg 00bb5170a0 Merge remote-tracking branch 'origin/v10/dev' into release/10.8
# Conflicts:
#	version.json
2023-11-09 13:53:46 +01:00
919138c299 #14835 Update link on permissionsreport.html (#14872)
* #14835 Update link on permissionsreport.html

Updated link to permissions page to point to 10-LTS documentation

* retrigger checks

---------

Co-authored-by: Rasmus Berntsen <rasmus.berntsen@isobar.com>
2023-11-09 13:53:18 +01:00
Ronald BarendseandGitHub ed65645a33 Add global.json and cleanup UI project file (#14900)
* Add global.json with minimum .NET 6.0.5 SDK version and roll forward policy

* Clean up UI project file
2023-11-09 13:52:21 +01:00
Ronald BarendseandGitHub d0be4ab263 Publish ContentTreeChangeNotification after saving/deleting blueprints (#14898) 2023-11-09 13:51:05 +01:00
Bjarke Berg ddb26632f9 Bump to rc 2023-11-09 13:38:34 +01:00
Bjarke Berg d5b1f5875b Bump version 2023-11-09 13:38:13 +01:00
Bjarke Berg f8c1a560ff Bump version 2023-11-09 13:36:57 +01:00
9e5ff305c8 Batched bulk WhereIn query to avoid To mana paramaters error (#15004)
Co-authored-by: Sven Geusens <sge@umbraco.dk>
2023-11-07 10:49:46 +01:00
Elitsa MarinovskaandGitHub ffaa77eb25 Update correct action (#15142) 2023-11-06 17:54:16 +01:00
Kenn JacobsenandGitHub 6777145079 Ensure that missing access rules do not break the site (#15081) 2023-11-02 08:45:27 +01:00
Elitsa MarinovskaandGitHub 92af8ac881 Check content permissions before performing action (#15043)
* Setting actionContext.Result when authz wasn't successful

* Taking into account permissions when it is a new node

* Cleanup

* Passing nodeId as path when new item
2023-11-01 11:30:32 +01:00
Lucas Bach BisgaardandSebastiaan Janssen bde88caf14 Fixes #14351 - Using Fallback to default langauge on a specific item changes the whole VariationContext (#14620)
(cherry picked from commit 738749b705)
2023-10-26 11:47:44 +02:00
Bjarke BergandGitHub f4ac6cdded Added "pr: none" to nightly trigger (#15044) 2023-10-26 09:01:25 +02:00
Elitsa MarinovskaandGitHub 132935c2d7 Handle corrupted index (#14889) 2023-10-09 10:19:36 +02:00
Kenn JacobsenandGitHub 04b05714e5 Add notification state to "final" published notification when publishing with descendants (#14922)
* Revert "V10/bugfix/14543 publish descendants (#14763)"

This reverts commit f750bca453.

* Add notification state to "final" published notification when publishing with descendants
2023-10-05 14:39:00 +02:00
Kenn JacobsenandGitHub 7be1852827 Make sure to sign out external users if an error occurs during sign-in (#14867) 2023-09-28 13:25:11 +02:00
Andreas ZerbstandGitHub 158cef1692 V10 fixed up failing acceptance test (#14829)
* Updated playwright, our builders, and our helpers

* Updated the use of our testhelper methods, we removed the need for sending umbracoApi through the methods as a parameter since it was unnecessary

* Added a timeout

* Updated successNotifications and added a locator

* Removed timeout from successNotification

* Added additional timeouts and updated old locators which were failing

* Updated locator so it only finds one element

* Removed timeouts and added a waitForTimeout

* Removed timeouts

* Removed timeouts

* Added timeouts

* Updated locator

* Added a timeout

* Updated locators for clicking button

* Changed locator for adding the image, sometimes the old locator would find 2 locators
2023-09-20 12:15:24 +02:00
Bjarke Berg 1a4f3981eb Merge remote-tracking branch 'origin/v10/dev' into v10/dev 2023-09-20 09:42:28 +02:00
Bjarke Berg 7765f98ee1 Added random delay in forgot password to lessen the risk the response times can be use to determine whether the mail exist or not. 2023-09-20 09:02:32 +02:00
6b72880c0b fix: ensured that allowed user name characters are supported in backoffice users as well (#14810) (#14825)
Co-authored-by: Arkadiusz Biel <bielu@bielu.com.pl>
2023-09-19 09:01:35 +02:00
Andy ButlandandGitHub d9f3936a43 Updated dependencies to Forms and Deploy for JSON schema generation. (#14826) 2023-09-19 08:54:41 +02:00
bf09c87f87 Mention MultiUrlPickerValueConverter instead (#14818)
Co-authored-by: Zeegaan <nge@umbraco.dk>
2023-09-18 13:48:11 +02:00
Sven GeusensandGitHub ec2134dea0 Disabled nighly build branch trigger (#14807) 2023-09-15 10:02:29 +02:00
Nikolaj GeisleandGitHub 8ccc2faee0 Add UsePagedSqlQuery setting to NucacheSettings (#14806)
* imlement UsePagedSqlQuery

* Remove unneccesary spacing
2023-09-14 20:11:08 +02:00
Jacob Overgaard 0a4cc27a5d V12: Property editors should only contain layout when in preview mode (#14796)
* Revert "Make sure the property editor layout is contained within its container (prevent Z-index bleed-through) (#13583)"

This reverts commit 8463d906a7.

* remove seemingly unused attribute readonly

* contain everything with contain:layout

this is set if the property editor is in preview mode to make sure all overlays work

* Revert "remove seemingly unused attribute readonly"

This reverts commit 7a2743e8f6.
2023-09-14 13:19:36 +02:00
c8e054baa2 V10/feature/pipeline test filters (#14766)
* Add configuration/code to not run certain tests based on variables/release builds

* Applied longrunning testAttribute to the worst offenders (>200ms on my machine)

* Fix yaml notation

* split up windows/non windows test runs

* Added supression for moved tests

* Fix yaml validation issues

* Change yaml string parameter null value to empty string

* Convert empty strings to whitespace strings

* Rename and cleanup some paramater to better reflect why we use them

* Nightly build test

* Change nightly build authentication type

* template paramater fix

* Update nightly pipeline name

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
2023-09-12 14:16:27 +02:00
Bjarke Berg 95ffdae475 Merge remote-tracking branch 'origin/v10/dev' into v10/dev 2023-09-12 09:43:52 +02:00
Andreas ZerbstandJacob Overgaard ca8ac21f4b Added test for checking if settings model contains default value (#14513)
* Added test for checking if a settings model contains default value.
Fixed tests

* Bumped version

* Moved createSliderWithDefaultValue function to our testHelpers. Removed the use of count in test since there was only one
2023-09-11 15:13:08 +02:00
Jacob Overgaard f85a0d1715 Render all tabs in the background to ensure they register their initial state on any $scope.model in the stack. (#14493)
Sometimes a tab, e.g. a "block list settings model" might register some default values, but if the user never clicks on the tab then they are not registered due to how AngularJS renders views with "ng-if".
2023-09-11 15:12:48 +02:00
Andreas ZerbstandGitHub 2fc29e638c Limbo models builder changes the content to element when using Compositions (#14776) 2023-09-07 17:18:57 +02:00
Andreas ZerbstandGitHub 56486b1ace V10 Cherrypicked issuu RTE embed (#14777)
* Added a UserAgent to header

* Added the use of iframe. This allows us to see the issuu embed in the RTE
2023-09-07 16:28:11 +02:00
Bjarke Berg fd043f6ecf Merge remote-tracking branch 'origin/v10/dev' into release/10.7 2023-09-05 10:19:13 +02:00
Bjarke Berg 39a102ed8a rollback version 2023-09-05 10:14:21 +02:00
f750bca453 V10/bugfix/14543 publish descendants (#14763)
* WIP: Fix publish descendants and related notifications

* Removed related entitities from publish notification

* Fixed root not being saved when publishingWithDescendants

* Updated integrationtests to reflect the update view on when to save the root when its part of a branch

* PR formatting fix

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

* PR Cleanup

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

* Spicing up the codebase with some PR pattern matching

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

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2023-09-05 10:14:06 +02:00
Bjarke Berg 7e36203f60 Merge remote-tracking branch 'origin/release/10.7' into v10/dev
# Conflicts:
#	version.json
2023-09-04 10:24:22 +02:00
Bjarke Berg 334dff8645 bump version number 2023-09-04 10:23:44 +02:00
234 changed files with 5575 additions and 3313 deletions
+1
View File
@@ -49,4 +49,5 @@
<PropertyGroup>
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
</PropertyGroup>
</Project>
+102 -54
View File
@@ -21,9 +21,33 @@ parameters:
displayName: Upload API docs
type: boolean
default: false
- name: forceReleaseTestFilter
displayName: Force to use the release test filters
type: boolean
default: false
- name: integrationNonReleaseTestFilter
displayName: TestFilter used for non-release type builds
type: string
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
- name: integrationReleaseTestFilter
displayName: TestFilter used for release type builds
type: string
default: ' '
- name: nonWindowsIntegrationNonReleaseTestFilter
displayName: TestFilter used for non-release type builds on non windows agents
type: string
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
- name: nonWindowsIntegrationReleaseTestFilter
displayName: TestFilter used for release type builds on non windows agents
type: string
default: ' '
- name: isNightly
displayName: 'Is nightly build (used for MyGet feed)'
type: boolean
default: false
variables:
nodeVersion: 14.18.1
nodeVersion: 20
dotnetVersion: 6.x
dotnetIncludePreviewVersions: false
solution: umbraco.sln
@@ -49,6 +73,7 @@ stages:
steps:
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: Cache@2
@@ -61,9 +86,9 @@ stages:
path: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: src/Umbraco.Web.UI.Client
displayName: Run npm ci
displayName: Run npm ci (Backoffice)
- task: gulp@0
displayName: Run gulp build
displayName: Run gulp build (Backoffice)
inputs:
gulpFile: src/Umbraco.Web.UI.Client/gulpfile.js
targets: coreBuild
@@ -80,44 +105,12 @@ stages:
command: restore
projects: $(solution)
- task: DotNetCoreCLI@2
displayName: Run dotnet build
name: build
displayName: Run dotnet build and generate NuGet packages
inputs:
command: build
projects: $(solution)
arguments: '--configuration $(buildConfiguration) --no-restore -p:ContinuousIntegrationBuild=true'
- script: |
version="$(Build.BuildNumber)"
echo "varsion: $version"
major="$(echo $version | cut -d '.' -f 1)"
echo "major version: $major"
echo "##vso[task.setvariable variable=majorVersion;isOutput=true]$major"
displayName: Set major version
name: determineMajorVersion
- task: PowerShell@2
displayName: Prepare nupkg
inputs:
targetType: inline
script: |
$umbracoVersion = "$(Build.BuildNumber)" -replace "\+",".g"
$templatePaths = Get-ChildItem 'templates/**/.template.config/template.json'
foreach ($templatePath in $templatePaths) {
$a = Get-Content $templatePath -Raw | ConvertFrom-Json
if ($a.symbols -and $a.symbols.UmbracoVersion) {
$a.symbols.UmbracoVersion.defaultValue = $umbracoVersion
$a | ConvertTo-Json -Depth 32 | Set-Content $templatePath
}
}
dotnet pack $(solution) --configuration $(buildConfiguration) -p:BuildProjectReferences=false --output $(Build.ArtifactStagingDirectory)/nupkg
- script: |
sha="$(Build.SourceVersion)"
sha=${sha:0:7}
buildnumber="$(Build.BuildNumber)_$(Build.BuildId)_$sha"
echo "##vso[build.updatebuildnumber]$buildnumber"
displayName: Update build number
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
- task: PublishPipelineArtifact@1
displayName: Publish nupkg
inputs:
@@ -130,11 +123,11 @@ stages:
artifactName: build_output
- stage: Build_Docs
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.buildApiDocs}}))
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.buildApiDocs}}))
displayName: Prepare API Documentation
dependsOn: Build
variables:
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
jobs:
# C# API Reference
- job:
@@ -188,9 +181,10 @@ stages:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: Use Node.js 10.15.0
displayName: Use Node.js 10.15.x
retryCountOnTaskFailure: 3
inputs:
versionSpec: 10.15.0 # Won't work with higher versions
versionSpec: 10.15.x # Won't work with higher versions
- script: |
npm ci --no-fund --no-audit --prefer-offline
npx gulp docs
@@ -259,6 +253,8 @@ stages:
- stage: Integration
displayName: Integration Tests
dependsOn: Build
variables:
releaseTestFilter: eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True')
jobs:
# Integration Tests (SQLite)
- job:
@@ -286,12 +282,30 @@ stages:
performMultiLevelLookup: true
includePreviewVersions: $(dotnetIncludePreviewVersions)
- task: DotNetCoreCLI@2
displayName: Run dotnet test
displayName: Run dotnet test Windows
condition: eq(variables['Agent.OS'],'Windows_NT')
inputs:
command: test
projects: '**/*.Tests.Integration.csproj'
arguments: '--configuration $(buildConfiguration) --no-build'
testRunTitle: Integration Tests SQLite - $(Agent.OS)
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
${{ else }}:
arguments: '--configuration $(buildConfiguration) ${{parameters.integrationNonReleaseTestFilter}}'
env:
Tests__Database__DatabaseType: 'Sqlite'
Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
- task: DotNetCoreCLI@2
displayName: Run dotnet test Non Windows
condition: ne(variables['Agent.OS'],'Windows_NT')
inputs:
command: test
projects: '**/*.Tests.Integration.csproj'
testRunTitle: Integration Tests SQLite - $(Agent.OS)
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
${{ else }}:
arguments: '--configuration $(buildConfiguration) ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
env:
Tests__Database__DatabaseType: 'Sqlite'
Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
@@ -299,7 +313,7 @@ stages:
# Integration Tests (SQL Server)
- job:
timeoutInMinutes: 120
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.sqlServerIntegrationTests}})
condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}})
displayName: Integration Tests (SQL Server)
strategy:
matrix:
@@ -310,7 +324,7 @@ stages:
Linux:
vmImage: 'ubuntu-latest'
testDb: SqlServer
connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);'
connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=true'
pool:
vmImage: $(vmImage)
variables:
@@ -321,6 +335,11 @@ stages:
inputs:
artifact: build_output
path: $(Build.SourcesDirectory)
- task: UseDotNet@2
displayName: Use .NET $(dotnetVersion)
inputs:
version: $(dotnetVersion)
includePreviewVersions: $(dotnetIncludePreviewVersions)
- powershell: sqllocaldb start mssqllocaldb
displayName: Start localdb (Windows only)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
@@ -328,12 +347,31 @@ stages:
displayName: Start SQL Server (Linux only)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- task: DotNetCoreCLI@2
displayName: Run dotnet test
displayName: Run dotnet test Windows
condition: eq(variables['Agent.OS'],'Windows_NT')
inputs:
command: test
projects: '**/*.Tests.Integration.csproj'
arguments: '--configuration $(buildConfiguration) --no-build'
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
${{ else }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
env:
Tests__Database__DatabaseType: $(testDb)
Tests__Database__SQLServerMasterConnectionString: $(connectionString)
Umbraco__CMS__Global__MainDomLock: 'SqlMainDomLock'
- task: DotNetCoreCLI@2
displayName: Run dotnet test NonWindows
condition: ne(variables['Agent.OS'],'Windows_NT')
inputs:
command: test
projects: '**/*.Tests.Integration.csproj'
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
${{ else }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
env:
Tests__Database__DatabaseType: $(testDb)
Tests__Database__SQLServerMasterConnectionString: $(connectionString)
@@ -361,6 +399,7 @@ stages:
dockerImageName: umbraco-linux
Windows:
vmImage: 'windows-latest'
DOTNET_GENERATE_ASPNET_CERTIFICATE: true # Automatically generate HTTPS development certificate on Windows
# Enable console logging in Release mode
Serilog__WriteTo__0__Name: Async
Serilog__WriteTo__0__Args__configure__0__Name: Console
@@ -385,6 +424,7 @@ stages:
path: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/misc/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: Cache@2
@@ -450,6 +490,8 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
- pwsh: |
docker logs $(dockerImageName) > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1
docker stop $(dockerImageName)
@@ -488,9 +530,11 @@ stages:
- Unit
- Integration
# - E2E # TODO: Enable when stable.
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.myGetDeploy}}))
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
@@ -505,16 +549,20 @@ stages:
command: 'push'
packagesToPush: $(Build.ArtifactStagingDirectory)/**/*.nupkg
nuGetFeedType: 'external'
publishFeedCredentials: 'MyGet - Pre-releases'
${{ if eq(parameters.isNightly, true) }}:
publishFeedCredentials: 'MyGet - Umbraco Nightly'
${{ else }}:
publishFeedCredentials: 'MyGet - Pre-releases'
- stage: Deploy_NuGet
displayName: NuGet release
dependsOn:
- Deploy_MyGet
- Build_Docs
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.nuGetDeploy}}))
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
@@ -535,12 +583,12 @@ stages:
pool:
vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :(
variables:
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
displayName: Upload API Documention
dependsOn:
- Build
- Deploy_NuGet
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.uploadApiDocs}}))
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadApiDocs}}))
jobs:
- job:
displayName: Upload C# Docs
+35
View File
@@ -0,0 +1,35 @@
name: Nightly_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
pr: none
trigger: none
schedules:
- cron: '0 0 * * *'
displayName: Daily midnight build
branches:
include:
- v10/dev
- v12/dev
- v13/dev
- v14/dev
steps:
- checkout: none
- task: TriggerBuild@4
inputs:
definitionIsInCurrentTeamProject: true
buildDefinition: '301'
queueBuildForUserThatTriggeredBuild: true
ignoreSslCertificateErrors: false
useSameSourceVersion: false
useCustomSourceVersion: false
useSameBranch: true
waitForQueuedBuildsToFinish: false
storeInEnvironmentVariable: false
templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true'
authenticationMethod: 'OAuth Token'
enableBuildInQueueCondition: false
dependentOnSuccessfulBuildCondition: false
dependentOnFailedBuildCondition: false
checkbuildsoncurrentbranch: false
failTaskIfConditionsAreNotFulfilled: false
+6
View File
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "6.0.300",
"rollForward": "latestFeature"
}
}
+67
View File
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<NoWarn>NU1507</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
<PackageVersion Include="Examine" Version="3.0.1" />
<PackageVersion Include="Examine.Core" Version="3.0.1" />
<PackageVersion Include="HtmlAgilityPack" Version="1.11.54" />
<PackageVersion Include="IPNetwork2" Version="2.6.618" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.6" />
<PackageVersion Include="MailKit" Version="3.2.0" />
<PackageVersion Include="Markdown" Version="2.2.1" />
<PackageVersion Include="MessagePack" Version="2.5.187" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.24" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.24" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="6.0.24" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.24" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="6.0.24" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="6.0.24" />
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.2.22" />
<PackageVersion Include="MiniProfiler.Shared" Version="4.2.22" />
<PackageVersion Include="ncrontab" Version="3.3.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NPoco.SqlServer" Version="5.3.2" />
<PackageVersion Include="Serilog" Version="2.12.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="5.0.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="2.0.2" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Expressions" Version="3.4.1" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="4.2.0" />
<PackageVersion Include="Serilog.Formatting.Compact" Version="1.1.0" />
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="1.0.5" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="3.4.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.Map" Version="1.0.2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.10" />
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
<PackageVersion Include="Smidge.InMemory" Version="4.3.0" />
<PackageVersion Include="Smidge.Nuglify" Version="4.2.1" />
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="6.0.4" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageVersion Include="System.Runtime.Caching" Version="6.0.0" />
<PackageVersion Include="Umbraco.CSharpTest.Net.Collections" Version="14.906.1403.1085" />
<!-- Add dependencies that we force an update to, even that we do not use them explicitly and they seems to be taken from the framework instead of from Nuget -->
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="6.0.1" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
</Project>
+4 -3
View File
@@ -3,16 +3,17 @@
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<EnablePackageValidation>false</EnablePackageValidation>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="NJsonSchema" Version="10.7.2" />
<PackageReference Include="NJsonSchema" Version="10.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<PackageReference Include="Umbraco.Deploy.Core" Version="10.2.2" />
<PackageReference Include="Umbraco.Forms.Core" Version="10.5.0" />
<PackageReference Include="Umbraco.Deploy.Core" Version="10.4.0" />
<PackageReference Include="Umbraco.Forms.Core" Version="10.5.4" />
</ItemGroup>
</Project>
@@ -4,6 +4,7 @@
<Description>Contains the presentation layer for the Umbraco CMS Management API.</Description>
<IsPackable>false</IsPackable>
<EnablePackageValidation>false</EnablePackageValidation>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
@@ -12,7 +13,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="13.16.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.New.Cms.Core\Umbraco.New.Cms.Core.csproj" />
@@ -20,7 +21,7 @@
<ProjectReference Include="..\Umbraco.New.Cms.Web.Common\Umbraco.New.Cms.Web.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="OpenApi.json" />
</ItemGroup>
@@ -134,9 +134,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
const string query = "SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id";
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
var i = db.ExecuteScalar<int?>(query, new { id = LockId });
// execute the lock timeout query and the actual query in a single server roundtrip
var i = db.ExecuteScalar<int?>($"{lockTimeoutQuery};{query}", new { id = LockId });
if (i == null)
{
@@ -169,9 +170,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
const string query =
@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id";
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
var i = db.Execute(query, new { id = LockId });
// execute the lock timeout query and the actual query in a single server roundtrip
var i = db.Execute($"{lockTimeoutQuery};{query}", new { id = LockId });
if (i == 0)
{
@@ -154,7 +154,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
try
{
var i = command.ExecuteNonQuery();
var i = db.ExecuteNonQuery(command);
if (i == 0)
{
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.5" />
<PackageReference Include="Microsoft.Data.Sqlite" />
</ItemGroup>
<ItemGroup>
+1
View File
@@ -4,6 +4,7 @@
<Description>Installs Umbraco CMS with all default dependencies in your ASP.NET Core project.</Description>
<IncludeBuildOutput>false</IncludeBuildOutput>
<IncludeSymbols>false</IncludeSymbols>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
@@ -84,8 +84,8 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
isolatedCache.ClearOfType<IContent>((k, v) => v.Path?.Contains(pathid) ?? false);
}
// if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove))
// if the item is not a blueprint and is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
if (payload.Blueprint is false && payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove))
{
idsRemoved.Add(payload.Id);
}
@@ -120,7 +120,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
// should rename it, and then, this is only for Deploy, and then, ???
// if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
// ...
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
if (payloads.Any(x => x.Blueprint is false))
{
// Only notify if the payload contains actual (non-blueprint) contents
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
}
base.Refresh(payloads);
}
@@ -157,8 +161,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
}
}
// TODO (V14): Change into a record
public class JsonPayload
{
public JsonPayload()
{ }
[Obsolete("Use the default constructor and property initializers.")]
public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes)
{
Id = id;
@@ -166,11 +175,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
ChangeTypes = changeTypes;
}
public int Id { get; }
public int Id { get; init; }
public Guid? Key { get; }
public Guid? Key { get; init; }
public TreeChangeTypes ChangeTypes { get; }
public TreeChangeTypes ChangeTypes { get; init; }
public bool Blueprint { get; init; }
}
#endregion
@@ -134,8 +134,14 @@ public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase<LanguageC
ContentCacheRefresher.RefreshContentTypes(AppCaches); // we need to evict all IContent items
// now refresh all nucache
ContentCacheRefresher.JsonPayload[] clearContentPayload =
new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
ContentCacheRefresher.JsonPayload[] clearContentPayload = new[]
{
new ContentCacheRefresher.JsonPayload()
{
ChangeTypes = TreeChangeTypes.RefreshAll
}
};
ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, clearContentPayload);
}
+31 -8
View File
@@ -9,6 +9,9 @@ namespace Umbraco.Cms.Core.Cache;
/// </summary>
public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
private static readonly TimeSpan _readLockTimeout = TimeSpan.FromSeconds(5);
private static readonly TimeSpan _writeLockTimeout = TimeSpan.FromSeconds(5);
private readonly ReaderWriterLockSlim _locker = new(LockRecursionPolicy.SupportsRecursion);
private bool _disposedValue;
@@ -33,7 +36,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
Lazy<object?>? result;
try
{
_locker.EnterReadLock();
if (_locker.TryEnterReadLock(_readLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when getting item");
}
result = MemoryCache.Get(key) as Lazy<object?>; // null if key not found
}
finally
@@ -195,7 +201,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
_locker.EnterWriteLock();
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing item");
}
if (MemoryCache[key] == null)
{
return;
@@ -223,8 +232,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
var isInterface = type.IsInterface;
try
{
_locker.EnterWriteLock();
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by type");
}
// ToArray required to remove
foreach (var key in MemoryCache
.Where(x =>
@@ -259,7 +270,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
_locker.EnterWriteLock();
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by generic type");
}
Type typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
@@ -296,7 +310,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
_locker.EnterWriteLock();
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing generic type with predicate");
}
Type typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
@@ -338,7 +355,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
_locker.EnterWriteLock();
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing with prefix");
}
// ToArray required to remove
foreach (var key in MemoryCache
@@ -365,7 +385,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
try
{
_locker.EnterWriteLock();
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cach when clearing by regex");
}
// ToArray required to remove
foreach (var key in MemoryCache
@@ -3,30 +3,26 @@ using System.Collections;
namespace Umbraco.Cms.Core.Composing;
/// <summary>
/// Provides a base class for builder collections.
/// Provides a base class for builder collections.
/// </summary>
/// <typeparam name="TItem">The type of the items.</typeparam>
public abstract class BuilderCollectionBase<TItem> : IBuilderCollection<TItem>
{
private readonly LazyReadOnlyCollection<TItem> _items;
/// Initializes a new instance of the
/// <see cref="BuilderCollectionBase{TItem}" />
/// with items.
/// <summary>
/// Initializes a new instance of the <see cref="BuilderCollectionBase{TItem}" /> with items.
/// </summary>
/// <param name="items">The items.</param>
public BuilderCollectionBase(Func<IEnumerable<TItem>> items) => _items = new LazyReadOnlyCollection<TItem>(items);
public BuilderCollectionBase(Func<IEnumerable<TItem>> items)
=> _items = new LazyReadOnlyCollection<TItem>(items);
/// <inheritdoc />
public int Count => _items.Count;
/// <summary>
/// Gets an enumerator.
/// </summary>
/// <inheritdoc />
public IEnumerator<TItem> GetEnumerator() => _items.GetEnumerator();
/// <summary>
/// Gets an enumerator.
/// </summary>
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
@@ -1,13 +1,16 @@
namespace Umbraco.Cms.Core.Composing;
/// <summary>
/// Represents a builder collection, ie an immutable enumeration of items.
/// Represents a builder collection, ie an immutable enumeration of items.
/// </summary>
/// <typeparam name="TItem">The type of the items.</typeparam>
public interface IBuilderCollection<out TItem> : IEnumerable<TItem>
{
/// <summary>
/// Gets the number of items in the collection.
/// Gets the number of items in the collection.
/// </summary>
/// <value>
/// The count.
/// </value>
int Count { get; }
}
@@ -14,6 +14,7 @@ public class NuCacheSettings
internal const string StaticNuCacheSerializerType = "MessagePack";
internal const int StaticSqlPageSize = 1000;
internal const int StaticKitBatchSize = 1;
internal const bool StaticUsePagedSqlQuery = true;
/// <summary>
/// Gets or sets a value defining the BTree block size.
@@ -40,4 +41,7 @@ public class NuCacheSettings
public int KitBatchSize { get; set; } = StaticKitBatchSize;
public bool UnPublishedContentCompression { get; set; } = false;
[DefaultValue(StaticUsePagedSqlQuery)]
public bool UsePagedSqlQuery { get; set; } = true;
}
@@ -2,6 +2,7 @@
// See LICENSE for more details.
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace Umbraco.Cms.Core.Configuration.Models;
@@ -24,6 +25,8 @@ public class SecuritySettings
internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60;
internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60;
private const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
private const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
/// <summary>
/// Gets or sets a value indicating whether to keep the user logged in.
@@ -109,4 +112,26 @@ public class SecuritySettings
[Obsolete("Use ContentSettings.AllowEditFromInvariant instead")]
[DefaultValue(StaticAllowEditInvariantFromNonDefault)]
public bool AllowEditInvariantFromNonDefault { get; set; } = StaticAllowEditInvariantFromNonDefault;
/// <summary>
/// Gets or sets the default duration (in milliseconds) of failed login attempts.
/// </summary>
/// <value>
/// The default duration (in milliseconds) of failed login attempts.
/// </value>
/// <remarks>
/// The user login endpoint ensures that failed login attempts take at least as long as the average successful login.
/// However, if no successful logins have occurred, this value is used as the default duration.
/// </remarks>
[DefaultValue(StaticUserDefaultFailedLoginDurationInMilliseconds)]
public long UserDefaultFailedLoginDurationInMilliseconds { get; set; } = StaticUserDefaultFailedLoginDurationInMilliseconds;
/// <summary>
/// Gets or sets the minimum duration (in milliseconds) of failed login attempts.
/// </summary>
/// <value>
/// The minimum duration (in milliseconds) of failed login attempts.
/// </value>
[DefaultValue(StaticUserMinimumFailedLoginDurationInMilliseconds)]
public long UserMinimumFailedLoginDurationInMilliseconds { get; set; } = StaticUserMinimumFailedLoginDurationInMilliseconds;
}
@@ -318,8 +318,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton<ConflictingPackageData>();
Services.AddSingleton<CompiledPackageXmlParser>();
// Register a noop IHtmlSanitizer to be replaced
// Register a noop IHtmlSanitizer & IMarkdownSanitizer to be replaced
Services.AddUnique<IHtmlSanitizer, NoopHtmlSanitizer>();
Services.AddUnique<IMarkdownSanitizer, NoopMarkdownSanitizer>();
Services.AddUnique<IPropertyTypeUsageService, PropertyTypeUsageService>();
Services.AddUnique<IDataTypeUsageService, DataTypeUsageService>();
@@ -134,27 +134,6 @@ public static class PublishedElementExtensions
#endregion
#region CheckVariation
/// <summary>
/// Method to check if VariationContext culture differs from culture parameter, if so it will update the VariationContext for the PublishedValueFallback.
/// </summary>
/// <param name="publishedValueFallback">The requested PublishedValueFallback.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <returns></returns>
private static void EventuallyUpdateVariationContext(IPublishedValueFallback publishedValueFallback, string? culture, string? segment)
{
IVariationContextAccessor? variationContextAccessor = publishedValueFallback.VariationContextAccessor;
//If there is a difference in requested culture and the culture that is set in the VariationContext, it will pick wrong localized content.
//This happens for example using links to localized content in a RichText Editor.
if (!string.IsNullOrEmpty(culture) && variationContextAccessor?.VariationContext?.Culture != culture)
{
variationContextAccessor!.VariationContext = new VariationContext(culture, segment);
}
}
#endregion
#region Value<T>
/// <summary>
@@ -195,8 +174,6 @@ public static class PublishedElementExtensions
{
IPublishedProperty? property = content.GetProperty(alias);
EventuallyUpdateVariationContext(publishedValueFallback, culture, segment);
// if we have a property, and it has a value, return that value
if (property != null && property.HasValue(culture, segment))
{
+1 -1
View File
@@ -358,7 +358,7 @@ namespace Umbraco.Cms.Core.IO
// nothing prevents us to reach the file, security-wise, yet it is outside
// this filesystem's root - throw
throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
throw new UnauthorizedAccessException($"Requested path {originalPath} is outside this filesystem's root.");
}
/// <summary>
+19 -19
View File
@@ -7,19 +7,21 @@ namespace Umbraco.Cms.Core.IO;
internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs";
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IIOHelper _ioHelper;
private const string ShadowFsPath = "ShadowFs";
private readonly Func<bool?>? _isScoped;
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILoggerFactory _loggerFactory;
private readonly string _shadowPath;
private readonly Func<bool?>? _isScoped;
private string? _shadowDir;
private ShadowFileSystem? _shadowFileSystem;
public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, string shadowPath, Func<bool?>? isScoped = null)
{
InnerFileSystem = innerFileSystem;
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
_loggerFactory = loggerFactory;
@@ -35,18 +37,19 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
get
{
if (_isScoped is not null && _shadowFileSystem is not null)
Func<bool?>? isScoped = _isScoped;
if (isScoped is not null && _shadowFileSystem is not null)
{
var isScoped = _isScoped!();
bool? scoped = isScoped();
// if the filesystem is created *after* shadowing starts, it won't be shadowing
// better not ignore that situation and raised a meaningful (?) exception
if (isScoped.HasValue && isScoped.Value && _shadowFileSystem == null)
// better not ignore that situation and raise a meaningful (?) exception
if (scoped.HasValue && scoped.Value && _shadowFileSystem == null)
{
throw new Exception("The filesystems are shadowing, but this filesystem is not.");
}
return isScoped.HasValue && isScoped.Value
return scoped.HasValue && scoped.Value
? _shadowFileSystem
: InnerFileSystem;
}
@@ -56,8 +59,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
}
/// <inheritdoc />
public IFileProvider? Create() =>
InnerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null;
public IFileProvider? Create() => InnerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null;
public IEnumerable<string> GetDirectories(string path) => FileSystem.GetDirectories(path);
@@ -69,8 +71,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
public void AddFile(string path, Stream stream) => FileSystem.AddFile(path, stream);
public void AddFile(string path, Stream stream, bool overrideExisting) =>
FileSystem.AddFile(path, stream, overrideExisting);
public void AddFile(string path, Stream stream, bool overrideExisting) => FileSystem.AddFile(path, stream, overrideExisting);
public IEnumerable<string> GetFiles(string path) => FileSystem.GetFiles(path);
@@ -107,8 +108,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength);
var virt = ShadowFsPath + "/" + id;
var shadowDir = hostingEnvironment.MapPathContentRoot(virt);
var shadowDir = Path.Combine(hostingEnvironment.LocalTempPath, ShadowFsPath, id);
if (Directory.Exists(shadowDir))
{
continue;
@@ -129,10 +129,10 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
// on ShadowFileSystemsScope.None - and if None is false then we should be running
// in a single thread anyways
var virt = Path.Combine(ShadowFsPath, id, _shadowPath);
_shadowDir = _hostingEnvironment.MapPathContentRoot(virt);
var rootUrl = Path.Combine(ShadowFsPath, id, _shadowPath);
_shadowDir = Path.Combine(_hostingEnvironment.LocalTempPath, rootUrl);
Directory.CreateDirectory(_shadowDir);
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, _hostingEnvironment.ToAbsolute(virt));
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, rootUrl);
_shadowFileSystem = new ShadowFileSystem(InnerFileSystem, tempfs);
}
@@ -160,7 +160,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
// shadowPath make be path/to/dir, remove each
dir = dir!.Replace('/', Path.DirectorySeparatorChar);
var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length;
var min = Path.Combine(_hostingEnvironment.LocalTempPath, ShadowFsPath).Length;
var pos = dir.LastIndexOf(Path.DirectorySeparatorChar);
while (pos > min)
{
@@ -19,6 +19,9 @@ public class Issuu : EmbedProviderBase
public override Dictionary<string, string> RequestParams => new()
{
// ApiUrl/?iframe=true
{ "iframe", "true" },
// ApiUrl/?format=xml
{ "format", "xml" },
};
@@ -55,11 +55,12 @@ public abstract class OEmbedProviderBase : IEmbedProvider
if (_httpClient == null)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Umbraco-CMS");
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
HttpResponseMessage response = _httpClient.SendAsync(request).Result;
HttpResponseMessage response = _httpClient.SendAsync(request).GetAwaiter().GetResult();
return response.Content.ReadAsStringAsync().Result;
}
}
@@ -1,49 +1,88 @@
namespace Umbraco.Cms.Core.Models.Editors;
/// <summary>
/// Used to track reference to other entities in a property value
/// Used to track a reference to another entity in a property value.
/// </summary>
public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
{
private static readonly UmbracoEntityReference _empty = new(UnknownTypeUdi.Instance, string.Empty);
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoEntityReference" /> struct.
/// </summary>
/// <param name="udi">The UDI.</param>
/// <param name="relationTypeAlias">The relation type alias.</param>
public UmbracoEntityReference(Udi udi, string relationTypeAlias)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
}
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoEntityReference" /> struct for a document or media item.
/// </summary>
/// <param name="udi">The UDI.</param>
public UmbracoEntityReference(Udi udi)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
switch (udi.EntityType)
{
case Constants.UdiEntityType.Document:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
break;
case Constants.UdiEntityType.Media:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
break;
default:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
// No relation type alias convention for this entity type, so leave it empty
RelationTypeAlias = string.Empty;
break;
}
}
/// <summary>
/// Gets the UDI.
/// </summary>
/// <value>
/// The UDI.
/// </value>
public Udi Udi { get; }
public static UmbracoEntityReference Empty() => _empty;
public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
/// <summary>
/// Gets the relation type alias.
/// </summary>
/// <value>
/// The relation type alias.
/// </value>
public string RelationTypeAlias { get; }
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right);
/// <summary>
/// Gets an empty reference.
/// </summary>
/// <returns>
/// An empty reference.
/// </returns>
public static UmbracoEntityReference Empty() => _empty;
/// <summary>
/// Determines whether the specified reference is empty.
/// </summary>
/// <param name="reference">The reference.</param>
/// <returns>
/// <c>true</c> if the specified reference is empty; otherwise, <c>false</c>.
/// </returns>
public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
/// <inheritdoc />
public override bool Equals(object? obj) => obj is UmbracoEntityReference reference && Equals(reference);
/// <inheritdoc />
public bool Equals(UmbracoEntityReference other) =>
EqualityComparer<Udi>.Default.Equals(Udi, other.Udi) &&
RelationTypeAlias == other.RelationTypeAlias;
/// <inheritdoc />
public override int GetHashCode()
{
var hashCode = -487348478;
@@ -52,5 +91,9 @@ public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
return hashCode;
}
/// <inheritdoc />
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right);
/// <inheritdoc />
public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right) => !(left == right);
}
@@ -5,6 +5,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent;
/// </summary>
public interface IPublishedValueFallback
{
[Obsolete("Scheduled for removal in v14")]
/// <summary>
/// VariationContextAccessor that is not required to be implemented, therefore throws NotImplementedException as default.
/// </summary>
@@ -20,6 +20,7 @@ public class PublishedValueFallback : IPublishedValueFallback
_variationContextAccessor = variationContextAccessor;
}
[Obsolete("Scheduled for removal in v14")]
public IVariationContextAccessor VariationContextAccessor { get { return _variationContextAccessor; } }
/// <inheritdoc />
@@ -0,0 +1,18 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Notifications;
/// <summary>
/// Notification that is send out when a Content item has been scaffolded from an original item and basic cleaning has been performed
/// </summary>
public sealed class ContentScaffoldedNotification : ScaffoldedNotification<IContent>
{
public ContentScaffoldedNotification(IContent original, IContent scaffold, int parentId, EventMessages messages)
: base(original, scaffold, parentId, messages)
{
}
}
@@ -0,0 +1,23 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Events;
namespace Umbraco.Cms.Core.Notifications;
public abstract class ScaffoldedNotification<T> : CancelableObjectNotification<T>
where T : class
{
protected ScaffoldedNotification(T original, T scaffold, int parentId, EventMessages messages)
: base(original, messages)
{
Scaffold = scaffold;
ParentId = parentId;
}
public T Original => Target;
public T Scaffold { get; }
public int ParentId { get; }
}
@@ -4,64 +4,156 @@ using Umbraco.Cms.Core.Models.Editors;
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Provides a builder collection for <see cref="IDataValueReferenceFactory" /> items.
/// </summary>
public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataValueReferenceFactory>
{
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
: base(items)
{
}
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
public IEnumerable<UmbracoEntityReference> GetAllReferences(
IPropertyCollection properties,
PropertyEditorCollection propertyEditors)
{
var trackedRelations = new HashSet<UmbracoEntityReference>();
foreach (IProperty p in properties)
/// <summary>
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
/// </summary>
/// <param name="items">The items.</param>
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
: base(items)
{ }
/// <summary>
/// Gets all unique references from the specified properties.
/// </summary>
/// <param name="properties">The properties.</param>
/// <param name="propertyEditors">The property editors.</param>
/// <returns>
/// The unique references from the specified properties.
/// </returns>
public ISet<UmbracoEntityReference> GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
{
var references = new HashSet<UmbracoEntityReference>();
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
{
if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor? editor))
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
{
continue;
}
// TODO: We will need to change this once we support tracking via variants/segments
// for now, we are tracking values from ALL variants
foreach (IPropertyValue propertyVal in p.Values)
// Use distinct values to avoid duplicate parsing of the same value
var values = new HashSet<object?>(properties.Count);
foreach (IPropertyValue propertyValue in propertyValuesByPropertyEditorAlias.SelectMany(x => x))
{
var val = propertyVal.EditedValue;
values.Add(propertyValue.EditedValue);
values.Add(propertyValue.PublishedValue);
}
IDataValueEditor? valueEditor = editor?.GetValueEditor();
if (valueEditor is IDataValueReference reference)
{
IEnumerable<UmbracoEntityReference> refs = reference.GetReferences(val);
foreach (UmbracoEntityReference r in refs)
{
trackedRelations.Add(r);
}
}
references.UnionWith(GetReferences(dataEditor, values));
}
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
// Allows developers to add support for references by a
// package /property editor that did not implement IDataValueReference themselves
foreach (IDataValueReferenceFactory item in this)
{
// Check if this value reference is for this datatype/editor
// Then call it's GetReferences method - to see if the value stored
// in the dataeditor/property has referecnes to media/content items
if (item.IsForEditor(editor))
{
foreach (UmbracoEntityReference r in item.GetDataValueReference().GetReferences(val))
{
trackedRelations.Add(r);
}
}
}
return references;
}
/// <summary>
/// Gets the references.
/// </summary>
/// <param name="dataEditor">The data editor.</param>
/// <param name="values">The values.</param>
/// <returns>
/// The references.
/// </returns>
public IEnumerable<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, params object?[] values)
=> GetReferences(dataEditor, (IEnumerable<object?>)values);
/// <summary>
/// Gets the references.
/// </summary>
/// <param name="dataEditor">The data editor.</param>
/// <param name="values">The values.</param>
/// <returns>
/// The references.
/// </returns>
public IEnumerable<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values)
{
// TODO: We will need to change this once we support tracking via variants/segments
// for now, we are tracking values from ALL variants
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
{
foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
{
yield return reference;
}
}
return trackedRelations;
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
// Allows developers to add support for references by a
// package /property editor that did not implement IDataValueReference themselves
foreach (IDataValueReferenceFactory dataValueReferenceFactory in this)
{
// Check if this value reference is for this datatype/editor
// Then call it's GetReferences method - to see if the value stored
// in the dataeditor/property has references to media/content items
if (dataValueReferenceFactory.IsForEditor(dataEditor))
{
IDataValueReference factoryDataValueReference = dataValueReferenceFactory.GetDataValueReference();
foreach (UmbracoEntityReference reference in values.SelectMany(factoryDataValueReference.GetReferences))
{
yield return reference;
}
}
}
}
/// <summary>
/// Gets all relation type aliases that are automatically tracked.
/// </summary>
/// <param name="propertyEditors">The property editors.</param>
/// <returns>
/// All relation type aliases that are automatically tracked.
/// </returns>
public ISet<string> GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
{
// Always add default automatic relation types
var automaticRelationTypeAliases = new HashSet<string>(Constants.Conventions.RelationTypes.AutomaticRelationTypes);
// Add relation types for all property editors
foreach (IDataEditor dataEditor in propertyEditors)
{
automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor));
}
return automaticRelationTypeAliases;
}
/// <summary>
/// Gets the automatic relation types aliases.
/// </summary>
/// <param name="dataEditor">The data editor.</param>
/// <returns>
/// The automatic relation types aliases.
/// </returns>
public IEnumerable<string> GetAutomaticRelationTypesAliases(IDataEditor dataEditor)
{
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
{
// Return custom relation types from value editor implementation
foreach (var alias in dataValueReference.GetAutomaticRelationTypesAliases())
{
yield return alias;
}
}
foreach (IDataValueReferenceFactory dataValueReferenceFactory in this)
{
if (dataValueReferenceFactory.IsForEditor(dataEditor))
{
// Return custom relation types from factory
foreach (var alias in dataValueReferenceFactory.GetDataValueReference().GetAutomaticRelationTypesAliases())
{
yield return alias;
}
}
}
}
}
@@ -9,15 +9,22 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// </summary>
public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory
{
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published, IEnumerable<string> availableCultures)
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published,
IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
{
yield return new KeyValuePair<string, IEnumerable<object?>>(
property.Alias,
property.GetValue(culture, segment, published).Yield());
}
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
/// <inheritdoc />
[Obsolete("Use the non-obsolete overload, scheduled for removal in v14")]
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture,
string? segment, bool published, IEnumerable<string> availableCultures)
=> GetIndexValues(property, culture, segment, published, availableCultures,
new Dictionary<Guid, IContentType>());
[Obsolete("Use the non-obsolete overload, scheduled for removal in v14")]
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)
=> GetIndexValues(property, culture, segment, published, Enumerable.Empty<string>());
=> GetIndexValues(property, culture, segment, published, Enumerable.Empty<string>(), new Dictionary<Guid, IContentType>());
}
@@ -16,11 +16,10 @@ public class DefaultPropertyValueConverterAttribute : Attribute
/// a DefaultPropertyValueConverter can be more specific than another one.
/// </summary>
/// <remarks>
/// An example where this is useful is that both the RelatedLiksEditorValueConverter and the JsonValueConverter
/// An example where this is useful is that both the MultiUrlPickerValueConverter and the JsonValueConverter
/// will be returned as value converters for the Related Links Property editor, however the JsonValueConverter
/// is a very generic converter and the RelatedLiksEditorValueConverter is more specific than it, so the
/// RelatedLiksEditorValueConverter
/// can specify that it 'shadows' the JsonValueConverter.
/// is a very generic converter and the MultiUrlPickerValueConverter is more specific than it, so the
/// MultiUrlPickerValueConverter can specify that it 'shadows' the JsonValueConverter.
/// </remarks>
public Type[] DefaultConvertersToShadow { get; }
}
@@ -22,9 +22,14 @@ public interface IPropertyIndexValueFactory
/// more than one value for a given field.
/// </para>
/// </remarks>
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture,
string? segment, bool published, IEnumerable<string> availableCultures,
IDictionary<Guid, IContentType> contentTypeDictionary) => GetIndexValues(property, culture, segment, published);
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published, IEnumerable<string> availableCultures)
=> GetIndexValues(property, culture, segment, published);
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published);
}
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
@@ -39,13 +40,13 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
}
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(
IProperty property,
string? culture,
string? segment,
bool published,
IEnumerable<string> availableCultures)
IEnumerable<string> availableCultures,
IDictionary<Guid, IContentType> contentTypeDictionary)
{
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
@@ -63,7 +64,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
return result;
}
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures));
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures, contentTypeDictionary));
}
catch (InvalidCastException)
{
@@ -87,9 +88,31 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
return summary;
}
/// <inheritdoc />
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 14.")]
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(
IProperty property,
string? culture,
string? segment,
bool published,
IEnumerable<string> availableCultures)
=> GetIndexValues(
property,
culture,
segment,
published,
Enumerable.Empty<string>(),
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
[Obsolete("Use method overload that has availableCultures, scheduled for removal in v14")]
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)
=> GetIndexValues(property, culture, segment, published, Enumerable.Empty<string>());
=> GetIndexValues(
property,
culture,
segment,
published,
Enumerable.Empty<string>(),
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
/// <summary>
/// Method to return a list of summary of the content. By default this returns an empty list
@@ -104,7 +127,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
/// <summary>
/// Method that handle the deserialized object.
/// </summary>
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
[Obsolete("Use the non-obsolete overload instead, scheduled for removal in v14")]
protected abstract IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
TSerialized deserializedPropertyValue,
IProperty property,
@@ -112,6 +135,15 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
string? segment,
bool published);
[Obsolete("Use the non-obsolete overload instead, scheduled for removal in v14")]
protected virtual IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
TSerialized deserializedPropertyValue,
IProperty property,
string? culture,
string? segment,
bool published,
IEnumerable<string> availableCultures) => Handle(deserializedPropertyValue, property, culture, segment, published);
/// <summary>
/// Method that handle the deserialized object.
/// </summary>
@@ -121,6 +153,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
string? culture,
string? segment,
bool published,
IEnumerable<string> availableCultures) =>
Handle(deserializedPropertyValue, property, culture, segment, published);
IEnumerable<string> availableCultures,
IDictionary<Guid, IContentType> contentTypeDictionary)
=> Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures);
}
@@ -0,0 +1,39 @@
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// A custom value editor to ensure that macro syntax is parsed when being persisted and formatted correctly for
/// display in the editor
/// </summary>
internal class MarkDownPropertyValueEditor : DataValueEditor
{
private readonly IMarkdownSanitizer _markdownSanitizer;
public MarkDownPropertyValueEditor(
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
DataEditorAttribute attribute,
IMarkdownSanitizer markdownSanitizer)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) => _markdownSanitizer = markdownSanitizer;
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
{
if (string.IsNullOrWhiteSpace(editorValue.Value?.ToString()))
{
return null;
}
var sanitized = _markdownSanitizer.Sanitize(editorValue.Value.ToString()!);
return sanitized.NullOrWhiteSpaceAsNull();
}
}
@@ -3,6 +3,7 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.DependencyInjection;
@@ -50,4 +51,11 @@ public class MarkdownPropertyEditor : DataEditor
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() =>
new MarkdownConfigurationEditor(_ioHelper, _editorConfigurationParser);
/// <summary>
/// Create a custom value editor
/// </summary>
/// <returns></returns>
protected override IDataValueEditor CreateValueEditor() =>
DataValueEditorFactory.Create<MarkDownPropertyValueEditor>(Attribute!);
}
@@ -8,6 +8,12 @@ namespace Umbraco.Cms.Core.PropertyEditors;
public class NoopPropertyIndexValueFactory : IPropertyIndexValueFactory
{
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published,
IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
=> Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published, IEnumerable<string> availableCultures) => Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
@@ -1,6 +1,8 @@
using System.Xml.XPath;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Xml;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PublishedCache;
@@ -9,10 +11,24 @@ public abstract class PublishedCacheBase : IPublishedCache
{
private readonly IVariationContextAccessor? _variationContextAccessor;
public PublishedCacheBase(IVariationContextAccessor variationContextAccessor) => _variationContextAccessor =
variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
protected PublishedCacheBase(bool previewDefault) => PreviewDefault = previewDefault;
[Obsolete("Use ctor with all parameters. This will be removed in V15")]
public PublishedCacheBase(IVariationContextAccessor variationContextAccessor)
: this(variationContextAccessor, false)
{
}
[Obsolete("Use ctor with all parameters. This will be removed in V15")]
protected PublishedCacheBase(bool previewDefault)
: this(StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(), previewDefault)
{
}
public PublishedCacheBase(IVariationContextAccessor variationContextAccessor, bool previewDefault)
{
_variationContextAccessor = variationContextAccessor;
PreviewDefault = previewDefault;
}
public bool PreviewDefault { get; }
+24
View File
@@ -50,4 +50,28 @@ public class WebPath
return sb.ToString();
}
/// <summary>
/// Determines whether the provided web path is well-formed according to the specified UriKind.
/// </summary>
/// <param name="webPath">The web path to check. This can be null.</param>
/// <param name="uriKind">The kind of Uri (Absolute, Relative, or RelativeOrAbsolute).</param>
/// <returns>
/// true if <paramref name="webPath"/> is well-formed; otherwise, false.
/// </returns>
public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind)
{
if (string.IsNullOrWhiteSpace(webPath))
{
return false;
}
if (webPath.StartsWith("//"))
{
return uriKind is not UriKind.Relative;
}
return Uri.IsWellFormedUriString(webPath, uriKind);
}
}
+23 -36
View File
@@ -167,12 +167,7 @@ public class ContentPermissions
throw new ArgumentNullException(nameof(user));
}
if (permissionsToCheck == null)
{
permissionsToCheck = Array.Empty<char>();
}
bool? hasPathAccess = null;
bool hasPathAccess;
entity = null;
if (nodeId == Constants.System.Root)
@@ -183,19 +178,17 @@ public class ContentPermissions
{
hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
}
if (hasPathAccess.HasValue)
else
{
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
}
entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document);
entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document);
if (entity == null)
{
return ContentAccess.NotFound;
}
if (entity == null)
{
return ContentAccess.NotFound;
}
hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
}
if (hasPathAccess == false)
{
@@ -208,7 +201,8 @@ public class ContentPermissions
}
// get the implicit/inherited permissions for the user for this path
return CheckPermissionsPath(entity.Path, user, permissionsToCheck)
// if there is no entity for this id, than just use the id as the path (i.e. -1 or -20)
return CheckPermissionsPath(entity?.Path ?? nodeId.ToString(), user, permissionsToCheck)
? ContentAccess.Granted
: ContentAccess.Denied;
}
@@ -235,12 +229,7 @@ public class ContentPermissions
throw new ArgumentNullException(nameof(user));
}
if (permissionsToCheck == null)
{
permissionsToCheck = Array.Empty<char>();
}
bool? hasPathAccess = null;
bool hasPathAccess;
contentItem = null;
if (nodeId == Constants.System.Root)
@@ -251,19 +240,17 @@ public class ContentPermissions
{
hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
}
if (hasPathAccess.HasValue)
else
{
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
}
contentItem = _contentService.GetById(nodeId);
contentItem = _contentService.GetById(nodeId);
if (contentItem == null)
{
return ContentAccess.NotFound;
}
if (contentItem == null)
{
return ContentAccess.NotFound;
}
hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches);
hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches);
}
if (hasPathAccess == false)
{
@@ -276,7 +263,8 @@ public class ContentPermissions
}
// get the implicit/inherited permissions for the user for this path
return CheckPermissionsPath(contentItem.Path, user, permissionsToCheck)
// if there is no content item for this id, than just use the id as the path (i.e. -1 or -20)
return CheckPermissionsPath(contentItem?.Path ?? nodeId.ToString(), user, permissionsToCheck)
? ContentAccess.Granted
: ContentAccess.Denied;
}
@@ -288,8 +276,7 @@ public class ContentPermissions
permissionsToCheck = Array.Empty<char>();
}
// get the implicit/inherited permissions for the user for this path,
// if there is no content item for this id, than just use the id as the path (i.e. -1 or -20)
// get the implicit/inherited permissions for the user for this path
EntityPermissionSet permission = _userService.GetPermissionsForPath(user, path);
var allowed = true;
@@ -0,0 +1,14 @@
namespace Umbraco.Cms.Core.Security;
/// <summary>
/// Sanitizer service for the markdown editor.
/// </summary>
public interface IMarkdownSanitizer
{
/// <summary>
/// Sanitizes Markdown
/// </summary>
/// <param name="markdown">Markdown to be sanitized</param>
/// <returns>Sanitized Markdown</returns>
string Sanitize(string markdown);
}
@@ -0,0 +1,8 @@
namespace Umbraco.Cms.Core.Security;
/// <inheritdoc />
public class NoopMarkdownSanitizer : IMarkdownSanitizer
{
/// <inheritdoc />
public string Sanitize(string markdown) => markdown;
}
+20 -8
View File
@@ -372,7 +372,7 @@ public class ContentService : RepositoryService, IContentService
public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId)
{
// TODO: what about culture?
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
// locking the content tree secures content types too
scope.WriteLock(Constants.Locks.ContentTree);
@@ -395,6 +395,8 @@ public class ContentService : RepositoryService, IContentService
Save(content, userId);
scope.Complete();
return content;
}
}
@@ -416,7 +418,7 @@ public class ContentService : RepositoryService, IContentService
throw new ArgumentNullException(nameof(parent));
}
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
// locking the content tree secures content types too
scope.WriteLock(Constants.Locks.ContentTree);
@@ -431,6 +433,7 @@ public class ContentService : RepositoryService, IContentService
Save(content, userId);
scope.Complete();
return content;
}
}
@@ -508,10 +511,11 @@ public class ContentService : RepositoryService, IContentService
/// <inheritdoc />
public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
scope.WriteLock(Constants.Locks.ContentTree);
_documentRepository.PersistContentSchedule(content, contentSchedule);
scope.Complete();
}
}
@@ -2105,7 +2109,7 @@ public class ContentService : RepositoryService, IContentService
}
// deal with the branch root - if it fails, abort
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs);
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary<string, object?> notificationState);
if (result != null)
{
results.Add(result);
@@ -2140,7 +2144,7 @@ public class ContentService : RepositoryService, IContentService
}
// no need to check path here, parent has to be published here
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs);
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
if (result != null)
{
results.Add(result);
@@ -2164,7 +2168,7 @@ public class ContentService : RepositoryService, IContentService
// (SaveAndPublishBranchOne does *not* do it)
scope.Notifications.Publish(
new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages));
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages));
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages).WithState(notificationState));
scope.Complete();
}
@@ -2185,8 +2189,10 @@ public class ContentService : RepositoryService, IContentService
ICollection<IContent> publishedDocuments,
EventMessages evtMsgs,
int userId,
IReadOnlyCollection<ILanguage> allLangs)
IReadOnlyCollection<ILanguage> allLangs,
out IDictionary<string, object?> notificationState)
{
notificationState = new Dictionary<string, object?>();
HashSet<string>? culturesToPublish = shouldPublish(document);
// null = do not include
@@ -2218,6 +2224,7 @@ public class ContentService : RepositoryService, IContentService
if (result.Success)
{
publishedDocuments.Add(document);
notificationState = savingNotification.State;
}
return result;
@@ -2957,7 +2964,7 @@ public class ContentService : RepositoryService, IContentService
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
scope.WriteLock(Constants.Locks.ContentTree);
@@ -2970,6 +2977,8 @@ public class ContentService : RepositoryService, IContentService
scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
}
scope.Complete();
return report;
}
}
@@ -3565,6 +3574,7 @@ public class ContentService : RepositoryService, IContentService
Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id, $"Saved content template: {content.Name}");
scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs));
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, evtMsgs));
scope.Complete();
}
@@ -3579,6 +3589,7 @@ public class ContentService : RepositoryService, IContentService
scope.WriteLock(Constants.Locks.ContentTree);
_documentBlueprintRepository.Delete(content);
scope.Notifications.Publish(new ContentDeletedBlueprintNotification(content, evtMsgs));
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.Remove, evtMsgs));
scope.Complete();
}
}
@@ -3679,6 +3690,7 @@ public class ContentService : RepositoryService, IContentService
}
scope.Notifications.Publish(new ContentDeletedBlueprintNotification(blueprints, evtMsgs));
scope.Notifications.Publish(new ContentTreeChangeNotification(blueprints, TreeChangeTypes.Remove, evtMsgs));
scope.Complete();
}
}
@@ -322,7 +322,6 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
}
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
scope.ReadLock(ReadLockIds);
return Repository.GetMany(ids.ToArray());
@@ -68,7 +68,7 @@ internal class ContentVersionService : IContentVersionService
/// <inheritdoc />
public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1)
{
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
scope.WriteLock(Constants.Locks.ContentTree);
_documentVersionRepository.SetPreventCleanup(versionId, preventCleanup);
@@ -77,6 +77,7 @@ internal class ContentVersionService : IContentVersionService
if (version is null)
{
scope.Complete();
return;
}
@@ -87,6 +88,7 @@ internal class ContentVersionService : IContentVersionService
var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'";
Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}");
scope.Complete();
}
}
@@ -120,13 +122,14 @@ internal class ContentVersionService : IContentVersionService
*
* tl;dr lots of scopes to enable other connections to use the DB whilst we work.
*/
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
IReadOnlyCollection<ContentVersionMeta>? allHistoricVersions =
_documentVersionRepository.GetDocumentVersionsEligibleForCleanup();
if (allHistoricVersions is null)
{
scope.Complete();
return Array.Empty<ContentVersionMeta>();
}
@@ -149,6 +152,8 @@ internal class ContentVersionService : IContentVersionService
versionsToDelete.Add(version);
}
scope.Complete();
}
if (!versionsToDelete.Any())
@@ -161,7 +166,7 @@ internal class ContentVersionService : IContentVersionService
foreach (IEnumerable<ContentVersionMeta> group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount))
{
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
scope.WriteLock(Constants.Locks.ContentTree);
var groupEnumerated = group.ToList();
@@ -174,12 +179,16 @@ internal class ContentVersionService : IContentVersionService
scope.Notifications.Publish(
new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId));
}
scope.Complete();
}
}
using (_scopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy");
scope.Complete();
}
return versionsToDelete;
+1 -1
View File
@@ -608,7 +608,7 @@ namespace Umbraco.Cms.Core.Services.Implement
public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete:true);
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return _dataTypeRepository.FindUsages(id);
}
@@ -33,7 +33,7 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
var theRest = new List<ContentVersionMeta>();
using (_scopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
var policyOverrides = _documentVersionRepository.GetCleanupPolicies()?
.ToDictionary(x => x.ContentTypeId);
@@ -77,6 +77,8 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
yield return version;
}
}
scope.Complete();
}
}
+2 -1
View File
@@ -1197,7 +1197,7 @@ namespace Umbraco.Cms.Core.Services
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
scope.WriteLock(Constants.Locks.MediaTree);
@@ -1210,6 +1210,7 @@ namespace Umbraco.Cms.Core.Services
scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
}
scope.Complete();
return report;
}
}
@@ -59,8 +59,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
/// <inheritdoc />
public async Task DeleteUserLoginsAsync(Guid userOrMemberKey)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey);
scope.Complete();
}
/// <inheritdoc />
@@ -155,8 +157,12 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
/// <inheritdoc />
public async Task<bool> DisableAsync(Guid userOrMemberKey, string providerName)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
return await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
var result = await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName);
scope.Complete();
return result;
}
/// <inheritdoc />
@@ -173,9 +179,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
/// <inheritdoc />
public Task SaveAsync(TwoFactorLogin twoFactorLogin)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
_twoFactorLoginRepository.Save(twoFactorLogin);
scope.Complete();
return Task.CompletedTask;
}
@@ -8,6 +8,11 @@ namespace Umbraco.Extensions;
public static class UserServiceExtensions
{
public static EntityPermission? GetPermissions(this IUserService userService, IUser? user, string path)
{
return userService.GetAllPermissions(user, path).FirstOrDefault();
}
public static EntityPermissionCollection GetAllPermissions(this IUserService userService, IUser? user, string path)
{
var ids = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(x =>
@@ -23,7 +28,7 @@ public static class UserServiceExtensions
" could not be parsed into an array of integers or the path was empty");
}
return userService.GetPermissions(user, ids[^1]).FirstOrDefault();
return userService.GetPermissions(user, ids[^1]);
}
/// <summary>
+118
View File
@@ -0,0 +1,118 @@
using System.Diagnostics;
namespace Umbraco.Cms.Core;
/// <summary>
/// Makes a code block timed (take at least a certain amount of time). This class cannot be inherited.
/// </summary>
public sealed class TimedScope : IDisposable, IAsyncDisposable
{
private readonly TimeSpan _duration;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly Stopwatch _stopwatch;
/// <summary>
/// Gets the elapsed time.
/// </summary>
/// <value>
/// The elapsed time.
/// </value>
public TimeSpan Elapsed => _stopwatch.Elapsed;
/// <summary>
/// Gets the remaining time.
/// </summary>
/// <value>
/// The remaining time.
/// </value>
public TimeSpan Remaining
=> TryGetRemaining(out TimeSpan remaining) ? remaining : TimeSpan.Zero;
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
public TimedScope(long millisecondsDuration)
: this(TimeSpan.FromMilliseconds(millisecondsDuration))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public TimedScope(long millisecondsDuration, CancellationToken cancellationToken)
: this(TimeSpan.FromMilliseconds(millisecondsDuration), cancellationToken)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope"/> class.
/// </summary>
/// <param name="duration">The duration the scope should at least take.</param>
public TimedScope(TimeSpan duration)
: this(duration, new CancellationTokenSource())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="duration">The duration the scope should at least take.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public TimedScope(TimeSpan duration, CancellationToken cancellationToken)
: this(duration, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{ }
private TimedScope(TimeSpan duration, CancellationTokenSource cancellationTokenSource)
{
_duration = duration;
_cancellationTokenSource = cancellationTokenSource;
_stopwatch = new Stopwatch();
_stopwatch.Start();
}
/// <summary>
/// Cancels the timed scope.
/// </summary>
public void Cancel()
=> _cancellationTokenSource.Cancel();
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <remarks>
/// This will block using <see cref="Thread.Sleep(TimeSpan)" /> until the remaining time has elapsed, if not cancelled.
/// </remarks>
public void Dispose()
{
if (_cancellationTokenSource.IsCancellationRequested is false &&
TryGetRemaining(out TimeSpan remaining))
{
Thread.Sleep(remaining);
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
/// </summary>
/// <returns>
/// A task that represents the asynchronous dispose operation.
/// </returns>
/// <remarks>
/// This will delay using <see cref="Task.Delay(TimeSpan, CancellationToken)" /> until the remaining time has elapsed, if not cancelled.
/// </remarks>
public async ValueTask DisposeAsync()
{
if (_cancellationTokenSource.IsCancellationRequested is false &&
TryGetRemaining(out TimeSpan remaining))
{
await Task.Delay(remaining, _cancellationTokenSource.Token).ConfigureAwait(false);
}
}
private bool TryGetRemaining(out TimeSpan remaining)
{
remaining = _duration.Subtract(Elapsed);
return remaining > TimeSpan.Zero;
}
}
+16 -16
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Umbraco.Cms.Core</PackageId>
<Title>Umbraco CMS - Core</Title>
@@ -7,22 +7,22 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="6.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="System.Runtime.Caching" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions"/>
<PackageReference Include="Microsoft.Extensions.Identity.Core" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Options"/>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations"/>
<PackageReference Include="System.ComponentModel.Annotations" />
<PackageReference Include="System.Reflection.Emit.Lightweight" />
<PackageReference Include="System.Runtime.Caching" />
<!-- Add dependencies that we force an update to, even that we do not use them explicitly and they seems to be taken from the framework instead of from Nuget -->
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="6.0.1" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Security.Cryptography.Xml" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<ItemGroup>
@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Examine" Version="3.0.1" />
<PackageReference Include="Examine" />
</ItemGroup>
<ItemGroup>
@@ -67,15 +67,6 @@ public class DistributedCacheBinder :
_distributedCache.RefreshContentCache(notification.Changes.ToArray());
}
// private void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs<IContent> e)
// {
// _distributedCache.RefreshUnpublishedPageCache(e.SavedEntities.ToArray());
// }
// private void ContentService_DeletedBlueprint(IContentService sender, DeleteEventArgs<IContent> e)
// {
// _distributedCache.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray());
// }
#endregion
#region LocalizationService / Dictionary
@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Cache;
@@ -132,7 +132,13 @@ public static class DistributedCacheExtensions
public static void RefreshAllContentCache(this DistributedCache dc)
{
ContentCacheRefresher.JsonPayload[] payloads = new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
ContentCacheRefresher.JsonPayload[] payloads = new[]
{
new ContentCacheRefresher.JsonPayload()
{
ChangeTypes = TreeChangeTypes.RefreshAll
}
};
// note: refresh all content cache does refresh content types too
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
@@ -145,8 +151,13 @@ public static class DistributedCacheExtensions
return;
}
IEnumerable<ContentCacheRefresher.JsonPayload> payloads = changes
.Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes));
IEnumerable<ContentCacheRefresher.JsonPayload> payloads = changes.Select(x => new ContentCacheRefresher.JsonPayload()
{
Id = x.Item.Id,
Key = x.Item.Key,
ChangeTypes = x.ChangeTypes,
Blueprint = x.Item.Blueprint
});
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
}
@@ -358,10 +358,13 @@ public static partial class UmbracoBuilderExtensions
builder
.AddNotificationHandler<ContentSavingNotification, BlockListPropertyNotificationHandler>()
.AddNotificationHandler<ContentCopyingNotification, BlockListPropertyNotificationHandler>()
.AddNotificationHandler<ContentScaffoldedNotification, BlockListPropertyNotificationHandler>()
.AddNotificationHandler<ContentSavingNotification, BlockGridPropertyNotificationHandler>()
.AddNotificationHandler<ContentCopyingNotification, BlockGridPropertyNotificationHandler>()
.AddNotificationHandler<ContentScaffoldedNotification, BlockGridPropertyNotificationHandler>()
.AddNotificationHandler<ContentSavingNotification, NestedContentPropertyHandler>()
.AddNotificationHandler<ContentCopyingNotification, NestedContentPropertyHandler>()
.AddNotificationHandler<ContentScaffoldedNotification, NestedContentPropertyHandler>()
.AddNotificationHandler<ContentCopiedNotification, FileUploadPropertyEditor>()
.AddNotificationHandler<ContentDeletedNotification, FileUploadPropertyEditor>()
.AddNotificationHandler<MediaDeletedNotification, FileUploadPropertyEditor>()
@@ -1,6 +1,9 @@
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Examine;
@@ -24,9 +27,26 @@ public abstract class BaseValueSetBuilder<TContent> : IValueSetBuilder<TContent>
[Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")]
protected void AddPropertyValue(IProperty property, string? culture, string? segment, IDictionary<string, IEnumerable<object?>>? values)
=> AddPropertyValue(property, culture, segment, values, Enumerable.Empty<string>());
=> AddPropertyValue(
property,
culture,
segment,
values,
Enumerable.Empty<string>(),
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
protected void AddPropertyValue(IProperty property, string? culture, string? segment, IDictionary<string, IEnumerable<object?>>? values, IEnumerable<string> availableCultures)
[Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")]
protected void AddPropertyValue(IProperty property, string? culture, string? segment,
IDictionary<string, IEnumerable<object?>>? values, IEnumerable<string> availableCultures)
=> AddPropertyValue(
property,
culture,
segment,
values,
Enumerable.Empty<string>(),
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
protected void AddPropertyValue(IProperty property, string? culture, string? segment, IDictionary<string, IEnumerable<object?>>? values, IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
{
IDataEditor? editor = _propertyEditors[property.PropertyType.PropertyEditorAlias];
if (editor == null)
@@ -35,7 +55,7 @@ public abstract class BaseValueSetBuilder<TContent> : IValueSetBuilder<TContent>
}
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> indexVals =
editor.PropertyIndexValueFactory.GetIndexValues(property, culture, segment, PublishedValuesOnly, availableCultures);
editor.PropertyIndexValueFactory.GetIndexValues(property, culture, segment, PublishedValuesOnly, availableCultures, contentTypeDictionary);
foreach (KeyValuePair<string, IEnumerable<object?>> keyVal in indexVals)
{
if (keyVal.Key.IsNullOrWhiteSpace())
@@ -21,13 +21,34 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
private static readonly object[] NoValue = new[] { "n" };
private static readonly object[] YesValue = new[] { "y" };
private readonly IScopeProvider _scopeProvider;
private readonly ICoreScopeProvider _scopeProvider;
private readonly IShortStringHelper _shortStringHelper;
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
private readonly IUserService _userService;
private readonly ILocalizationService _localizationService;
private readonly IContentTypeService _contentTypeService;
public ContentValueSetBuilder(
PropertyEditorCollection propertyEditors,
UrlSegmentProviderCollection urlSegmentProviders,
IUserService userService,
IShortStringHelper shortStringHelper,
ICoreScopeProvider scopeProvider,
bool publishedValuesOnly,
ILocalizationService localizationService,
IContentTypeService contentTypeService)
: base(propertyEditors, publishedValuesOnly)
{
_urlSegmentProviders = urlSegmentProviders;
_userService = userService;
_shortStringHelper = shortStringHelper;
_scopeProvider = scopeProvider;
_localizationService = localizationService;
_contentTypeService = contentTypeService;
}
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
public ContentValueSetBuilder(
PropertyEditorCollection propertyEditors,
UrlSegmentProviderCollection urlSegmentProviders,
@@ -36,16 +57,20 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
IScopeProvider scopeProvider,
bool publishedValuesOnly,
ILocalizationService localizationService)
: base(propertyEditors, publishedValuesOnly)
: this(
propertyEditors,
urlSegmentProviders,
userService,
shortStringHelper,
scopeProvider,
publishedValuesOnly,
localizationService,
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
{
_urlSegmentProviders = urlSegmentProviders;
_userService = userService;
_shortStringHelper = shortStringHelper;
_scopeProvider = scopeProvider;
_localizationService = localizationService;
}
[Obsolete("Use the constructor that takes an ILocalizationService, scheduled for removal in v14")]
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
public ContentValueSetBuilder(
PropertyEditorCollection propertyEditors,
UrlSegmentProviderCollection urlSegmentProviders,
@@ -60,7 +85,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
shortStringHelper,
scopeProvider,
publishedValuesOnly,
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>())
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(),
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
{
}
@@ -72,7 +98,7 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
// We can lookup all of the creator/writer names at once which can save some
// processing below instead of one by one.
using (IScope scope = _scopeProvider.CreateScope())
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
creatorIds = _userService.GetProfilesById(content.Select(x => x.CreatorId).ToArray())
.ToDictionary(x => x.Id, x => x);
@@ -86,6 +112,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
private IEnumerable<ValueSet> GetValueSetsEnumerable(IContent[] content, Dictionary<int, IProfile> creatorIds, Dictionary<int, IProfile> writerIds)
{
IDictionary<Guid, IContentType> contentTypeDictionary = _contentTypeService.GetAll().ToDictionary(x => x.Key);
// TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways
// but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since
// Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]`
@@ -162,13 +190,13 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
{
if (!property.PropertyType.VariesByCulture())
{
AddPropertyValue(property, null, null, values, availableCultures);
AddPropertyValue(property, null, null, values, availableCultures, contentTypeDictionary);
}
else
{
foreach (var culture in c.AvailableCultures)
{
AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures);
AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures, contentTypeDictionary);
}
}
}
@@ -66,7 +66,17 @@ public class ExamineIndexRebuilder : IIndexRebuilder
_logger.LogInformation("Starting async background thread for rebuilding index {indexName}.", indexName);
_backgroundTaskQueue.QueueBackgroundWorkItem(
cancellationToken => Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken)));
cancellationToken =>
{
// Do not flow AsyncLocal to the child thread
using (ExecutionContext.SuppressFlow())
{
Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken));
// immediately return so the queue isn't waiting.
return Task.CompletedTask;
}
});
}
else
{
@@ -93,12 +103,16 @@ public class ExamineIndexRebuilder : IIndexRebuilder
_backgroundTaskQueue.QueueBackgroundWorkItem(
cancellationToken =>
{
// This is a fire/forget task spawned by the background thread queue (which means we
// don't need to worry about ExecutionContext flowing).
Task.Run(() => RebuildIndexes(onlyEmptyIndexes, delay.Value, cancellationToken));
// Do not flow AsyncLocal to the child thread
using (ExecutionContext.SuppressFlow())
{
// This is a fire/forget task spawned by the background thread queue (which means we
// don't need to worry about ExecutionContext flowing).
Task.Run(() => RebuildIndexes(onlyEmptyIndexes, delay.Value, cancellationToken));
// immediately return so the queue isn't waiting.
return Task.CompletedTask;
// immediately return so the queue isn't waiting.
return Task.CompletedTask;
}
});
}
else
@@ -1,10 +1,12 @@
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Examine;
@@ -14,6 +16,7 @@ public class MediaValueSetBuilder : BaseValueSetBuilder<IMedia>
private readonly ContentSettings _contentSettings;
private readonly MediaUrlGeneratorCollection _mediaUrlGenerators;
private readonly IShortStringHelper _shortStringHelper;
private readonly IContentTypeService _contentTypeService;
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
private readonly IUserService _userService;
@@ -23,19 +26,41 @@ public class MediaValueSetBuilder : BaseValueSetBuilder<IMedia>
MediaUrlGeneratorCollection mediaUrlGenerators,
IUserService userService,
IShortStringHelper shortStringHelper,
IOptions<ContentSettings> contentSettings)
IOptions<ContentSettings> contentSettings,
IContentTypeService contentTypeService)
: base(propertyEditors, false)
{
_urlSegmentProviders = urlSegmentProviders;
_mediaUrlGenerators = mediaUrlGenerators;
_userService = userService;
_shortStringHelper = shortStringHelper;
_contentTypeService = contentTypeService;
_contentSettings = contentSettings.Value;
}
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
public MediaValueSetBuilder(
PropertyEditorCollection propertyEditors,
UrlSegmentProviderCollection urlSegmentProviders,
MediaUrlGeneratorCollection mediaUrlGenerators,
IUserService userService,
IShortStringHelper shortStringHelper,
IOptions<ContentSettings> contentSettings)
: this(propertyEditors,
urlSegmentProviders,
mediaUrlGenerators,
userService,
shortStringHelper,
contentSettings,
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
{
}
/// <inheritdoc />
public override IEnumerable<ValueSet> GetValueSets(params IMedia[] media)
{
IDictionary<Guid, IContentType> contentTypeDictionary = _contentTypeService.GetAll().ToDictionary(x => x.Key);
foreach (IMedia m in media)
{
var urlValue = m.GetUrlSegment(_shortStringHelper, _urlSegmentProviders);
@@ -65,7 +90,7 @@ public class MediaValueSetBuilder : BaseValueSetBuilder<IMedia>
foreach (IProperty property in m.Properties)
{
AddPropertyValue(property, null, null, values, m.AvailableCultures);
AddPropertyValue(property, null, null, values, m.AvailableCultures, contentTypeDictionary);
}
var vs = new ValueSet(m.Id.ToInvariantString(), IndexTypes.Media, m.ContentType.Alias, values);
@@ -1,20 +1,34 @@
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Examine;
public class MemberValueSetBuilder : BaseValueSetBuilder<IMember>
{
public MemberValueSetBuilder(PropertyEditorCollection propertyEditors)
private readonly IContentTypeService _contentTypeService;
public MemberValueSetBuilder(PropertyEditorCollection propertyEditors, IContentTypeService contentTypeService)
: base(propertyEditors, false)
{
_contentTypeService = contentTypeService;
}
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
public MemberValueSetBuilder(PropertyEditorCollection propertyEditors)
: this(propertyEditors, StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
{
}
/// <inheritdoc />
public override IEnumerable<ValueSet> GetValueSets(params IMember[] members)
{
IDictionary<Guid, IContentType> contentTypeDictionary = _contentTypeService.GetAll().ToDictionary(x => x.Key);
foreach (IMember m in members)
{
var values = new Dictionary<string, IEnumerable<object?>>
@@ -37,7 +51,7 @@ public class MemberValueSetBuilder : BaseValueSetBuilder<IMember>
foreach (IProperty property in m.Properties)
{
AddPropertyValue(property, null, null, values, m.AvailableCultures);
AddPropertyValue(property, null, null, values, m.AvailableCultures, contentTypeDictionary);
}
var vs = new ValueSet(m.Id.ToInvariantString(), IndexTypes.Member, m.ContentType.Alias, values);
@@ -94,7 +94,7 @@ public class ScheduledPublishing : RecurringHostedServiceBase
// but then what should be its "scope"? could we attach it to scopes?
// - and we should definitively *not* have to flush it here (should be auto)
using UmbracoContextReference contextReference = _umbracoContextFactory.EnsureUmbracoContext();
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
/* We used to assume that there will never be two instances running concurrently where (IsMainDom && ServerRole == SchedulingPublisher)
* However this is possible during an azure deployment slot swap for the SchedulingPublisher instance when trying to achieve zero downtime deployments.
@@ -113,6 +113,8 @@ public class ScheduledPublishing : RecurringHostedServiceBase
grouped.Count(),
grouped.Key);
}
scope.Complete();
}
finally
{
@@ -67,7 +67,7 @@ public class ThreadAbortExceptionEnricher : ILogEventEnricher
private void DumpThreadAborts(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
if (!IsTimeoutThreadAbortException(logEvent.Exception))
if (logEvent.Exception is null || !IsTimeoutThreadAbortException(logEvent.Exception))
{
return;
}
@@ -1,6 +1,9 @@
using Umbraco.Cms.Core.Models;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Web.Common.DependencyInjection;
using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;
namespace Umbraco.Cms.Core.Logging.Viewer;
@@ -10,6 +13,21 @@ public class LogViewerConfig : ILogViewerConfig
private readonly ILogViewerQueryRepository _logViewerQueryRepository;
private readonly IScopeProvider _scopeProvider;
[Obsolete("Use non-obsolete ctor. This will be removed in Umbraco 14.")]
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, Umbraco.Cms.Core.Scoping.IScopeProvider scopeProvider)
: this(logViewerQueryRepository, StaticServiceProvider.Instance.GetRequiredService<IScopeProvider>())
{
}
//Temp ctor used by MSDI (Greedy)
[Obsolete("Use non-obsolete ctor. This will be removed in Umbraco 14.")]
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, Umbraco.Cms.Core.Scoping.IScopeProvider coreScopeProvider, IScopeProvider scopeProvider)
: this(logViewerQueryRepository, scopeProvider)
{
}
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, IScopeProvider scopeProvider)
{
_logViewerQueryRepository = logViewerQueryRepository;
@@ -26,15 +44,16 @@ public class LogViewerConfig : ILogViewerConfig
public IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
using IScope scope = _scopeProvider.CreateScope();
_logViewerQueryRepository.Save(new LogViewerQuery(name, query));
scope.Complete();
return GetSavedSearches();
}
public IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
using IScope scope = _scopeProvider.CreateScope();
ILogViewerQuery? item = name is null ? null : _logViewerQueryRepository.GetByName(name);
if (item is not null)
{
@@ -42,6 +61,8 @@ public class LogViewerConfig : ILogViewerConfig
}
// Return the updated object - so we can instantly reset the entire array from the API response
return GetSavedSearches();
IReadOnlyList<SavedLogSearch> result = GetSavedSearches()!;
scope.Complete();
return result;
}
}
@@ -250,6 +250,11 @@ public class ManifestParser : IManifestParser
return Array.Empty<string>();
}
return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories);
var files = Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories);
// Ensure a consistent, alphabetical sorting of paths, because this is not guaranteed to be the same between file systems or OSes
Array.Sort(files);
return files;
}
}
@@ -37,6 +37,7 @@ public static class PublishedModelUtility
switch (itemType)
{
case PublishedItemType.Content:
case PublishedItemType.Element:
return publishedSnapshot.Content?.GetContentType(alias);
case PublishedItemType.Media:
return publishedSnapshot.Media?.GetContentType(alias);
@@ -37,5 +37,5 @@ internal class AccessDto
[ResultColumn]
[Reference(ReferenceType.Many, ReferenceMemberName = "AccessId")]
public List<AccessRuleDto> Rules { get; set; } = null!;
public List<AccessRuleDto> Rules { get; set; } = new();
}
@@ -1,3 +1,4 @@
using System.Data.Common;
using NPoco;
using Umbraco.Cms.Infrastructure.Migrations.Install;
@@ -33,4 +34,7 @@ public interface IUmbracoDatabase : IDatabase
bool IsUmbracoInstalled();
DatabaseSchemaResult ValidateSchema();
/// <returns>The number of rows affected.</returns>
int ExecuteNonQuery(DbCommand command) => command.ExecuteNonQuery();
}
@@ -1082,82 +1082,78 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
protected void PersistRelations(TEntity entity)
{
// Get all references from our core built in DataEditors/Property Editors
// Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection
var trackedRelations = new List<UmbracoEntityReference>();
trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors));
// Get all references and automatic relation type aliases
ISet<UmbracoEntityReference> references = _dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors);
ISet<string> automaticRelationTypeAliases = _dataValueReferenceFactories.GetAllAutomaticRelationTypesAliases(PropertyEditors);
var relationTypeAliases = GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToArray();
// First delete all auto-relations for this entity
RelationRepository.DeleteByParent(entity.Id, relationTypeAliases);
if (trackedRelations.Count == 0)
if (references.Count == 0)
{
// Delete all relations using the automatic relation type aliases
RelationRepository.DeleteByParent(entity.Id, automaticRelationTypeAliases.ToArray());
// No need to add new references/relations
return;
}
trackedRelations = trackedRelations.Distinct().ToList();
var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi)
.ToDictionary(x => (Udi)x!, x => x!.Guid);
// Lookup all relation type IDs
var relationTypeLookup = RelationTypeRepository.GetMany(Array.Empty<int>())
.Where(x => automaticRelationTypeAliases.Contains(x.Alias))
.ToDictionary(x => x.Alias, x => x.Id);
// lookup in the DB all INT ids for the GUIDs and chuck into a dictionary
var keyToIds = Database.Fetch<NodeIdKey>(Sql()
.Select<NodeDto>(x => x.NodeId, x => x.UniqueId)
.From<NodeDto>()
.WhereIn<NodeDto>(x => x.UniqueId, udiToGuids.Values))
.ToDictionary(x => x.UniqueId, x => x.NodeId);
var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>())?
.ToDictionary(x => x.Alias, x => x);
IEnumerable<ReadOnlyRelation> toSave = trackedRelations.Select(rel =>
{
if (allRelationTypes is null || !allRelationTypes.TryGetValue(rel.RelationTypeAlias, out IRelationType? relationType))
{
throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist");
}
if (!udiToGuids.TryGetValue(rel.Udi, out Guid guid))
{
return null; // This shouldn't happen!
}
if (!keyToIds.TryGetValue(guid, out var id))
{
return null; // This shouldn't happen!
}
return new ReadOnlyRelation(entity.Id, id, relationType.Id);
}).WhereNotNull();
// Save bulk relations
RelationRepository.SaveBulk(toSave);
}
private IEnumerable<string> GetAutomaticRelationTypesAliases(
IPropertyCollection properties,
PropertyEditorCollection propertyEditors)
{
var automaticRelationTypesAliases = new HashSet<string>(Constants.Conventions.RelationTypes.AutomaticRelationTypes);
foreach (IProperty property in properties)
// Lookup node IDs for all GUID based UDIs
IEnumerable<Guid> keys = references.Select(x => x.Udi).OfType<GuidUdi>().Select(x => x.Guid);
var keysLookup = Database.FetchByGroups<NodeIdKey, Guid>(keys, Constants.Sql.MaxParameterCount, guids =>
{
if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? editor) is false )
{
continue;
}
return Sql()
.Select<NodeDto>(x => x.NodeId, x => x.UniqueId)
.From<NodeDto>()
.WhereIn<NodeDto>(x => x.UniqueId, guids);
}).ToDictionary(x => x.UniqueId, x => x.NodeId);
if (editor.GetValueEditor() is IDataValueReference reference)
// Get all valid relations
var relations = new List<(int ChildId, int RelationTypeId)>(references.Count);
foreach (UmbracoEntityReference reference in references)
{
if (string.IsNullOrEmpty(reference.RelationTypeAlias))
{
foreach (var alias in reference.GetAutomaticRelationTypesAliases())
{
automaticRelationTypesAliases.Add(alias);
}
// Reference does not specify a relation type alias, so skip adding a relation
Logger.LogDebug("The reference to {Udi} does not specify a relation type alias, so it will not be saved as relation.", reference.Udi);
}
else if (!automaticRelationTypeAliases.Contains(reference.RelationTypeAlias))
{
// Returning a reference that doesn't use an automatic relation type is an issue that should be fixed in code
Logger.LogError("The reference to {Udi} uses a relation type {RelationTypeAlias} that is not an automatic relation type.", reference.Udi, reference.RelationTypeAlias);
}
else if (!relationTypeLookup.TryGetValue(reference.RelationTypeAlias, out int relationTypeId))
{
// A non-existent relation type could be caused by an environment issue (e.g. it was manually removed)
Logger.LogWarning("The reference to {Udi} uses a relation type {RelationTypeAlias} that does not exist.", reference.Udi, reference.RelationTypeAlias);
}
else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id))
{
// Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints)
Logger.LogInformation("The reference to {Udi} can not be saved as relation, because doesn't have a node ID.", reference.Udi);
}
else
{
relations.Add((id, relationTypeId));
}
}
return automaticRelationTypesAliases;
// Get all existing relations (optimize for adding new and keeping existing relations)
var query = Query<IRelation>().Where(x => x.ParentId == entity.Id).WhereIn(x => x.RelationTypeId, relationTypeLookup.Values);
var existingRelations = RelationRepository.GetPagedRelationsByQuery(query, 0, int.MaxValue, out _, null)
.ToDictionary(x => (x.ChildId, x.RelationTypeId)); // Relations are unique by parent ID, child ID and relation type ID
// Add relations that don't exist yet
var relationsToAdd = relations.Except(existingRelations.Keys).Select(x => new ReadOnlyRelation(entity.Id, x.ChildId, x.RelationTypeId));
RelationRepository.SaveBulk(relationsToAdd);
// Delete relations that don't exist anymore
foreach (IRelation relation in existingRelations.Where(x => !relations.Contains(x.Key)).Select(x => x.Value))
{
RelationRepository.Delete(relation);
}
}
/// <summary>
@@ -1223,8 +1223,11 @@ AND umbracoNode.id <> @id",
/// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true
/// when it should be false
/// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values
/// stored for each
/// property, culture and current/published version.
/// stored for each property, culture and current/published version.
///
/// Some of the sql statements in this function have a tendency to take a lot of parameters (nodeIds)
/// as the WhereIn Npoco method translates all the nodeIds being passed in as parameters when using the SqlClient provider.
/// this results in to many parameters (>2100) error => We need to batch the calls
/// </remarks>
private void RenormalizeDocumentEditedFlags(
IReadOnlyCollection<int> propertyTypeIds,
@@ -1380,15 +1383,23 @@ AND umbracoNode.id <> @id",
// Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
foreach (IGrouping<bool, DocumentCultureVariationDto> editValue in toUpdate.GroupBy(x => x.Edited))
{
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentCultureVariationDto>(x => x.Id, editValue.Select(x => x.Id)));
// update in batches to account for maximum parameter count
foreach (IEnumerable<DocumentCultureVariationDto> batchedValues in editValue.InGroupsOf(Constants.Sql.MaxParameterCount))
{
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentCultureVariationDto>(x => x.Id, batchedValues.Select(x => x.Id)));
}
}
// Now bulk update the umbracoDocument table
foreach (IGrouping<bool, KeyValuePair<int, bool>> editValue in editedDocument.GroupBy(x => x.Value))
foreach (IGrouping<bool, KeyValuePair<int, bool>> groupByValue in editedDocument.GroupBy(x => x.Value))
{
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentDto>(x => x.NodeId, editValue.Select(x => x.Key)));
// update in batches to account for maximum parameter count
foreach (IEnumerable<KeyValuePair<int, bool>> batch in groupByValue.InGroupsOf(Constants.Sql.MaxParameterCount))
{
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, groupByValue.Key))
.WhereIn<DocumentDto>(x => x.NodeId, batch.Select(x => x.Key)));
}
}
}
@@ -264,9 +264,16 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository
_hostingEnvironment.MapPathContentRoot(Path.Combine(
_createdPackagesFolderPath,
definition.Name.Replace(' ', '_')));
var expectedRoot = Path.GetFullPath(_hostingEnvironment.MapPathContentRoot(_createdPackagesFolderPath));
var finalPackagePath = Path.GetFullPath(Path.Combine(directoryName, fileName));
if (finalPackagePath.StartsWith(expectedRoot) == false)
{
throw new IOException("Invalid path due to the package name");
}
Directory.CreateDirectory(directoryName);
var finalPackagePath = Path.Combine(directoryName, fileName);
// Clean existing files
foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath })
@@ -224,6 +224,14 @@ public class UmbracoDatabase : Database, IUmbracoDatabase
return databaseSchemaValidationResult ?? new DatabaseSchemaResult();
}
public int ExecuteNonQuery(DbCommand command)
{
OnExecutingCommand(command);
var i = command.ExecuteNonQuery();
OnExecutedCommand(command);
return i;
}
/// <summary>
/// Returns true if Umbraco database tables are detected to be installed
/// </summary>
@@ -17,12 +17,14 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
{
private BlockEditorValues? _blockEditorValues;
private readonly IDataTypeService _dataTypeService;
private readonly ILogger<BlockEditorPropertyValueEditor> _logger;
private readonly PropertyEditorCollection _propertyEditors;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
private readonly ILogger<BlockEditorPropertyValueEditor> _logger;
protected BlockEditorPropertyValueEditor(
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
ILocalizedTextService textService,
ILogger<BlockEditorPropertyValueEditor> logger,
@@ -32,6 +34,7 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
: base(textService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_propertyEditors = propertyEditors;
_dataValueReferenceFactories = dataValueReferenceFactories;
_dataTypeService = dataTypeService;
_logger = logger;
}
@@ -42,73 +45,60 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
set => _blockEditorValues = value;
}
/// <inheritdoc />
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
{
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
var result = new List<UmbracoEntityReference>();
BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson);
if (blockEditorData == null)
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
foreach (var valuesByPropertyEditorAlias in GetAllPropertyValues(value).GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Value))
{
return Enumerable.Empty<UmbracoEntityReference>();
}
// loop through all content and settings data
foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
{
foreach (KeyValuePair<string, BlockItemData.BlockPropertyValue> prop in row.PropertyValues)
if (!_propertyEditors.TryGet(valuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
{
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
continue;
}
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
if (!(valueEditor is IDataValueReference reference))
{
continue;
}
var val = prop.Value.Value?.ToString();
IEnumerable<UmbracoEntityReference> refs = reference.GetReferences(val);
result.AddRange(refs);
// Use distinct values to avoid duplicate parsing of the same value
foreach (UmbracoEntityReference reference in _dataValueReferenceFactories.GetReferences(dataEditor, valuesByPropertyEditorAlias.Distinct()))
{
yield return reference;
}
}
return result;
}
/// <inheritdoc />
public IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId)
{
foreach (BlockItemData.BlockPropertyValue propertyValue in GetAllPropertyValues(value))
{
if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) ||
dataEditor.GetValueEditor() is not IDataValueTags dataValueTags)
{
continue;
}
object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration;
foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId))
{
yield return tag;
}
}
}
private IEnumerable<BlockItemData.BlockPropertyValue> GetAllPropertyValues(object? value)
{
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson);
if (blockEditorData == null)
if (blockEditorData is null)
{
return Enumerable.Empty<ITag>();
yield break;
}
var result = new List<ITag>();
// loop through all content and settings data
foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
// Return all property values from the content and settings data
IEnumerable<BlockItemData> data = blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData);
foreach (BlockItemData.BlockPropertyValue propertyValue in data.SelectMany(x => x.PropertyValues.Select(x => x.Value)))
{
foreach (KeyValuePair<string, BlockItemData.BlockPropertyValue> prop in row.PropertyValues)
{
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
if (valueEditor is not IDataValueTags tagsProvider)
{
continue;
}
object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;
result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
}
yield return propertyValue;
}
return result;
}
#region Convert database // editor
@@ -119,7 +109,6 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
/// Ensure that sub-editor values are translated through their ToEditor methods
/// </summary>
/// <param name="property"></param>
/// <param name="dataTypeService"></param>
/// <param name="culture"></param>
/// <param name="segment"></param>
/// <returns></returns>
@@ -50,6 +50,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor
public BlockGridEditorPropertyValueEditor(
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
ILocalizedTextService textService,
ILogger<BlockEditorPropertyValueEditor> logger,
@@ -58,7 +59,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor
IIOHelper ioHelper,
IContentTypeService contentTypeService,
IPropertyValidationService propertyValidationService)
: base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
: base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
{
BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), contentTypeService, logger);
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
@@ -46,6 +46,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor
public BlockListEditorPropertyValueEditor(
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
IContentTypeService contentTypeService,
ILocalizedTextService textService,
@@ -54,7 +55,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IPropertyValidationService propertyValidationService) :
base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
base(attribute, propertyEditors, dataValueReferenceFactories,dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
{
BlockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, logger);
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
@@ -38,10 +38,13 @@ internal sealed class BlockValuePropertyIndexValueFactory :
_contentTypeService = contentTypeService;
}
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
protected override IContentType? GetContentTypeOfNestedItem(BlockItemData input) =>
_contentTypeService.Get(input.ContentTypeKey);
protected override IContentType? GetContentTypeOfNestedItem(BlockItemData input, IDictionary<Guid, IContentType> contentTypeDictionary)
=> contentTypeDictionary.TryGetValue(input.ContentTypeKey, out var result) ? result : null;
protected override IDictionary<string, object?> GetRawProperty(BlockItemData blockItemData) =>
blockItemData.RawPropertyValues;
@@ -10,9 +10,16 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Handles nested Udi keys when
/// - saving: Empty keys get generated
/// - copy: keys get replaced by new ones while keeping references intact
/// - scaffolding: keys get replaced by new ones while keeping references intact
/// </summary>
public abstract class ComplexPropertyEditorContentNotificationHandler :
INotificationHandler<ContentSavingNotification>,
INotificationHandler<ContentCopyingNotification>
INotificationHandler<ContentCopyingNotification>,
INotificationHandler<ContentScaffoldedNotification>
{
protected abstract string EditorAlias { get; }
@@ -31,6 +38,12 @@ public abstract class ComplexPropertyEditorContentNotificationHandler :
}
}
public void Handle(ContentScaffoldedNotification notification)
{
IEnumerable<IProperty> props = notification.Scaffold.GetPropertiesByEditor(EditorAlias);
UpdatePropertyValues(props, false);
}
protected abstract string FormatPropertyValue(string rawJson, bool onlyMissingKeys);
private void UpdatePropertyValues(IEnumerable<IProperty> props, bool onlyMissingKeys)
@@ -2,19 +2,20 @@
// See LICENSE for more details.
using System.Runtime.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -26,11 +27,57 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
NullValueHandling = NullValueHandling.Ignore,
};
private readonly IEntityService _entityService;
private readonly ILogger<MultiUrlPickerValueEditor> _logger;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IPublishedUrlProvider _publishedUrlProvider;
private readonly IContentService _contentService;
private readonly IMediaService _mediaService;
public MultiUrlPickerValueEditor(
ILogger<MultiUrlPickerValueEditor> logger,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
DataEditorAttribute attribute,
IPublishedUrlProvider publishedUrlProvider,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IContentService contentService,
IMediaService mediaService)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_publishedUrlProvider = publishedUrlProvider;
_contentService = contentService;
_mediaService = mediaService;
}
[Obsolete("Use non-obsolete constructor. Scheduled for removal in Umbraco 14.")]
public MultiUrlPickerValueEditor(
IEntityService entityService,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
ILogger<MultiUrlPickerValueEditor> logger,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
DataEditorAttribute attribute,
IPublishedUrlProvider publishedUrlProvider,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IContentService contentService,
IMediaService mediaService)
:this(
logger,
localizedTextService,
shortStringHelper,
attribute,
publishedUrlProvider,
jsonSerializer,
ioHelper,
contentService,
mediaService)
{
}
[Obsolete("Use non-obsolete constructor. Scheduled for removal in Umbraco 14.")]
public MultiUrlPickerValueEditor(
IEntityService entityService,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
@@ -41,13 +88,18 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
IPublishedUrlProvider publishedUrlProvider,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
: this(
logger,
localizedTextService,
shortStringHelper,
attribute,
publishedUrlProvider,
jsonSerializer,
ioHelper,
StaticServiceProvider.Instance.GetRequiredService<IContentService>(),
StaticServiceProvider.Instance.GetRequiredService<IMediaService>())
{
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
_publishedSnapshotAccessor = publishedSnapshotAccessor ??
throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_publishedUrlProvider = publishedUrlProvider;
}
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
@@ -86,26 +138,6 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
{
List<LinkDto>? links = JsonConvert.DeserializeObject<List<LinkDto>>(value);
List<LinkDto>? documentLinks = links?.FindAll(link =>
link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Document);
List<LinkDto>? mediaLinks = links?.FindAll(link =>
link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Media);
var entities = new List<IEntitySlim>();
if (documentLinks?.Count > 0)
{
entities.AddRange(
_entityService.GetAll(
UmbracoObjectTypes.Document,
documentLinks.Select(link => link.Udi!.Guid).ToArray()));
}
if (mediaLinks?.Count > 0)
{
entities.AddRange(
_entityService.GetAll(UmbracoObjectTypes.Media, mediaLinks.Select(link => link.Udi!.Guid).ToArray()));
}
var result = new List<LinkDisplay>();
if (links is null)
{
@@ -114,7 +146,7 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
foreach (LinkDto dto in links)
{
GuidUdi? udi = null;
GuidUdi? udi = dto.Udi;
var icon = "icon-link";
var published = true;
var trashed = false;
@@ -122,35 +154,30 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
if (dto.Udi != null)
{
IUmbracoEntity? entity = entities.Find(e => e.Key == dto.Udi.Guid);
if (entity == null)
if (dto.Udi.EntityType == Constants.UdiEntityType.Document)
{
continue;
}
url = _publishedUrlProvider.GetUrl(dto.Udi.Guid, UrlMode.Relative, culture);
IContent? c = _contentService.GetById(dto.Udi.Guid);
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
if (entity is IDocumentEntitySlim documentEntity)
{
icon = documentEntity.ContentTypeIcon;
published = culture == null
? documentEntity.Published
: documentEntity.PublishedCultures.Contains(culture);
udi = new GuidUdi(Constants.UdiEntityType.Document, documentEntity.Key);
url = publishedSnapshot.Content?.GetById(entity.Key)?.Url(_publishedUrlProvider) ?? "#";
trashed = documentEntity.Trashed;
if (c is not null)
{
published = culture == null
? c.Published
: c.PublishedCultures.Contains(culture);
icon = c.ContentType.Icon;
trashed = c.Trashed;
}
}
else if (entity is IContentEntitySlim contentEntity)
else if (dto.Udi.EntityType == Constants.UdiEntityType.Media)
{
icon = contentEntity.ContentTypeIcon;
published = !contentEntity.Trashed;
udi = new GuidUdi(Constants.UdiEntityType.Media, contentEntity.Key);
url = publishedSnapshot.Media?.GetById(entity.Key)?.Url(_publishedUrlProvider) ?? "#";
trashed = contentEntity.Trashed;
}
else
{
// Not supported
continue;
url = _publishedUrlProvider.GetMediaUrl(dto.Udi.Guid, UrlMode.Relative, culture);
IMedia? m = _mediaService.GetById(dto.Udi.Guid);
if (m is not null)
{
published = m.Trashed is false;
icon = m.ContentType.Icon;
trashed = m.Trashed;
}
}
}
@@ -89,9 +89,10 @@ public class NestedContentPropertyEditor : DataEditor
internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags
{
private readonly IDataTypeService _dataTypeService;
private readonly PropertyEditorCollection _propertyEditors;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
private readonly ILogger<NestedContentPropertyEditor> _logger;
private readonly NestedContentValues _nestedContentValues;
private readonly PropertyEditorCollection _propertyEditors;
public NestedContentPropertyValueEditor(
IDataTypeService dataTypeService,
@@ -100,16 +101,19 @@ public class NestedContentPropertyEditor : DataEditor
IShortStringHelper shortStringHelper,
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
DataValueReferenceFactoryCollection dataValueReferenceFactories,
ILogger<NestedContentPropertyEditor> logger,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IPropertyValidationService propertyValidationService)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
_propertyEditors = propertyEditors;
_dataValueReferenceFactories = dataValueReferenceFactories;
_logger = logger;
_nestedContentValues = new NestedContentValues(contentTypeService);
Validators.Add(new NestedContentValidator(propertyValidationService, _nestedContentValues, contentTypeService));
}
@@ -137,66 +141,47 @@ public class NestedContentPropertyEditor : DataEditor
}
}
/// <inheritdoc />
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
{
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
var result = new List<UmbracoEntityReference>();
foreach (NestedContentValues.NestedContentRowValue row in _nestedContentValues.GetPropertyValues(rawJson))
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
foreach (var valuesByPropertyEditorAlias in GetAllPropertyValues(value).GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Value))
{
foreach (KeyValuePair<string, NestedContentValues.NestedContentPropertyValue> prop in
row.PropertyValues)
if (!_propertyEditors.TryGet(valuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
{
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
continue;
}
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
if (!(valueEditor is IDataValueReference reference))
{
continue;
}
var val = prop.Value.Value?.ToString();
IEnumerable<UmbracoEntityReference> refs = reference.GetReferences(val);
result.AddRange(refs);
// Use distinct values to avoid duplicate parsing of the same value
foreach (UmbracoEntityReference reference in _dataValueReferenceFactories.GetReferences(dataEditor, valuesByPropertyEditorAlias.Distinct()))
{
yield return reference;
}
}
return result;
}
/// <inheritdoc />
public IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId)
{
IReadOnlyList<NestedContentValues.NestedContentRowValue> rows =
_nestedContentValues.GetPropertyValues(value);
var result = new List<ITag>();
foreach (NestedContentValues.NestedContentRowValue row in rows.ToList())
foreach (NestedContentValues.NestedContentPropertyValue propertyValue in GetAllPropertyValues(value))
{
foreach (KeyValuePair<string, NestedContentValues.NestedContentPropertyValue> prop in row.PropertyValues
.ToList())
if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) ||
dataEditor.GetValueEditor() is not IDataValueTags dataValueTags)
{
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
continue;
}
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
if (valueEditor is not IDataValueTags tagsProvider)
{
continue;
}
object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;
result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration;
foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId))
{
yield return tag;
}
}
return result;
}
private IEnumerable<NestedContentValues.NestedContentPropertyValue> GetAllPropertyValues(object? value)
=> _nestedContentValues.GetPropertyValues(value).SelectMany(x => x.PropertyValues.Values);
#region DB to String
public override string ConvertDbToString(IPropertyType propertyType, object? propertyValue)
@@ -422,7 +407,8 @@ public class NestedContentPropertyEditor : DataEditor
// set values to null
row.PropertyValues[elementTypeProp.Alias] = new NestedContentValues.NestedContentPropertyValue
{
PropertyType = elementTypeProp, Value = null,
PropertyType = elementTypeProp,
Value = null,
};
row.RawPropertyValues[elementTypeProp.Alias] = null;
}
@@ -39,6 +39,11 @@ internal sealed class NestedContentPropertyIndexValueFactory
_contentTypeService = contentTypeService;
}
protected override IContentType? GetContentTypeOfNestedItem(
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue input, IDictionary<Guid, IContentType> contentTypeDictionary)
=> contentTypeDictionary.Values.FirstOrDefault(x=>x.Alias.Equals(input.ContentTypeAlias));
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
protected override IContentType? GetContentTypeOfNestedItem(
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue input)
=> _contentTypeService.Get(input.ContentTypeAlias);
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Examine;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
@@ -42,20 +43,39 @@ internal abstract class NestedPropertyIndexValueFactoryBase<TSerialized, TItem>
bool published) =>
Handle(deserializedPropertyValue, property, culture, segment, published, Enumerable.Empty<string>());
[Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")]
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
TSerialized deserializedPropertyValue,
IProperty property,
string? culture,
string? segment,
bool published,
IEnumerable<string> availableCultures)
IEnumerable<string> availableCultures) =>
Handle(
deserializedPropertyValue,
property,
culture,
segment,
published,
Enumerable.Empty<string>(),
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
TSerialized deserializedPropertyValue,
IProperty property,
string? culture,
string? segment,
bool published,
IEnumerable<string> availableCultures,
IDictionary<Guid, IContentType> contentTypeDictionary)
{
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
var index = 0;
foreach (TItem nestedContentRowValue in GetDataItems(deserializedPropertyValue))
{
IContentType? contentType = GetContentTypeOfNestedItem(nestedContentRowValue);
IContentType? contentType = GetContentTypeOfNestedItem(nestedContentRowValue, contentTypeDictionary);
if (contentType is null)
{
@@ -125,6 +145,9 @@ internal abstract class NestedPropertyIndexValueFactoryBase<TSerialized, TItem>
/// <summary>
/// Gets the content type using the nested item.
/// </summary>
protected abstract IContentType? GetContentTypeOfNestedItem(TItem nestedItem, IDictionary<Guid, IContentType> contentTypeDictionary);
[Obsolete("Use non-obsolete overload. Scheduled for removal in Umbraco 14.")]
protected abstract IContentType? GetContentTypeOfNestedItem(TItem nestedItem);
/// <summary>
+12 -14
View File
@@ -23,10 +23,9 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private readonly bool _autoComplete;
private readonly CoreDebugSettings _coreDebugSettings;
private readonly object _dictionaryLocker;
private readonly IEventAggregator _eventAggregator;
private readonly IsolationLevel _isolationLevel;
private readonly object _lockQueueLocker = new();
private readonly object _locker = new();
private readonly ILogger<Scope> _logger;
private readonly MediaFileManager _mediaFileManager;
private readonly RepositoryCacheMode _repositoryCacheMode;
@@ -87,7 +86,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping
_scopeFileSystem = scopeFileSystems;
_autoComplete = autoComplete;
Detachable = detachable;
_dictionaryLocker = new object();
#if DEBUG_SCOPES
_scopeProvider.RegisterScope(this);
@@ -562,7 +560,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
DisposeLastScope();
}
lock (_lockQueueLocker)
lock (_locker)
{
_queuedLocks?.Clear();
}
@@ -573,24 +571,24 @@ namespace Umbraco.Cms.Infrastructure.Scoping
public void EagerReadLock(params int[] lockIds) => EagerReadLockInner(InstanceId, null, lockIds);
/// <inheritdoc />
public void ReadLock(params int[] lockIds) => LazyReadLockInner(InstanceId, lockIds);
public void ReadLock(params int[] lockIds) => EagerReadLockInner(InstanceId, null, lockIds);
public void EagerReadLock(TimeSpan timeout, int lockId) =>
EagerReadLockInner(InstanceId, timeout, lockId);
/// <inheritdoc />
public void ReadLock(TimeSpan timeout, int lockId) => LazyReadLockInner(InstanceId, timeout, lockId);
public void ReadLock(TimeSpan timeout, int lockId) => EagerReadLockInner(InstanceId, timeout, lockId);
public void EagerWriteLock(params int[] lockIds) => EagerWriteLockInner(InstanceId, null, lockIds);
/// <inheritdoc />
public void WriteLock(params int[] lockIds) => LazyWriteLockInner(InstanceId, lockIds);
public void WriteLock(params int[] lockIds) => EagerWriteLockInner(InstanceId, null, lockIds);
public void EagerWriteLock(TimeSpan timeout, int lockId) =>
EagerWriteLockInner(InstanceId, timeout, lockId);
/// <inheritdoc />
public void WriteLock(TimeSpan timeout, int lockId) => LazyWriteLockInner(InstanceId, timeout, lockId);
public void WriteLock(TimeSpan timeout, int lockId) => EagerWriteLockInner(InstanceId, timeout, lockId);
/// <summary>
/// Used for testing. Ensures and gets any queued read locks.
@@ -659,7 +657,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
else
{
lock (_lockQueueLocker)
lock (_locker)
{
if (_queuedLocks?.Count > 0)
{
@@ -970,7 +968,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
else
{
lock (_dictionaryLocker)
lock (_locker)
{
_readLocksDictionary?.Remove(instanceId);
_writeLocksDictionary?.Remove(instanceId);
@@ -1045,7 +1043,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private void LazyLockInner(DistributedLockType lockType, Guid instanceId, params int[] lockIds)
{
lock (_lockQueueLocker)
lock (_locker)
{
if (_queuedLocks == null)
{
@@ -1061,7 +1059,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private void LazyLockInner(DistributedLockType lockType, Guid instanceId, TimeSpan timeout, int lockId)
{
lock (_lockQueueLocker)
lock (_locker)
{
if (_queuedLocks == null)
{
@@ -1088,7 +1086,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
else
{
lock (_dictionaryLocker)
lock (_locker)
{
foreach (var lockId in lockIds)
{
@@ -1122,7 +1120,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
else
{
lock (_dictionaryLocker)
lock (_locker)
{
foreach (var lockId in lockIds)
{
@@ -51,13 +51,15 @@ public sealed class ContentIndexingNotificationHandler : INotificationHandler<Co
foreach (ContentCacheRefresher.JsonPayload payload in (ContentCacheRefresher.JsonPayload[])args.MessageObject)
{
if (payload.Blueprint)
{
// Skip blueprints
continue;
}
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
{
if (deleteBatch == null)
{
deleteBatch = new HashSet<int>();
}
deleteBatch ??= new HashSet<int>();
deleteBatch.Add(payload.Id);
}
else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
@@ -96,7 +96,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
throw new ArgumentNullException(nameof(user));
}
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
// create member
IMember memberEntity = _memberService.CreateMember(
@@ -150,6 +150,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
x.Value)));
}
scope.Complete();
return Task.FromResult(IdentityResult.Success);
}
catch (Exception ex)
@@ -179,7 +180,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
}
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
IMember? found = _memberService.GetById(asInt);
if (found != null)
@@ -220,6 +221,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
}
}
scope.Complete();
return Task.FromResult(IdentityResult.Success);
}
catch (Exception ex)
@@ -134,8 +134,8 @@ public abstract class UmbracoUserManager<TUser, TPasswordConfig> : UserManager<T
/// <inheritdoc />
public override async Task<bool> CheckPasswordAsync(TUser user, string? password)
{
// we cannot proceed if the user passed in does not have an identity
if (user.HasIdentity == false)
// we cannot proceed if the user passed in does not have an identity, or if no password is provided.
if (user.HasIdentity == false || password is null)
{
return false;
}
@@ -252,7 +252,7 @@ public abstract class UmbracoUserManager<TUser, TPasswordConfig> : UserManager<T
public async Task<bool> ValidateCredentialsAsync(string username, string password)
{
TUser user = await FindByNameAsync(username);
if (user == null)
{
return false;
@@ -263,7 +263,7 @@ public abstract class UmbracoUserManager<TUser, TPasswordConfig> : UserManager<T
throw new NotSupportedException("The current user store does not implement " +
typeof(IUserPasswordStore<>));
}
var result = await VerifyPasswordAsync(userPasswordStore, user, password);
return result == PasswordVerificationResult.Success || result == PasswordVerificationResult.SuccessRehashNeeded;
@@ -1,3 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Umbraco.Cms.Core;
@@ -6,6 +7,7 @@ using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Macros;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Templates;
@@ -15,12 +17,23 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
private readonly ILogger<HtmlMacroParameterParser> _logger;
private readonly IMacroService _macroService;
private readonly ParameterEditorCollection _parameterEditors;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
[Obsolete("Use the non-obsolete overload instead, scheduled for removal in v14")]
public HtmlMacroParameterParser(IMacroService macroService, ILogger<HtmlMacroParameterParser> logger, ParameterEditorCollection parameterEditors)
: this(
macroService,
logger,
parameterEditors,
StaticServiceProvider.Instance.GetRequiredService<DataValueReferenceFactoryCollection>())
{ }
public HtmlMacroParameterParser(IMacroService macroService, ILogger<HtmlMacroParameterParser> logger, ParameterEditorCollection parameterEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
{
_macroService = macroService;
_logger = logger;
_parameterEditors = parameterEditors;
_dataValueReferenceFactories = dataValueReferenceFactories;
}
/// <summary>
@@ -41,6 +54,7 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
(macroAlias, macroAttributes) => foundMacros.Add(new Tuple<string?, Dictionary<string, string>>(
macroAlias,
new Dictionary<string, string>(macroAttributes, StringComparer.OrdinalIgnoreCase))));
foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
{
yield return umbracoEntityReference;
@@ -52,8 +66,7 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
/// </summary>
/// <param name="macroGridControls"></param>
/// <returns></returns>
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromGridControlMacros(
IEnumerable<GridValue.GridControl> macroGridControls)
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable<GridValue.GridControl> macroGridControls)
{
var foundMacros = new List<Tuple<string?, Dictionary<string, string>>>();
@@ -65,8 +78,7 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
// Collect any macro parameters that contain the media udi format
if (gridMacro is not null && gridMacro.MacroParameters is not null && gridMacro.MacroParameters.Any())
{
foundMacros.Add(
new Tuple<string?, Dictionary<string, string>>(gridMacro.MacroAlias, gridMacro.MacroParameters));
foundMacros.Add(new Tuple<string?, Dictionary<string, string>>(gridMacro.MacroAlias, gridMacro.MacroParameters));
}
}
@@ -101,14 +113,12 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
continue;
}
foundMacroUmbracoEntityReferences.Add(
new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key)));
foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key)));
// Only do this if the macros actually have parameters
if (macroConfig.Properties.Keys.Any(f => f != "macroAlias"))
{
foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(
macro.Item2, macroConfig, _parameterEditors))
foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors))
{
yield return umbracoEntityReference;
}
@@ -130,41 +140,23 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
/// look up the corresponding property editor for a macro parameter
/// </param>
/// <returns></returns>
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacroParameters(
Dictionary<string, string> macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors)
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacroParameters(Dictionary<string, string> macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors)
{
var foundUmbracoEntityReferences = new List<UmbracoEntityReference>();
foreach (IMacroProperty parameter in macroConfig.Properties)
{
if (macroParameters.TryGetValue(parameter.Alias, out var parameterValue))
{
var parameterEditorAlias = parameter.EditorAlias;
// Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter
IDataEditor? parameterEditor = parameterEditors.FirstOrDefault(f =>
string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase));
IDataEditor? parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase));
if (parameterEditor is not null)
{
// Get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast as IDataValueReference to determine if 'it is' implemented for the editor
if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor)
foreach (UmbracoEntityReference entityReference in _dataValueReferenceFactories.GetReferences(parameterEditor, parameterValue))
{
foreach (UmbracoEntityReference entityReference in parameterValueEditor.GetReferences(
parameterValue))
{
foundUmbracoEntityReferences.Add(entityReference);
}
}
else
{
_logger.LogInformation(
"{0} doesn't have a ValueEditor that implements IDataValueReference",
parameterEditor.Alias);
yield return entityReference;
}
}
}
}
return foundUmbracoEntityReferences;
}
// Poco class to deserialise the Json for a Macro Control
@@ -11,36 +11,36 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Examine.Core" Version="3.0.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
<PackageReference Include="IPNetwork2" Version="2.5.422" />
<PackageReference Include="MailKit" Version="3.2.0" />
<PackageReference Include="Markdown" Version="2.2.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="6.0.5" />
<PackageReference Include="MiniProfiler.Shared" Version="4.2.22" />
<PackageReference Include="ncrontab" Version="3.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NPoco.SqlServer" Version="5.3.2" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.2" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Expressions" Version="3.4.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="1.0.5" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="6.0.1" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
<PackageReference Include="Examine.Core"/>
<PackageReference Include="HtmlAgilityPack"/>
<PackageReference Include="IPNetwork2" />
<PackageReference Include="MailKit" />
<PackageReference Include="Markdown" />
<PackageReference Include="Microsoft.CSharp" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" />
<PackageReference Include="MiniProfiler.Shared" />
<PackageReference Include="ncrontab" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="NPoco.SqlServer" />
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Enrichers.Process" />
<PackageReference Include="Serilog.Enrichers.Thread" />
<PackageReference Include="Serilog.Expressions" />
<PackageReference Include="Serilog.Extensions.Hosting" />
<PackageReference Include="Serilog.Formatting.Compact" />
<PackageReference Include="Serilog.Formatting.Compact.Reader" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Async" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="Serilog.Sinks.Map" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="System.IO.FileSystem.AccessControl" />
<PackageReference Include="System.Security.Cryptography.Pkcs" />
<PackageReference Include="System.Threading.Tasks.Dataflow" />
</ItemGroup>
<ItemGroup>
@@ -36,7 +36,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab
IDomainCache domainCache,
IOptions<GlobalSettings> globalSettings,
IVariationContextAccessor variationContextAccessor)
: base(previewDefault)
: base(variationContextAccessor, previewDefault)
{
_snapshot = snapshot;
_snapshotCache = snapshotCache;
@@ -27,6 +27,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache;
/// </remarks>
public class ContentStore
{
private static readonly TimeSpan _monitorTimeout = TimeSpan.FromSeconds(30);
// TODO: collection trigger (ok for now)
// see SnapDictionary notes
private const long CollectMinGenDelta = 8;
@@ -330,7 +332,12 @@ public class ContentStore
throw new InvalidOperationException("Recursive locks not allowed");
}
Monitor.Enter(_wlocko, ref lockInfo.Taken);
Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken);
if (Monitor.IsEntered(_wlocko) is false)
{
throw new TimeoutException("Could not enter monitor before timeout in content store");
}
lock (_rlocko)
{
@@ -736,16 +743,22 @@ public class ContentStore
{
EnsureLocked();
IPublishedContentType?[] contentTypes = _contentTypesById
IPublishedContentType[] contentTypes = _contentTypesById
.Where(kvp =>
kvp.Value.Value != null &&
kvp.Value.Value.PropertyTypes.Any(p => dataTypeIds.Contains(p.DataType.Id)))
.Select(kvp => kvp.Value.Value)
.Select(x => getContentType(x!.Id))
.Where(x => x != null) // poof, gone, very unlikely and probably an anomaly
.WhereNotNull() // poof, gone, very unlikely and probably an anomaly
.ToArray();
var contentTypeIdsA = contentTypes.Select(x => x!.Id).ToArray();
// all content types that are affected by this data type update must be updated
foreach (IPublishedContentType contentType in contentTypes)
{
SetContentTypeLocked(contentType);
}
var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray();
var contentTypeNodes = new Dictionary<int, List<int>>();
foreach (var id in contentTypeIdsA)
{
@@ -761,7 +774,7 @@ public class ContentStore
}
}
foreach (IPublishedContentType contentType in contentTypes.WhereNotNull())
foreach (IPublishedContentType contentType in contentTypes)
{
// again, weird situation
if (contentTypeNodes.ContainsKey(contentType.Id) == false)
@@ -17,7 +17,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa
#region Constructors
public MediaCache(bool previewDefault, ContentStore.Snapshot snapshot, IVariationContextAccessor variationContextAccessor)
: base(previewDefault)
: base(variationContextAccessor, previewDefault)
{
_snapshot = snapshot;
_variationContextAccessor = variationContextAccessor;
@@ -221,19 +221,12 @@ AND cmsContentNu.nodeId IS NULL
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document))
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
// Use a more efficient COUNT query
Sql<ISqlContext>? sqlCountQuery = SqlContentSourcesCount()
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document));
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
IContentCacheDataSerializer serializer =
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document);
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
IEnumerable<ContentSourceDto> dtos = GetContentNodeDtos(sql);
foreach (ContentSourceDto row in dtos)
{
yield return CreateContentNodeKit(row, serializer);
}
@@ -246,19 +239,12 @@ AND cmsContentNu.nodeId IS NULL
.Append(SqlWhereNodeIdX(SqlContext, id))
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
// Use a more efficient COUNT query
Sql<ISqlContext>? sqlCountQuery = SqlContentSourcesCount(SqlContentSourcesSelectUmbracoNodeJoin)
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document))
.Append(SqlWhereNodeIdX(SqlContext, id));
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
IContentCacheDataSerializer serializer =
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document);
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
IEnumerable<ContentSourceDto> dtos = GetContentNodeDtos(sql);
foreach (ContentSourceDto row in dtos)
{
yield return CreateContentNodeKit(row, serializer);
}
@@ -276,19 +262,12 @@ AND cmsContentNu.nodeId IS NULL
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
// Use a more efficient COUNT query
Sql<ISqlContext> sqlCountQuery = SqlContentSourcesCount()
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document))
.WhereIn<ContentDto>(x => x.ContentTypeId, ids);
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
IContentCacheDataSerializer serializer =
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document);
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
IEnumerable<ContentSourceDto> dtos = GetContentNodeDtos(sql);
foreach (ContentSourceDto row in dtos)
{
yield return CreateContentNodeKit(row, serializer);
}
@@ -319,18 +298,12 @@ AND cmsContentNu.nodeId IS NULL
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media))
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
// Use a more efficient COUNT query
Sql<ISqlContext>? sqlCountQuery = SqlMediaSourcesCount()
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media));
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
IContentCacheDataSerializer serializer =
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media);
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
IEnumerable<ContentSourceDto> dtos = GetMediaNodeDtos(sql);
foreach (ContentSourceDto row in dtos)
{
yield return CreateMediaNodeKit(row, serializer);
}
@@ -343,19 +316,12 @@ AND cmsContentNu.nodeId IS NULL
.Append(SqlWhereNodeIdX(SqlContext, id))
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
// Use a more efficient COUNT query
Sql<ISqlContext>? sqlCountQuery = SqlMediaSourcesCount(SqlContentSourcesSelectUmbracoNodeJoin)
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media))
.Append(SqlWhereNodeIdX(SqlContext, id));
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
IContentCacheDataSerializer serializer =
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media);
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
IEnumerable<ContentSourceDto> dtos = GetMediaNodeDtos(sql);
foreach (ContentSourceDto row in dtos)
{
yield return CreateMediaNodeKit(row, serializer);
}
@@ -373,19 +339,12 @@ AND cmsContentNu.nodeId IS NULL
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
// Use a more efficient COUNT query
Sql<ISqlContext> sqlCountQuery = SqlMediaSourcesCount()
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media))
.WhereIn<ContentDto>(x => x.ContentTypeId, ids);
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
IContentCacheDataSerializer serializer =
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media);
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
IEnumerable<ContentSourceDto> dtos = GetMediaNodeDtos(sql);
foreach (ContentSourceDto row in dtos)
{
yield return CreateMediaNodeKit(row, serializer);
}
@@ -1030,4 +989,54 @@ WHERE cmsContentNu.nodeId IN (
return s;
}
private IEnumerable<ContentSourceDto> GetMediaNodeDtos(Sql<ISqlContext> sql)
{
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
// QueryPaged is very slow on large sites however, so use fetch if UsePagedSqlQuery is disabled.
IEnumerable<ContentSourceDto> dtos;
if (_nucacheSettings.Value.UsePagedSqlQuery)
{
// Use a more efficient COUNT query
Sql<ISqlContext>? sqlCountQuery = SqlMediaSourcesCount()
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media));
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
dtos = Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount);
}
else
{
dtos = Database.Fetch<ContentSourceDto>(sql);
}
return dtos;
}
private IEnumerable<ContentSourceDto> GetContentNodeDtos(Sql<ISqlContext> sql)
{
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
// QueryPaged is very slow on large sites however, so use fetch if UsePagedSqlQuery is disabled.
IEnumerable<ContentSourceDto> dtos;
if (_nucacheSettings.Value.UsePagedSqlQuery)
{
// Use a more efficient COUNT query
Sql<ISqlContext>? sqlCountQuery = SqlContentSourcesCount()
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document));
Sql<ISqlContext>? sqlCount =
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
dtos = Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount);
}
else
{
dtos = Database.Fetch<ContentSourceDto>(sql);
}
return dtos;
}
}
@@ -127,9 +127,25 @@ public class NuCacheContentService : RepositoryService, INuCacheContentService
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
{
scope.ReadLock(Constants.Locks.ContentTree);
scope.ReadLock(Constants.Locks.MediaTree);
scope.ReadLock(Constants.Locks.MemberTree);
if (contentTypeIds is null && mediaTypeIds is null && memberTypeIds is null)
{
scope.ReadLock(Constants.Locks.ContentTree,Constants.Locks.MediaTree,Constants.Locks.MemberTree);
}
if (contentTypeIds is not null && contentTypeIds.Any())
{
scope.ReadLock(Constants.Locks.ContentTree);
}
if (mediaTypeIds is not null && mediaTypeIds.Any())
{
scope.ReadLock(Constants.Locks.MediaTree);
}
if (memberTypeIds is not null && memberTypeIds.Any())
{
scope.ReadLock(Constants.Locks.MemberTree);
}
_repository.Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds);
@@ -56,7 +56,7 @@ internal class PublishedContent : PublishedContentBase
// add one property per property type - this is required, for the indexing to work
// if contentData supplies pdatas, use them, else use null
contentData.Properties.TryGetValue(propertyType.Alias, out PropertyData[]? pdatas); // else will be null
properties[i++] = new Property(propertyType, this, pdatas, _publishedSnapshotAccessor);
properties[i++] = new Property(propertyType, this, pdatas, _publishedSnapshotAccessor, propertyType.CacheLevel);
}
PropertiesArray = properties;
@@ -210,7 +210,16 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
// they require.
using (_contentStore.GetScopedWriteLock(_scopeProvider))
{
NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _);
NotifyLocked(
new[]
{
new ContentCacheRefresher.JsonPayload()
{
ChangeTypes = TreeChangeTypes.RefreshAll
}
},
out _,
out _);
}
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
@@ -852,6 +861,12 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
// contentStore is write-locked during changes - see note above, calls to this method are wrapped in contentStore.GetScopedWriteLock
foreach (ContentCacheRefresher.JsonPayload payload in payloads)
{
if (payload.Blueprint)
{
// Skip blueprints
continue;
}
_logger.LogDebug("Notified {ChangeTypes} for content {ContentId}", payload.ChangeTypes, payload.Id);
if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
@@ -9,6 +9,8 @@ public class SnapDictionary<TKey, TValue>
where TValue : class
where TKey : notnull
{
private static readonly TimeSpan _monitorTimeout = TimeSpan.FromSeconds(30);
// minGenDelta to be adjusted
// we may want to throttle collects even if delta is reached
// we may want to force collect if delta is not reached but very old
@@ -198,7 +200,12 @@ public class SnapDictionary<TKey, TValue>
throw new InvalidOperationException("Recursive locks not allowed");
}
Monitor.Enter(_wlocko, ref lockInfo.Taken);
Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken);
if (Monitor.IsEntered(_wlocko) is false)
{
throw new TimeoutException("Could not enter the monitor before timeout in SnapDictionary");
}
lock (_rlocko)
{
@@ -7,10 +7,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Umbraco.CSharpTest.Net.Collections" Version="14.906.1403.1085" />
<PackageReference Include="MessagePack" Version="2.3.85" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.16" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Umbraco.CSharpTest.Net.Collections" />
<PackageReference Include="MessagePack" />
<PackageReference Include="K4os.Compression.LZ4" />
<PackageReference Include="Newtonsoft.Json"/>
</ItemGroup>
<ItemGroup>
@@ -19,6 +19,8 @@ public class
{
private readonly ContentPermissions _contentPermissions;
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Document;
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsQueryStringHandler" /> class.
/// </summary>
@@ -47,7 +49,11 @@ public class
return Task.FromResult(true);
}
var argument = routeVal.ToString();
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
// It's the first one that'll be processed by the protected method so we should verify that.
var argument = routeVal.Count == 1
? routeVal.ToString()
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
if (!TryParseNodeId(argument, out nodeId))
{
@@ -18,6 +18,8 @@ public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<
{
private readonly MediaPermissions _mediaPermissions;
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Media;
/// <summary>
/// Initializes a new instance of the <see cref="MediaPermissionsQueryStringHandler" /> class.
/// </summary>
@@ -44,7 +46,11 @@ public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<
return Task.FromResult(true);
}
var argument = routeVal.ToString();
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
// It's the first one that'll be processed by the protected method so we should verify that.
var argument = routeVal.Count == 1
? routeVal.ToString()
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
if (!TryParseNodeId(argument, out var nodeId))
{
@@ -49,12 +49,18 @@ public abstract class PermissionsQueryStringHandler<T> : MustSatisfyRequirementA
/// </summary>
protected IEntityService EntityService { get; set; }
/// <summary>
/// Defaults to Unknown so all types are allowed, since Keys are unique across all node types this works,
/// but it if you are certain you are looking for a specific type this should be overwritten for DB query performance.
/// </summary>
protected virtual UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Unknown;
/// <summary>
/// Attempts to parse a node ID from a string representation found in a querystring value.
/// </summary>
/// <param name="argument">Querystring value.</param>
/// <param name="nodeId">Output parsed Id.</param>
/// <returns>True of node ID could be parased, false it not.</returns>
/// <returns>True of node ID could be parsed, false it not.</returns>
protected bool TryParseNodeId(string argument, out int nodeId)
{
// If the argument is an int, it will parse and can be assigned to nodeId.
@@ -75,7 +81,7 @@ public abstract class PermissionsQueryStringHandler<T> : MustSatisfyRequirementA
if (Guid.TryParse(argument, out Guid key))
{
nodeId = EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
nodeId = EntityService.GetId(key, KeyParsingFilterType).Result;
return true;
}
@@ -1,9 +1,12 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Controllers;
[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
public class AnalyticsController : UmbracoAuthorizedJsonController
{
private readonly IMetricsConsentService _metricsConsentService;

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