From fb73a6a66b6b38c1d84beb3c94abfe8d8f40cead Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 18 Oct 2023 12:17:29 +0200 Subject: [PATCH 001/146] Remove duplicate/incorrect Umbraco.Core project reference --- tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj index 90f301b2a1..c9275c6b94 100644 --- a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj +++ b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj @@ -12,6 +12,5 @@ - From b9c7de39ed74bce44960be87e920ef361787403f Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Wed, 18 Oct 2023 13:20:39 +0100 Subject: [PATCH 002/146] don't overwrite linkpicker title when you pick from tree IF the editor has just typed into the linktitle --- .../linkpicker/linkpicker.controller.js | 21 +++++++++++++++++-- .../linkpicker/linkpicker.html | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 98755c4090..3baba2b77a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -33,6 +33,8 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", selectedSearchResults: [] }; + $scope.userLinkNameInput = ''; + $scope.showTarget = $scope.model.hideTarget !== true; $scope.showAnchor = $scope.model.hideAnchor !== true; @@ -143,7 +145,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.currentNode.selected = true; $scope.model.target.id = args.node.id; $scope.model.target.udi = args.node.udi; - $scope.model.target.name = args.node.name; + if ($scope.oKToUpdateLinkTargetName()) { + $scope.model.target.name = args.node.name; + } if (args.node.id < 0) { $scope.model.target.url = "/"; @@ -167,7 +171,18 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", openMiniListView(args.node); } } + $scope.trackUserInput = function (userInput) { + $scope.userLinkNameInput = userInput; + } + $scope.oKToUpdateLinkTargetName = function () { + if (!$scope.userLinkNameInput || !$scope.model.target.name) { + return true; + } + else { + return false; + } + } $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { @@ -190,8 +205,10 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.model.target.id = media.id; $scope.model.target.udi = media.udi; - $scope.model.target.isMedia = true; + $scope.model.target.isMedia = true; + if ($scope.oKToUpdateLinkTargetName()) { $scope.model.target.name = media.name; + } $scope.model.target.url = media.image; editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index de9a0413d3..6a020f69ff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -49,6 +49,7 @@ placeholder="@placeholders_entername" class="umb-property-editor umb-textstring" ng-model="model.target.name" + ng-change="trackUserInput(model.target.name)" id="nodeNameLinkPicker"/> From d4d594bdb5e8d53cff35f29afa535f1e52b1d6e5 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Wed, 18 Oct 2023 23:44:58 +0200 Subject: [PATCH 003/146] 14523: Return focus to trigger element when close event occurs (#14991) * Return focus to trigger element when close event occurs * Add missing semi colon --- .../src/common/services/navigation.service.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 77b97545b6..2354b76c92 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -14,8 +14,8 @@ * */ function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState, backdropService) { - //the promise that will be resolved when the navigation is ready + var activeElement = undefined; var navReadyPromise = $q.defer(); //the main tree's API reference, this is acquired when the tree has initialized @@ -82,7 +82,6 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); } - break; } } @@ -134,9 +133,20 @@ function navigationService($routeParams, $location, $q, $injector, eventsService backdropService.close(); leftColumn.classList.remove(aboveClass); } + + returnFocusToTriggerElement(); } } + function returnFocusToTriggerElement() { + if(!activeElement) return; + + const elementToFocus = activeElement.querySelector(".umb-tree-item__inner .umb-button-ellipsis"); + document.body.classList.add("tabbing-active"); + elementToFocus.style.backgroundColor = "hsla(0,0%,100%,.8)"; + elementToFocus.focus(); + } + function showBackdrop() { var backDropOptions = { 'element': document.getElementById('leftcolumn') @@ -680,9 +690,12 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (appState.getMenuState("allowHideMenuDialog") === false) { return; } + if (showMenu) { this.showMenu({ skipDefault: true, node: appState.getMenuState("currentNode") }); } else { + activeElement = document.querySelector("#tree .active"); + closeBackdrop(); setMode("default"); } From 74d01c6573e6f0a0c1bf48c100fec0720045ff5e Mon Sep 17 00:00:00 2001 From: Georgina Bidder Date: Sat, 14 Oct 2023 15:24:08 +0100 Subject: [PATCH 004/146] our umbraco link works as expected. --- .../tests/DefaultConfig/HelpPanel/helpLinks.spec.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts index 882eaf1bbf..2239613571 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts @@ -36,6 +36,19 @@ test("Check the youtube link works as expected", async ({ page, umbracoUi }) => } else { await expect(page).toHaveURL(/.*UmbracoLearningBase/); + await page.close(); } }); + test("Check the Our Umbraco link works as expected", async ({ page, umbracoUi }) => { + // Action + await umbracoUi.clickElement(umbracoUi.getGlobalHelp()); + let ourUmbracoLink = await page.locator('[key="help_umbracoForum"]'); + await ourUmbracoLink.click(); + let ourUmbraco = page.waitForEvent("popup"); + let ourUmbracoPopup = await ourUmbraco; + + //Assert + await expect(ourUmbracoPopup).toHaveURL(/.*our.umbraco.com/); + await ourUmbracoPopup.close(); + }); }); \ No newline at end of file From c0b01807a4193c1f1de9325be7b2a0bc03fe1927 Mon Sep 17 00:00:00 2001 From: georgebid <91198628+georgebid@users.noreply.github.com> Date: Wed, 18 Oct 2023 23:13:48 +0100 Subject: [PATCH 005/146] New login page playwright test - Ensure Show/hide password button works as expected. (#14970) --- .../tests/DefaultConfig/Login/login.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts index d97de439d2..a079fa6124 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts @@ -66,4 +66,30 @@ test.describe('Login', () => { await expect(usernameField).toBeVisible(); await expect(passwordField).toBeVisible(); }); + + test('Ensure show/hide password button works', async ({page}) => { + + // Precondition + let error = page.locator('.text-error'); + await expect(error).toBeHidden(); + + let passwordField = await page.locator('#umb-passwordTwo'); + await expect(passwordField).toBeVisible(); + await expect(passwordField).toHaveAttribute('type', 'password'); + + // Action + await page.fill('#umb-passwordTwo', process.env.UMBRACO_USER_PASSWORD); + let showPassword = await page.locator('[key="login_showPassword"]'); + await showPassword.click(); + + // Assert + await expect(passwordField).toHaveAttribute('type', 'text'); + + // Action + let hidePassword = await page.locator('[key="login_hidePassword"]'); + await hidePassword.click(); + + // Assert + await expect(passwordField).toHaveAttribute('type', 'password'); + }); }); \ No newline at end of file From 74af2a07000f0de32756b23ee79e3cc11f358203 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:18:48 +0000 Subject: [PATCH 006/146] Bump @babel/traverse from 7.22.5 to 7.23.2 in /src/Umbraco.Web.UI.Client Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.5 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- src/Umbraco.Web.UI.Client/package-lock.json | 101 ++++++++++---------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index fc79e1fa29..a1a2b0fab1 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -100,12 +100,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -160,12 +161,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -326,22 +327,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -483,9 +484,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { "@babel/types": "^7.22.5" @@ -504,9 +505,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -551,13 +552,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -565,9 +566,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1719,33 +1720,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1754,13 +1755,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { From b26709b0afb07d12419874bcd53982b2fa65b04f Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Thu, 19 Oct 2023 03:30:35 +0200 Subject: [PATCH 007/146] Fix permissions for assign hostname (#14896) * Fix permissions for assign hostname * Reduced code duplication --- src/Umbraco.Core/Services/UserServiceExtensions.cs | 7 ++++++- .../Controllers/ContentController.cs | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index f17a266616..7399f37fe8 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -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]); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index d8c306bff4..b6b612466e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -2400,8 +2400,10 @@ public class ContentController : ContentControllerBase } // Validate permissions on node - EntityPermission? permission = _userService.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, node.Path); - if (permission?.AssignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false) + var permissions = _userService.GetAllPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, node.Path); + + if (permissions.Any(x => + x.AssignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) && x.EntityId == node.Id) == false) { HttpContext.SetReasonPhrase("Permission Denied."); return BadRequest("You do not have permission to assign domains on that node."); From 0c82b9b70016ecc31415690535243295cdb10071 Mon Sep 17 00:00:00 2001 From: Richard Thompson Date: Thu, 19 Oct 2023 17:00:54 +0100 Subject: [PATCH 008/146] Stopped the second tab being highlighted by default (#14969) * The second button shouldn't be highlighted automatically if it's in the second tab otherwise both the first and second tabs are highlighted * Corrected the logic --- .../directives/components/forms/umbfocuslock.directive.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js index f3a0451191..69c11a11cc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -124,7 +124,8 @@ } else if(defaultFocusedElement === null ){ // If the first focusable elements are either items from the umb-sub-views-nav menu or the umb-button-ellipsis we most likely want to start the focus on the second item - var avoidStartElm = focusableElements.findIndex(elm => elm.classList.contains('umb-button-ellipsis') || elm.classList.contains('umb-sub-views-nav-item__action') || elm.classList.contains('umb-tab-button')); + // We don't want to focus the second button if it's in a tab otherwise the second tab is highlighted as well as the first tab + var avoidStartElm = focusableElements.findIndex(elm => elm.classList.contains('umb-button-ellipsis') || elm.classList.contains('umb-sub-views-nav-item__action') || (elm.classList.contains('umb-tab-button') && !elm.parent.classList.contains('umb-tab'))); if(avoidStartElm === 0) { focusableElements[1].focus(); From f8b66aaf507ae575bce0fb85ee55dbab38fb15f8 Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Sat, 9 Sep 2023 23:10:48 +0200 Subject: [PATCH 009/146] Fix issue with pressing enter on color picker labels Prevent form submission when pressing enter in color picker labels, instead execute "add" code. Improve validation highlights. --- .../colorpicker/colorpicker.prevalues.html | 72 ++-- .../multicolorpicker.controller.js | 393 ++++++++++-------- 2 files changed, 255 insertions(+), 210 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index 69a2e42f19..5f6f784f55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -1,38 +1,46 @@
-
-
+
+
- - + + - - -
-
- - - -
+ +
-
-
- -
-
-
-
#{{item.value}}
- -
-
-
- - -
-
+
+ + +
+
+
+
+ +
+
+
+
#{{item.value}}
+ +
+
+
+ + +
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index 5406927d38..b16722e7de 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -1,191 +1,228 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController", - function ($scope, angularHelper, $element, eventsService) { + function ($scope, angularHelper, $element, eventsService) { - const vm = this; + const vm = this; - vm.add = add; - vm.remove = remove; - vm.edit = edit; - vm.cancel = cancel; + vm.add = add; + vm.addOnEnter = addOnEnter; + vm.validateLabel = validateLabel; + vm.remove = remove; + vm.edit = edit; + vm.cancel = cancel; - vm.show = show; - vm.hide = hide; - vm.change = change; + vm.show = show; + vm.hide = hide; + vm.change = change; - vm.labelEnabled = false; - vm.editItem = null; + vm.labelEnabled = false; + vm.editItem = null; - // NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. - const defaultColor = "000000"; - const defaultLabel = null; + // NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. + const defaultColor = "000000"; + const defaultLabel = null; - $scope.newColor = defaultColor; - $scope.newLabel = defaultLabel; - $scope.hasError = false; - $scope.focusOnNew = false; + $scope.newColor = defaultColor; + $scope.newLabel = defaultLabel; + $scope.colorHasError = false; + $scope.labelHasError = false; + $scope.focusOnNew = false; - $scope.options = { - type: "color", - color: defaultColor, - allowEmpty: false, - showAlpha: false - }; + $scope.options = { + type: "color", + color: defaultColor, + allowEmpty: false, + showAlpha: false + }; - function hide() { - // show the add button - $element.find(".btn.add").show(); + function hide() { + // show the add button + $element.find(".btn.add").show(); + } + + function show() { + // hide the add button + $element.find(".btn.add").hide(); + } + + function change(color) { + angularHelper.safeApply($scope, function () { + if (color) { + $scope.newColor = color.toHexString().trimStart("#"); + $scope.colorHasError = !colorIsValid(); } + }); + } - function show() { - // hide the add button - $element.find(".btn.add").hide(); - } - - function change(color) { - angularHelper.safeApply($scope, function () { - if (color) { - $scope.newColor = color.toHexString().trimStart("#"); - } - }); - } - - var evts = []; - evts.push(eventsService.on("toggleValue", function (e, args) { - if (args.inputId === "useLabel") { - vm.labelEnabled = args.value; - } - })); - - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - - if (!Utilities.isArray($scope.model.value)) { - //make an array from the dictionary - var items = []; - for (var i in $scope.model.value) { - var oldValue = $scope.model.value[i]; - if (Object.prototype.hasOwnProperty.call(oldValue, "value")) { - items.push({ - value: oldValue.value, - label: oldValue.label, - sortOrder: oldValue.sortOrder, - id: i - }); - } else { - items.push({ - value: oldValue, - label: oldValue, - sortOrder: oldValue.sortOrder, - id: i - }); - } - } - - //ensure the items are sorted by the provided sort order - items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); - - //now make the editor model the array - $scope.model.value = items; - } - - // ensure labels - for (var ii = 0; ii < $scope.model.value.length; ii++) { - var item = $scope.model.value[ii]; - item.label = Object.prototype.hasOwnProperty.call(item, "label") ? item.label : item.value; - } - - function validLabel(label) { - return label !== null && typeof label !== "undefined" && label !== "" && label.length && label.length > 0; - } - - function remove(item, evt) { - - evt.preventDefault(); - - $scope.model.value = _.reject($scope.model.value, function (x) { - return x.value === item.value && x.label === item.label; - }); - - setDirty(); - } - - function add(evt) { - evt.preventDefault(); - - if ($scope.newColor) { - var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor; - var exists = _.find($scope.model.value, function (item) { - return item != vm.editItem && (item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase()); - }); - if (!exists) { - if (vm.editItem == null) { - $scope.model.value.push({ - value: $scope.newColor, - label: newLabel - }); - } else { - - if(vm.editItem.value === vm.editItem.label && vm.editItem.value === newLabel) { - vm.editItem.label = $scope.newColor; - - } - else { - vm.editItem.label = newLabel; - } - - vm.editItem.value = $scope.newColor; - - vm.editItem = null; - } - - $scope.newLabel = ""; - $scope.hasError = false; - $scope.focusOnNew = true; - setDirty(); - return; - } - - // there was an error, do the highlight (will be set back by the directive) - $scope.hasError = true; - } - } - - function edit(item, evt) { - evt.preventDefault(); - - vm.editItem = item; - - $scope.newColor = item.value; - $scope.newLabel = item.label; - } - - function cancel(evt) { - evt.preventDefault(); - - vm.editItem = null; - $scope.newColor = defaultColor; - $scope.newLabel = defaultLabel; - } - - function setDirty() { - if (vm.modelValueForm) { - vm.modelValueForm.selectedColor.$setDirty(); - } - } - - $scope.sortableOptions = { - axis: 'y', - containment: 'parent', - cursor: 'move', - //handle: ".handle, .thumbnail", - items: '> div.control-group', - tolerance: 'pointer', - update: function () { - setDirty(); - } - }; + var evts = []; + evts.push(eventsService.on("toggleValue", function (e, args) { + if (args.inputId === "useLabel") { + vm.labelEnabled = args.value; + } + })); + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } }); + + if (!Utilities.isArray($scope.model.value)) { + //make an array from the dictionary + var items = []; + for (var i in $scope.model.value) { + var oldValue = $scope.model.value[i]; + if (Object.prototype.hasOwnProperty.call(oldValue, "value")) { + items.push({ + value: oldValue.value, + label: oldValue.label, + sortOrder: oldValue.sortOrder, + id: i + }); + } else { + items.push({ + value: oldValue, + label: oldValue, + sortOrder: oldValue.sortOrder, + id: i + }); + } + } + + //ensure the items are sorted by the provided sort order + items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); + + //now make the editor model the array + $scope.model.value = items; + } + + // ensure labels + for (var ii = 0; ii < $scope.model.value.length; ii++) { + var item = $scope.model.value[ii]; + item.label = Object.prototype.hasOwnProperty.call(item, "label") ? item.label : item.value; + } + + function remove(item, evt) { + + evt.preventDefault(); + + $scope.model.value = _.reject($scope.model.value, function (x) { + return x.value === item.value && x.label === item.label; + }); + + setDirty(); + } + + function colorIsValid() { + var colorExists = _.find($scope.model.value, function (item) { + return item != vm.editItem && item.value.toUpperCase() === $scope.newColor.toUpperCase(); + }); + + return colorExists ? false : true; + } + + function getLabel() { + var validLabel = $scope.newLabel !== null && typeof $scope.newLabel !== "undefined" && $scope.newLabel !== "" && $scope.newLabel.length && $scope.newLabel.length > 0; + return validLabel ? $scope.newLabel : $scope.newColor; + } + + function labelIsValid() { + var label = getLabel(); + label = label.toUpperCase(); + + var labelExists = _.find($scope.model.value, function (item) { + return item != vm.editItem && item.label.toUpperCase() === label; + }); + + return labelExists ? false : true; + } + + function validateLabel() { + $scope.labelHasError = !labelIsValid(); + } + + function addOnEnter(evt) { + if (evt.keyCode === 13) { + add(evt); + } + } + + function add(evt) { + evt.preventDefault(); + + if ($scope.newColor) { + + $scope.colorHasError = !colorIsValid(); + $scope.labelHasError = !labelIsValid(); + + if ($scope.labelHasError || $scope.colorHasError) { + return; + } + + var newLabel = getLabel(); + + if (vm.editItem == null) { + $scope.model.value.push({ + value: $scope.newColor, + label: newLabel + }); + } else { + + if (vm.editItem.value === vm.editItem.label && vm.editItem.value === newLabel) { + vm.editItem.label = $scope.newColor; + + } + else { + vm.editItem.label = newLabel; + } + + vm.editItem.value = $scope.newColor; + + vm.editItem = null; + } + + $scope.newLabel = ""; + $scope.colorHasError = false; + $scope.labelHasError = false; + $scope.focusOnNew = true; + setDirty(); + return; + + } + } + + function edit(item, evt) { + evt.preventDefault(); + + vm.editItem = item; + + $scope.newColor = item.value; + $scope.newLabel = item.label; + } + + function cancel(evt) { + evt.preventDefault(); + + vm.editItem = null; + $scope.newColor = defaultColor; + $scope.newLabel = defaultLabel; + } + + function setDirty() { + if (vm.modelValueForm) { + vm.modelValueForm.selectedColor.$setDirty(); + } + } + + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + cursor: 'move', + //handle: ".handle, .thumbnail", + items: '> div.control-group', + tolerance: 'pointer', + update: function () { + setDirty(); + } + }; + + }); From f3028a00b3e29f8f0c8ee86ce4f2913305300a0d Mon Sep 17 00:00:00 2001 From: Emma Garland Date: Sat, 21 Oct 2023 11:00:50 +0100 Subject: [PATCH 010/146] Added word-wrap so block item doesn't extend over the whole line --- .../src/views/components/blockcard/umb-block-card.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less index a40983e07c..7be8008b8a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less @@ -104,6 +104,7 @@ umb-block-card { font-size: 14px; color: @ui-action-type; margin: 0 16px -1px; + word-wrap: break-word; } .__subname { color: @gray-4; From 16c5c4c4e8e4af74d8da25fbcc6e2a5268f66062 Mon Sep 17 00:00:00 2001 From: Emma Garland Date: Sat, 21 Oct 2023 11:38:12 +0100 Subject: [PATCH 011/146] Fixed warning SA1116 (The parameters should begin on the line after the declaration...), whenever in ctor --- src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs b/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs index f29e0465f5..d4d63acf2b 100644 --- a/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs @@ -10,7 +10,8 @@ public abstract class QueryOptionBase private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IRequestRoutingService _requestRoutingService; - public QueryOptionBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, + public QueryOptionBase( + IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService) { _publishedSnapshotAccessor = publishedSnapshotAccessor; From ed006931a49521b1749d322483e67b3396d8a8a8 Mon Sep 17 00:00:00 2001 From: Richard Thompson Date: Sun, 22 Oct 2023 17:48:52 +0100 Subject: [PATCH 012/146] Grant all users access to the content recycle bin (#14977) * Grant all users access to the content recycle bin If users don't have access to the root node of the content section they now get access to the recycle bin. * Content editors get access to their nodes and recycle bin All users with access to the content section will get access to the recycle bin as well as the nodes they have access to, even if they don't have access to the root of the content section --- .../Trees/ContentTreeControllerBase.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs index 930c51c713..84e7692d0c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs @@ -384,8 +384,8 @@ public abstract class ContentTreeControllerBase : TreeController, ITreeNodeContr /// protected sealed override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { - //check if we're rendering the root - if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root)) + // check if we're rendering the root + if (id == Constants.System.RootString) { var altStartId = string.Empty; @@ -394,7 +394,7 @@ public abstract class ContentTreeControllerBase : TreeController, ITreeNodeContr altStartId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId); } - //check if a request has been made to render from a specific start node + // check if a request has been made to render from a specific start node if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.RootString) { @@ -402,16 +402,17 @@ public abstract class ContentTreeControllerBase : TreeController, ITreeNodeContr } ActionResult nodesResult = GetTreeNodesInternal(id, queryStrings); - if (!(nodesResult.Result is null)) + + if (nodesResult.Result is not null) { return nodesResult.Result; } TreeNodeCollection? nodes = nodesResult.Value; - //only render the recycle bin if we are not in dialog and the start id is still the root - //we need to check for the "application" key in the queryString because its value is required here, - //and for some reason when there are no dashboards, this parameter is missing + // only render the recycle bin if we are not in dialog and the start id is still the root + // we need to check for the "application" key in the queryString because its value is required here, + // and for some reason when there are no dashboards, this parameter is missing if (IsDialog(queryStrings) == false && id == Constants.System.RootString && queryStrings.HasKey("application")) { From d7497314132cbbb67a500fbaffd9e4036a97b09f Mon Sep 17 00:00:00 2001 From: Lucas Bach Bisgaard Date: Sun, 22 Oct 2023 19:25:51 +0200 Subject: [PATCH 013/146] Condition on the userState instead of the dates. (#14950) Co-authored-by: Lucas Bach Bisgaard --- src/Umbraco.Web.BackOffice/Controllers/UsersController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 5ba735c98b..c855a87ea4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -532,7 +532,7 @@ public class UsersController : BackOfficeNotificationsController { // first validate the username if we're showing it ActionResult userResult = CheckUniqueUsername(userSave.Username, - u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + u => u.UserState != UserState.Invited); if (userResult.Result is not null) { return userResult.Result; @@ -540,7 +540,7 @@ public class UsersController : BackOfficeNotificationsController } IUser? user = CheckUniqueEmail(userSave.Email, - u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + u => u.UserState != UserState.Invited); if (ModelState.IsValid == false) { From 71c00579e4c6c8743ccff3d6f13702c48e309e5e Mon Sep 17 00:00:00 2001 From: Ambert van Unen Date: Wed, 4 Oct 2023 18:18:03 +0200 Subject: [PATCH 014/146] Added aria-labelledby so no longer 'empty' --- .../directives/components/buttons/umbtogglegroup.directive.js | 1 + .../src/views/components/buttons/umb-toggle-group.html | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js index 092908cd8d..7e9bbe942f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtogglegroup.directive.js @@ -73,6 +73,7 @@ Use this directive to render a group of toggle buttons. function link(scope, el, attr, ctrl) { for(let i = 0; i < scope.items.length; i++) { scope.items[i].inputId = "umb-toggle-group-item_" + String.CreateGuid(); + scope.items[i].labelId = "umb-toggle-group-item_" + String.CreateGuid(); } scope.change = function(item) { if (item.disabled) { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html index 4576a30418..637404718b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html @@ -4,10 +4,11 @@ checked="item.checked" disabled="item.disabled" input-id="{{item.inputId}}" + aria-labelledby="{{item.labelId}}" on-click="change(item)">
-
+
{{ item.description }}
From 7c59a81e5c6f0bb239bfdf157a8584cbfeb85435 Mon Sep 17 00:00:00 2001 From: Lucas Bach Bisgaard Date: Sat, 21 Oct 2023 21:46:47 +0200 Subject: [PATCH 015/146] Bug fix for compistion on member and media give 404 --- .../components/umbgroupsbuilder.directive.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 6ba0aec2ee..2faa9cbeda 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -518,8 +518,18 @@ editorService.close(); } }; - editorService.documentTypeEditor(editor); + switch (scope.contentType) { + case "documentType": + editorService.documentTypeEditor(editor); + break; + case "mediaType": + editorService.mediaTypeEditor(editor); + break; + case "memberType": + editorService.memberTypeEditor(editor); + break; + } }; /* ---------- TABS ---------- */ From 223598b037408f4204f1175a092296a73186a262 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:40:33 +0000 Subject: [PATCH 016/146] Bump tinymce from 6.5.1 to 6.7.1 in /src/Umbraco.Web.UI.Client Bumps [tinymce](https://github.com/tinymce/tinymce/tree/HEAD/modules/tinymce) from 6.5.1 to 6.7.1. - [Changelog](https://github.com/tinymce/tinymce/blob/develop/modules/tinymce/CHANGELOG.md) - [Commits](https://github.com/tinymce/tinymce/commits/6.7.1/modules/tinymce) --- updated-dependencies: - dependency-name: tinymce dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- src/Umbraco.Web.UI.Client/package-lock.json | 8 ++++---- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index a1a2b0fab1..1f41954d6e 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -39,7 +39,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.5.1", + "tinymce": "6.7.1", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" @@ -16327,9 +16327,9 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "node_modules/tinymce": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.5.1.tgz", - "integrity": "sha512-J67fxJiX3tjvVqer1dg1+cOxMeE2P55ESGhaakvqGPbAUU45HnCMLSioaOsxV1KfcXustw9WJo0rtn1SNQlVKQ==" + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.7.1.tgz", + "integrity": "sha512-SIGJgWk2d/X59VbO+i81QfNx2EP1P5t+sza2/1So3OLGtmMBhEJMag7sN/Mo8sq4s0niwb65Z51yLju32jP11g==" }, "node_modules/to-absolute-glob": { "version": "2.0.2", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 14d148de6c..5c4ff50a19 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -51,7 +51,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.5.1", + "tinymce": "6.7.1", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" From b423329d978a75aebd28124dea7c2137cd6beb1f Mon Sep 17 00:00:00 2001 From: Nurhak Kaya Date: Mon, 23 Oct 2023 23:20:04 +0100 Subject: [PATCH 017/146] Turkish language support changes (#15005) * Turkish language support changes * small updates and corrections --------- Co-authored-by: Nurhak Kaya --- .../EmbeddedResources/Lang/tr.xml | 101 ++++++++++-------- 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml index 1e1504f44a..c188b8c216 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml @@ -322,19 +322,19 @@ Yeni %0% 'i nerede oluşturmak istiyorsunuz - Altında bir öğe oluşturun + Şu dizin altında bir öğe oluşturun: İçerik şablonu yapmak istediğiniz belge türünü seçin Bir klasör adı girin Bir tür ve başlık seçin İzinler altında İzin verilen alt düğüm türlerini düzenleyerek Ayarlar bölümündeki Belge Türleri 'nde etkinleştirmelisiniz.]]> - Ayarlar bölümündeki Belge Türleri 'nde oluşturmanız gerekir.]]> + Ayarlar bölümündeki Belge Türleri 'nde oluşturmanız gerekiyor.]]> İçerik ağacında seçilen sayfa, altında herhangi bir sayfanın oluşturulmasına izin vermiyor. - Bu belge türü için izinleri düzenleyin + Şu belge türü için izinleri düzenleyin Yeni bir belge türü oluşturun İzinler altında Kök olarak izin ver seçeneğini değiştirerek, Ayarlar bölümündeki Belge Türleri 'nde etkinleştirmeniz gerekir. ]]> İzinler altında İzin verilen alt düğüm türlerini düzenleyerek Ayarlar bölümündeki Medya Türleri 'nde etkinleştirmelisiniz. .]]> Ağaçtaki seçili ortam, altında başka bir ortamın oluşturulmasına izin vermiyor. - Bu medya türü için izinleri düzenleyin + Şu medya türü için izinleri düzenleyin: Şablonsuz Belge Türü Yeni klasör Yeni veri türü @@ -360,11 +360,11 @@ Kal Değişiklikleri sil Kaydedilmemiş değişiklikleriniz var - Bu sayfadan ayrılmak istediğinizden emin misiniz? - kaydedilmemiş değişiklikleriniz var + Bu sayfadan ayrılmak istediğinizden emin misiniz? - Kaydedilmemiş değişiklikleriniz var Yayınlama, seçili öğelerin sitede görünür olmasını sağlar. - Yayından kaldırıldığında, seçili öğeler ve bunların tüm alt öğeleri siteden kaldırılır. - Yayından kaldırıldığında bu sayfa ve tüm soyundan gelenler siteden kaldırılır. - Kaydedilmemiş değişiklikleriniz var. Belge Türünde değişiklik yapmak değişiklikleri geçersiz kılacaktır. + Yayından kaldırma, seçili öğeler ile birlikte bu öğelerin tüm alt öğelerini de siteden kaldırır. + Yayından kaldırma, bu seçili öğe ile birlikte onun tüm alt öğelerini de siteden kaldırır. + Kaydedilmemiş değişiklikleriniz var. Belge Türü'nde değişiklik yapmak bu kaydedilmemiş değişikliklerinizi geçersiz kılacaktır. Bitti @@ -428,7 +428,7 @@ İzinlerini ayarlamak istediğiniz kullanıcı gruplarını seçin Geri dönüşüm kutusundaki öğeler artık siliniyor. Lütfen bu işlem yapılırken bu pencereyi kapatmayın Geri dönüşüm kutusu artık boş - Öğeler geri dönüşüm kutusundan silindiğinde sonsuza kadar kaybolacaklar + Öğeler geri dönüşüm kutusundan silindiğinde bir daha geri gelmeyecek şekilde ortadan kaybolacaklar regexlib.com 'un web hizmeti şu anda bazı sorunlar yaşıyor ve biz üzerinde kontrol yok. Bu rahatsızlıktan dolayı çok üzgünüz.]]> Bir form alanına doğrulama eklemek için normal ifade arayın. Örnek: 'e-posta,'posta kodu','URL' Makroyu Kaldır @@ -608,15 +608,16 @@ Bu özellik geçersiz + Seçenekler Hakkında İşlem İşlemler Ekle - Takma reklam + Takma isim Tümü Emin misiniz? Geri - Genel bakışa dön + Gözden geçirmeye geri dön Kenarlık İptal Hücre kenar boşluğu @@ -624,14 +625,16 @@ Temizle Kapat Pencereyi Kapat + Pencereyi Kapat Yorum - Onayla + Doğrula Sınırla Oranları sınırlayın İçerik Devam et Kopyala Oluştur + Bölümü kırp Veritabanı Tarih Varsayılan @@ -641,7 +644,7 @@ Tasarım Sözlük Boyutlar - Sil + Gözden çıkar Aşağı İndir Düzenle @@ -654,6 +657,7 @@ İlk Odak noktası Genel + Genel Gruplar Grup Yükseklik @@ -661,7 +665,7 @@ Gizle Geçmiş Simge - Kimlik + Belirleyici Numara İçe Aktar Yalnızca bu klasörü ara Bilgi @@ -692,7 +696,6 @@ Kapalı Tamam - Seçenekler Açık veya Sıralama ölçütü @@ -854,12 +857,12 @@ Umbraco'yu çalıştırmaya ve paketleri kurmaya hazırsınız!]]> Klasör sorununu çözme - ASP.NET ile ilgili sorunlar ve klasör oluşturma hakkında daha fazla bilgi için bu bağlantıyı izleyin + ASP.NET ile ilgili sorunlar ve klasör oluşturma hakkında daha fazla bilgi için bu bağlantıyı takip edin Klasör izinlerini ayarlama Sıfırdan başlamak istiyorum @@ -895,12 +898,12 @@ ]]> Runway Nedir - Adım 1/5 Lisansı kabul edin + Adım 1/5 Lisansın kabulü Adım 2/5: Veritabanı yapılandırması - Adım 3/5: Dosya İzinlerini Doğrulama - 4. Adım: Umbraco güvenliğini kontrol edin - Adım 5/5: Umbraco başlamanıza hazır - Umbraco'yu seçtiğiniz için teşekkür ederiz + Adım 3/5: Dosya izinlerini doğrulama + Adım 4/5: Umbraco güvenliğinin kontrolü + Adım 5/5: Umbraco, üzerinde çalışmaya başlamanız için hazır + Umbraco'yu seçtiğiniz için teşekkürler Yeni sitenize göz atın Runway'i kurdunuz, öyleyse neden yeni web sitenizin nasıl göründüğüne bakmıyorsunuz.]]> @@ -933,31 +936,31 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey Kültür Adı - Boştaydınız ve çıkış otomatik olarak içinde gerçekleşecek + Herhangi bir işlem yapmadınız ve sistemden çıkış otomatik olarak şu belirtilen süre içinde gerçekleşecek Çalışmanızı kaydetmek için şimdi yenileyin Mutlu Pazarlar - Mutlu manik Pazartesi - Mutlu salı günleri - Harika Çarşamba Günleri - Mutlu, gök gürültülü Perşembe - Mutlu Cuma Günü - Mutlu Yıllar + Muhteşem Pazartesiler + Mükemmel Salılar + Harika Çarşambalar + Şahane Perşembeler + Mutlu Cumalar + Eğlenceli Cumartesiler Aşağıda oturum açın ile oturum açın Oturum zaman aşımına uğradı © 2015 - %0%
Umbraco.com

]]>
Şifrenizi mi unuttunuz? - Parolanızı sıfırlamak için bir bağlantıyla belirtilen adrese bir e-posta gönderilecektir - Kayıtlarımızla eşleşirse, şifre sıfırlama talimatlarını içeren bir e-posta, belirtilen adrese gönderilecektir + Şifrenizi yenilemeniz için belirtilen adrese bir bağlantı bilgisi gönderilecektir + Eğer kayıtlarımızla eşleşirse, şifre yenileme talimatlarını içeren bir e-posta belirtilen adrese gönderilecektir Şifreyi göster Şifreyi gizle Giriş formuna dön Lütfen yeni bir şifre girin Şifreniz güncellendi Tıkladığınız bağlantı geçersiz veya süresi dolmuş - Umbraco: Şifreyi Sıfırla + Umbraco: Şifreyi Yenile @@ -996,7 +999,7 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey

- Şifre sıfırlama istendi + Şifre yenileme istendi

Umbraco arka ofisinde oturum açmak için kullanıcı adınız: %0% @@ -1007,7 +1010,7 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey - Şifrenizi sıfırlamak için bu bağlantıyı tıklayın + Şifrenizi yenilemek için bu bağlantıyı tıklayın @@ -1041,38 +1044,46 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey ]]> + Umbraco: Güvenlik Kodu + Güvenlik kodunuz: %0% + Son adım + 2-aşamalı kimlik doğrulamayı etkinleştirdiniz ve kimliğinizi doğrulamanız gerekmekte. + Lütfen bir tane 2-aşamalı kimlik doğrulama hizmet sağlayıcı seçin + Doğrulama kodu + Lütfen doğrulama kodunu girin + Geçersiz kod girildi - Dashboard + Gösterge Paneli Bölümler İçerik Yukarıdaki sayfayı seçin ... %0%,%1% konumuna kopyalandı - %0% belgesinin aşağıya kopyalanacağı yeri seçin + %0% belgesinin kopyalanacağı yeri aşağıdan seçin %0%,%1% konumuna taşındı - %0% belgesinin aşağıya taşınacağı yeri seçin + %0% belgesinin taşınacağı yeri aşağıdan seçin , yeni içeriğinizin kökü olarak seçildi, aşağıdaki 'tamam'ı tıklayın. Henüz düğüm seçilmedi, lütfen 'tamam'ı tıklamadan önce yukarıdaki listeden bir düğüm seçin - Geçerli düğüme, türü nedeniyle seçilen düğüm altında izin verilmiyor + Geçerli düğüme, türü nedeniyle, seçilen düğüm altında izin verilmiyor Geçerli düğüm, alt sayfalarından birine taşınamaz - Geçerli düğüm kökte bulunamaz + Geçerli düğüm kök düğüm seviyesinde bulunamaz 1 veya daha fazla alt belge üzerinde yetersiz izniniz olduğundan işleme izin verilmiyor. - Kopyalanan öğeleri orijinalle ilişkilendir + Kopyalanan öğeleri asıl öğe ile ilişkilendir %0%]]> için bildiriminizi seçin - için bildirim ayarları kaydedildi + Bildirim ayarları bu belirtilen öğe için kaydedildi Date: Wed, 25 Oct 2023 17:43:04 +0300 Subject: [PATCH 018/146] Contributing section seperation (#14849) --- .github/CONTRIBUTING.md | 257 ++---------------- .github/contributing-before-you-start.md | 54 ++++ .github/contributing-core-collabs-team.md | 28 ++ .github/contributing-creating-a-pr.md | 51 ++++ .github/contributing-first-issue.md | 93 +++++++ .../contributing-other-ways-to-contribute.md | 10 + 6 files changed, 258 insertions(+), 235 deletions(-) create mode 100644 .github/contributing-before-you-start.md create mode 100644 .github/contributing-core-collabs-team.md create mode 100644 .github/contributing-creating-a-pr.md create mode 100644 .github/contributing-first-issue.md create mode 100644 .github/contributing-other-ways-to-contribute.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 06fd873638..a984746cc1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,268 +4,55 @@ These contribution guidelines are mostly just that - guidelines, not rules. This is what we've found to work best over the years, but if you choose to ignore them, we still love you! 💖 Use your best judgement, and feel free to propose changes to this document in a pull request. -## Coding not your thing? Or want more ways to contribute? +## Getting Started +We have a guide on [what to consider before you start](contributing-before-you-start.md) and more detailed guides at the end of this article. -This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved] - -If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better. - -We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy. - -## Table of contents - -- [Before you start](#before-you-start) - * [Code of Conduct](#code-of-conduct) - * [What can I contribute?](#what-can-i-contribute) - + [Making larger changes](#making-larger-changes) - + [Pull request or package?](#pull-request-or-package) - + [Unwanted changes](#unwanted-changes) - + [Ownership and copyright](#ownership-and-copyright) -- [Finding your first issue: Up for grabs](#finding-your-first-issue-up-for-grabs) -- [Making your changes](#making-your-changes) - + [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository) - + [Style guide](#style-guide) - + [Questions?](#questions) -- [Creating a pull request](#creating-a-pull-request) -- [The review process](#the-review-process) - * [Dealing with requested changes](#dealing-with-requested-changes) - + [No longer available?](#no-longer-available) - * [The Core Collaborators team](#the-core-collaborators-team) - -## Before you start - - -### Code of Conduct - -This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct]. - -### What can I contribute? - -We categorise pull requests (PRs) into two categories: - -| PR type | Definition | -| --------- | ------------------------------------------------------------ | -| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. | -| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). | - -We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process]. - -We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations. - -Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge. - -#### Making larger changes - -[making larger changes]: #making-larger-changes - -Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented. - -If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress. - -#### Pull request or package? - -[pr or package]: #pull-request-or-package - -If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes]. - -If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS. - -#### Unwanted changes -While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused. -Of course this will depend heavily on the specific change, but please take the following examples in mind. - -- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you. -- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues. -- **Changes to obsolete code and/or property editors ✍️** -- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it. -- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files! -- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core. - -While these are only a few examples, it is important to ask yourself these questions before making a pull request: - -- How many will benefit from this change? -- Are there other ways to achieve this? And if so, how do they compare? -- How maintainable is the change? -- What would be the effort to test it properly? -- Do the benefits outweigh the risks? - -#### Ownership and copyright - -It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code. - -Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS. - -If you're not sure, leave a note on your contribution and we will be happy to guide you. - -When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards. - -## Finding your first issue: Up for grabs - -Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out. - -If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has. - -## Making your changes - -Great question! The short version goes like this: +The following steps are a quick-start guide: 1. **Fork** - Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo] + Create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS) ![Fork the repository](img/forkrepository.png) -1. **Clone** +2. **Clone** - When GitHub has created your fork, you can clone it in your favorite Git tool + When GitHub has created your fork, you can clone it in your favorite Git tool or on the command line with `git clone https://github.com/[YourUsername]/Umbraco-CMS`. ![Clone the fork](img/clonefork.png) -1. **Switch to the correct branch** +3. **Switch to the correct branch** Switch to the `contrib` branch -1. **Build** +4. **Build** - Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs]. + Build your fork of Umbraco locally as described in the build documentation, you can [build with any IDE that support dotnet or the command line](BUILD.md). -1. **Branch** +5. **Branch** - Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first. + Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp/12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first. -1. **Change** +6. **Change** - Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions]. + Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](contributing-first-issue.md#questions). -1. **Commit and push** +7. **Commit and push** Done? Yay! 🎉 Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub. -#### Keeping your Umbraco fork in sync with the main repository -[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository +8. **Create pull request** -Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes. + On GitHub, in your forked repository (`https://github.com/[YourUsername]/Umbraco-CMS`) you will see a banner saying that you pushed a new branch and a button to make a pull request. Tap the button and follow the instuctions. -To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once: + Want to read further? [Creating a pull request and what happens next](contributing-creating-a-pr.md). -``` -git remote add upstream https://github.com/umbraco/Umbraco-CMS.git -``` +## Further contribution guides -Then when you want to get the changes from the main repository: +- [Before you start](contributing-before-you-start.md) +- [Finding your first issue: Up for grabs](contributing-before-you-start.md) +- [Contributing to the new backoffice](https://github.com/umbraco/Umbraco-CMS/blob/v14/dev/.github/New%20BackOffice%20-%20README.md) +- [Other ways to contribute](contributing-other-ways-to-contribute.md) -``` -git fetch upstream -git rebase upstream/contrib -``` - -In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed. - -[More information on how this works can be found on the thoughtbot blog.][sync fork ext] - -#### Style guide - -To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. - -That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. - -#### Questions? -[questions]: #questions - -You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away: - -- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward. -- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs]. -- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction. - -## Creating a pull request - -Exciting! You're ready to show us your changes. - -We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier. - -GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. -![Create a pull request](img/createpullrequest.png) - -We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting. - -Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise. - -## The review process -[review process]: #the-review-process - -You've sent us your first contribution - congratulations! Now what? - -The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes. - -You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides. - -You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes: - -- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco -- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible -- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see -- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package] - -### Dealing with requested changes - -If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco! - -#### No longer available? - -We understand you have other things to do and can't just drop everything to help us out. - -So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time. - -If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it. - -There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository. - -### The Core Collaborators team -[Core collabs]: #the-core-collaborators-team - -The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time: - -- [Busra Sengul][Busra Sengul] -- [Emma Garland][Emma Garland] -- [George Bidder][George Bidder] -- [Jason Elkin][Jason Elkin] -- [Laura Neto][Laura Neto] -- [Michael Latouche][Michael Latouche] -- [Sebastiaan][Sebastiaan] - - -These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged. - - - - - -[MIT license]: ../LICENSE.md "Umbraco's license declaration" -[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio" -[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code" - - - -[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile" -[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile" -[George Bidder]: https://github.com/georgebid "George's GitHub profile" -[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile" -[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile" -[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile" -[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile" -[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile" -[ Umbrabot ]: https://github.com/umbrabot -[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow" -[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated" -[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests" -[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/ -[get involved]: https://community.umbraco.com/get-involved/ -[docs repo]: https://github.com/umbraco/UmbracoDocs -[code of conduct]: https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md -[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs -[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS -[issues]: https://github.com/umbraco/Umbraco-CMS/issues -[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions diff --git a/.github/contributing-before-you-start.md b/.github/contributing-before-you-start.md new file mode 100644 index 0000000000..1668cf9541 --- /dev/null +++ b/.github/contributing-before-you-start.md @@ -0,0 +1,54 @@ +## Before you start + + +### Code of Conduct + +This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct]. + +### What can I contribute? + +We categorise pull requests (PRs) into two categories: + +| PR type | Definition | +| --------- | ------------------------------------------------------------ | +| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. | +| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). | + +We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process]. + +We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations. + +Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge. + +#### Making larger changes + +[making larger changes]: #making-larger-changes + +Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented. + +If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress. + +#### Pull request or package? + +[pr or package]: #pull-request-or-package + +If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes]. + +If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS. + +#### Ownership and copyright + +It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code. + +Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS. + +If you're not sure, leave a note on your contribution and we will be happy to guide you. + +When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards. + + +[MIT license]: ../LICENSE.md "Umbraco's license declaration" + + +[issues]: https://github.com/umbraco/Umbraco-CMS/issues +[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions diff --git a/.github/contributing-core-collabs-team.md b/.github/contributing-core-collabs-team.md new file mode 100644 index 0000000000..1e00529ee9 --- /dev/null +++ b/.github/contributing-core-collabs-team.md @@ -0,0 +1,28 @@ +### The Core Collaborators team +[Core collabs]: #the-core-collaborators-team + +The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time: + +- [Busra Sengul][Busra Sengul] +- [Emma Garland][Emma Garland] +- [George Bidder][George Bidder] +- [Jason Elkin][Jason Elkin] +- [Laura Neto][Laura Neto] +- [Kyle Eck][Kyle Eck] +- [Michael Latouche][Michael Latouche] +- [Sebastiaan][Sebastiaan] + + +These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged. + + + + +[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile" +[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile" +[George Bidder]: https://github.com/georgebid "George's GitHub profile" +[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile" +[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile" +[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile" +[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile" +[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile" diff --git a/.github/contributing-creating-a-pr.md b/.github/contributing-creating-a-pr.md new file mode 100644 index 0000000000..dc1d67ea65 --- /dev/null +++ b/.github/contributing-creating-a-pr.md @@ -0,0 +1,51 @@ +## Creating a pull request + +Exciting! You're ready to show us your changes. + +We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier. + +GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. +![Create a pull request](img/createpullrequest.png) + +We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting. + +Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise. + +## The review process +[review process]: #the-review-process + +You've sent us your contribution - congratulations! Now what? + +The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes. + +You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides. + +You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes: + +- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco +- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible +- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see +- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package] + +### Dealing with requested changes + +If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco! + +#### No longer available? + +We understand you have other things to do and can't just drop everything to help us out. + +So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time. + +If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it. + +There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository. + + +[ Umbrabot ]: https://github.com/umbrabot +[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow" + + +[making larger changes]: contributing-before-you-start.md#making-large-changes +[pr or package]: contributing-before-you-start.md#pull-request-or-package +[Core collabs]: contributing-core-collabs-team.md \ No newline at end of file diff --git a/.github/contributing-first-issue.md b/.github/contributing-first-issue.md new file mode 100644 index 0000000000..8027d69519 --- /dev/null +++ b/.github/contributing-first-issue.md @@ -0,0 +1,93 @@ +## Finding your first issue: Up for grabs + +Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out. + +If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has. + +## Making your changes + +Great question! The short version goes like this: + +1. **Fork** + + Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo] + + ![Fork the repository](img/forkrepository.png) + +1. **Clone** + + When GitHub has created your fork, you can clone it in your favorite Git tool + + ![Clone the fork](img/clonefork.png) + +1. **Switch to the correct branch** + + Switch to the `contrib` branch + +1. **Build** + + Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs]. + +1. **Branch** + + Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first. + +1. **Change** + + Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions]. + +1. **Commit and push** + + Done? Yay! 🎉 + + Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub. + +#### Keeping your Umbraco fork in sync with the main repository +[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository + +Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes. + +To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once: + +``` +git remote add upstream https://github.com/umbraco/Umbraco-CMS.git +``` + +Then when you want to get the changes from the main repository: + +``` +git fetch upstream +git rebase upstream/contrib +``` + +In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed. + +[More information on how this works can be found on the thoughtbot blog.][sync fork ext] + +#### Style guide + +To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. + +That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. + +#### Questions? +[questions]: #questions + +You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away: + +- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward. +- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs]. +- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction. + + + + +[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio" +[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code" + + +[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated" +[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests" +[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/ +[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS +[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs \ No newline at end of file diff --git a/.github/contributing-other-ways-to-contribute.md b/.github/contributing-other-ways-to-contribute.md new file mode 100644 index 0000000000..773471b8f1 --- /dev/null +++ b/.github/contributing-other-ways-to-contribute.md @@ -0,0 +1,10 @@ +## Coding not your thing? Or want more ways to contribute? + +This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved] + +If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better. + +We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy. + +[get involved]: https://community.umbraco.com/get-involved/ +[docs repo]: https://github.com/umbraco/UmbracoDocs \ No newline at end of file From ca5dce571c1e92a46403063b6b29dfbd8b86ecb0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 25 Oct 2023 16:45:38 +0200 Subject: [PATCH 019/146] Update CONTRIBUTING.md --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a984746cc1..2d89c6102e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -53,6 +53,6 @@ The following steps are a quick-start guide: - [Before you start](contributing-before-you-start.md) - [Finding your first issue: Up for grabs](contributing-before-you-start.md) -- [Contributing to the new backoffice](https://github.com/umbraco/Umbraco-CMS/blob/v14/dev/.github/New%20BackOffice%20-%20README.md) +- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/) - [Other ways to contribute](contributing-other-ways-to-contribute.md) From 634afc4b18d2b1bfd5fbe6de290c9790b2218074 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 26 Oct 2023 09:15:54 +0200 Subject: [PATCH 020/146] Add "unwanted changes" doc back --- .github/CONTRIBUTING.md | 1 + .github/contributing-unwanted-changes.md | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .github/contributing-unwanted-changes.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2d89c6102e..e5264fa8d8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -54,5 +54,6 @@ The following steps are a quick-start guide: - [Before you start](contributing-before-you-start.md) - [Finding your first issue: Up for grabs](contributing-before-you-start.md) - [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/) +- [Unwanted changes](contributing-unwanted-changes.md) - [Other ways to contribute](contributing-other-ways-to-contribute.md) diff --git a/.github/contributing-unwanted-changes.md b/.github/contributing-unwanted-changes.md new file mode 100644 index 0000000000..c078f4600e --- /dev/null +++ b/.github/contributing-unwanted-changes.md @@ -0,0 +1,18 @@ +## Unwanted changes +While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused. +Of course this will depend heavily on the specific change, but please take the following examples in mind. + +- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you. +- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues. +- **Changes to obsolete code and/or property editors ✍️** +- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it. +- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files! +- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core. + +While these are only a few examples, it is important to ask yourself these questions before making a pull request: + +- How many will benefit from this change? +- Are there other ways to achieve this? And if so, how do they compare? +- How maintainable is the change? +- What would be the effort to test it properly? +- Do the benefits outweigh the risks? \ No newline at end of file From d5859669f69c6a45c67e2c60bc2f83309cca430a Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 23 Oct 2023 02:16:46 +0200 Subject: [PATCH 021/146] Updated FR file --- .../EmbeddedResources/Lang/en.xml | 4 +++ .../EmbeddedResources/Lang/fr.xml | 29 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 4213dcc16a..6efbb57c95 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1479,6 +1479,10 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Rollback to Select version View + + Versions + Current draft version + Current published version Edit script file diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml index a753a054fe..781685f150 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml @@ -34,6 +34,7 @@ Récupérer Choisissez où copier Choisissez où déplacer + Choisissez où importer dans l'arborescence ci-dessous a été déplacé vers a été copié vers @@ -476,7 +477,14 @@ Ceci supprimera le noeud et toutes ses langues. Si vous souhaitez supprimer une langue spécifique, vous devriez plutôt supprimer la publication du noeud dans cette langue-là. - Il n'y a pas d'éléments dans le dictionnaire. + + Pour importer un élément de dictionnaire, trouvez le fichier ".udt" sur votre ordinateur en cliquant sur le bouton "Importer" (une confirmation vous sera demandée dans l'écran suivant) + + L'élément de dictionnaire n'existe pas. + L'éléménet parent n'existe pas. + Il n'y a pas d'élément dans le dictionnaire.. + Il n'y a pas d'élément de dictionnaire dans ce fichier. + Créer un élément de dictionnaire Un moment s'il vous plaît... Précédent Propriétés + En savoir plus Reconstruire Email de réception des données de formulaire Corbeille @@ -709,6 +718,7 @@ Aucun élément n'a été ajouté Serveur Paramètres + Partagé Montrer Afficher la page à l'envoi Taille @@ -1263,6 +1273,10 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Revenir à Choisissez une version Voir + + Versions + Version de travail + Version publiée Editer le fichier de script @@ -1388,6 +1402,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à La validation a échoué pour la langue '%0%' Le Type de Document a été exporté dans le fichier Une erreur est survenue durant l'export du type de document + Les éléments de dictionnaire ont été exportés vers le fichier + Une erreur est survenue lors de l'export des éléments de dictionnaire. + Les éléments de dictionnaire suivants ont été importés! La date de publication ne peut pas être dans le passé Impossible de planifier la publication du document car la langue obligatoire '%0%' n'est pas publiée Impossible de planifier la publication du document car la langue obligatoire '%0%' a une date de publication postérieure à celle d'une langue non obligatoire @@ -1584,6 +1601,13 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Un Type d'Elément est destiné à être utilisé par exemple dans Nested Content, et pas dans l'arborescence. Ceci n'est pas d'application pour un Type d'Elément Vous avez apporté des modifications à cette propriété. Etes-vous certain.e de vouloir les annuler? + Nettoyer l'historique + Autoriser le remplacement des paramètres globaux de nettoyage de l'historique. + Garder toutes les versions plus récentes que jours + Garder la dernière version quotidienne pendantt jours + Empêcher le nettoyage + Activer le nettoyage + REMARQUE! Le nettoyage de l'historique des versions de contenu est désactvé globallement. Ces paramètre ne prendront pas effet avant qu'il ne soit axtivé.]]> Ajouter une langue @@ -2092,6 +2116,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Macro de Partial View Membre Chercher dans l'arborescence de contenu + Quantité maximum + Afficher les ékéments enfant pour + Ouvrir le noeud de contexte pour Références From 38a0417ef35608f960740bfc5b7e49166b31d591 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 23 Oct 2023 02:48:48 +0200 Subject: [PATCH 022/146] Correct typo's... --- src/Umbraco.Core/EmbeddedResources/Lang/fr.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml index 781685f150..7f14f75456 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml @@ -481,8 +481,8 @@ Pour importer un élément de dictionnaire, trouvez le fichier ".udt" sur votre ordinateur en cliquant sur le bouton "Importer" (une confirmation vous sera demandée dans l'écran suivant) L'élément de dictionnaire n'existe pas. - L'éléménet parent n'existe pas. - Il n'y a pas d'élément dans le dictionnaire.. + L'élément parent n'existe pas. + Il n'y a pas d'élément dans le dictionnaire. Il n'y a pas d'élément de dictionnaire dans ce fichier. Créer un élément de dictionnaire @@ -1604,10 +1604,10 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Nettoyer l'historique Autoriser le remplacement des paramètres globaux de nettoyage de l'historique. Garder toutes les versions plus récentes que jours - Garder la dernière version quotidienne pendantt jours + Garder la dernière version quotidienne pendant jours Empêcher le nettoyage Activer le nettoyage - REMARQUE! Le nettoyage de l'historique des versions de contenu est désactvé globallement. Ces paramètre ne prendront pas effet avant qu'il ne soit axtivé.]]> + REMARQUE! Le nettoyage de l'historique des versions de contenu est désactvé globalement. Ces paramètres ne prendront pas effet avant qu'il ne soit activé.]]> Ajouter une langue @@ -2117,7 +2117,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Membre Chercher dans l'arborescence de contenu Quantité maximum - Afficher les ékéments enfant pour + Afficher les éléments enfant pour Ouvrir le noeud de contexte pour From 51553e02703f33685e1174e4425c65eefae38162 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Fri, 27 Oct 2023 11:47:42 +0100 Subject: [PATCH 023/146] Make clearer where to go to see build documentation (#15054) * Make clearer where to go to see build documentation * lose a bracket --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e5264fa8d8..431fa7c2fa 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,7 +27,7 @@ The following steps are a quick-start guide: 4. **Build** - Build your fork of Umbraco locally as described in the build documentation, you can [build with any IDE that support dotnet or the command line](BUILD.md). + Build your fork of Umbraco locally [as described in the build documentation](BUILD.md), you can build with any IDE that supports dotnet or the command line. 5. **Branch** From 8e609af90168a7c5e088b30193266733269457ec Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Sat, 28 Oct 2023 17:19:34 +0200 Subject: [PATCH 024/146] Updates to Danish translations (#15057) * Updates to Danish translations Typo fixes & grammar fixes * Updating as discussed in the PR --- .../EmbeddedResources/Lang/da.xml | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 008faa39fe..16d193c925 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -17,7 +17,7 @@ Opret gruppe Slet Deaktivér - Edit settings + Redigér indstillinger Tøm papirkurv Aktivér Eksportér dokumenttype @@ -26,7 +26,7 @@ Redigér i Canvas Log af Flyt - Notificeringer + Notifikationer Offentlig adgang Udgiv Afpublicér @@ -84,7 +84,7 @@ Tillad adgang til at oversætte en node Tillad adgang til at gemme en node Tillad adgang til at oprette en indholdsskabelon - Tillad adgang til at oprette notificeringer for noder + Tillad adgang til at oprette notifikationer for noder Indhold @@ -105,10 +105,10 @@ Domænet '%0%' er nu opdateret eller rediger nuværende domæner + Yderligere understøttes også første niveau af stien efter domænet, f.eks. "example.com/en" eller "/en". ]]> Nedarv Sprog - eller nedarv sprog fra forældre noder. Gælder også
+ eller nedarv sprog fra forældernoder. Gælder også
for den aktuelle node, medmindre et domæne nedenfor også indstiller et sprog.]]>
Domæner @@ -120,7 +120,7 @@ Fortryd indryk afsnit Indsæt formularfelt Indsæt grafisk overskrift - Redigér Html + Redigér HTML Indryk afsnit Kursiv Centrér @@ -154,7 +154,7 @@ Slet tag Fortryd Bekræft - Flere publiseringsmuligheder + Flere publiceringsmuligheder Indsæt @@ -243,21 +243,21 @@ Ups: dette dokument er udgivet, men er ikke i cachen (intern fejl) Kunne ikke hente URL'en Dette dokument er udgivet, men dets URL ville kollidere med indholdet %0% - Dette dokument er udgivet, men dets URL kan ikke dirigeres + Dette dokument er udgivet, men dets URL kan ikke genereres Udgiv Udgivet Udgivet (Ventede ændringer) Udgivelsesstatus - Udgiv med undersider for at udgive %0% og alle sider under og dermed gøre deres indhold offentligt tilgængelige.]]> + Udgiv med undersider for at udgive %0% og alle sider under, og dermed gøre deres indhold offentligt tilgængeligt.]]>
- Udgiv med undersider for at udgive de valgte sprog og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]> + Udgiv med undersider for at udgive de valgte sprog og de samme sprog for sider under, og dermed gøre deres indhold offentligt tilgængeligt.]]> Udgivelsesdato Afpubliceringsdato Fjern dato Vælg dato Sorteringsrækkefølgen er opdateret - For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider + For at sortere, træk siderne eller klik på en af kolonneoverskrifterne. Du kan vælge flere sider ved at holde "shift" eller "control" nede mens du vælger. Statistik @@ -298,8 +298,8 @@ Fjern denne tekstboks Indholdsrod Inkluder ikke-udgivet indhold. - Denne værdi er skjult.Hvis du har brug for adgang til at se denne værdi, bedes du - kontakte din web-administrator. + Denne værdi er skjult. Hvis du har brug for adgang til at se denne værdi, bedes du + kontakte din administrator. Denne værdi er skjult. Hvilke sprog vil du gerne udgive? @@ -334,11 +334,11 @@ Klik for at uploade eller klik her for at vælge filer - Kan ikke uploade denne fil, den har ikke en godkendt filtype - Maks filstørrelse er + Kan ikke uploade denne fil; den har ikke en godkendt filtype + Maksimal filstørrelse er Medie rod - Overordnet og destinations mappe kan ikke være den samme - Oprettelse af mappen under parent med id %0% fejlede + Overordnet og destinationsmappe kan ikke være den samme + Oprettelse af mappen under node med id %0% fejlede Omdøbning af mappen med id %0% fejlede Træk dine filer ind i dropzonen for, at uploade dem til mediebiblioteket. @@ -348,7 +348,7 @@ Opret et nyt medlem Alle medlemmer - Medlemgrupper har ingen yderligere egenskaber til redigering. + Medlemsgrupper har ingen yderligere egenskaber til redigering. Totrinsbekræftelse @@ -358,7 +358,7 @@ Kopiering af medietypen fejlede Flytning af medietypen fejlede - Auto vælg + Vælg automatisk Kopiering af medlemstypen fejlede @@ -370,18 +370,18 @@ Angiv et navn for mappen Vælg en type og skriv en titel - "dokumenttyper".]]> + Indstillinger under "Dokumenttyper".]]> - Document Types within the Settings section.]]> + Dokumenttyper i Indstillinger-sektionen.]]> Den valgte side i træet tillader ikke at sider oprettes under den. Rediger tilladelser for denne dokumenttype. Opret en ny dokumenttype - Dokumenttyper inde i Indstillinger sektionen, ved at ændre Tillad på rodniveau indestillingen under Permissions.]]> + Dokumenttyper inde i Indstillinger sektionen, ved at ændre Tillad på rodniveau indstillingen under Permissions.]]> - "media typer".]]> + "Medietyper".]]> Det valgte medie i træet tillader ikke at medier oprettes under det. Rediger tilladelser for denne medietype. @@ -436,7 +436,7 @@ Udgivelse vil gøre de valgte sider synlige på sitet. Afpublicering vil fjerne de valgte sider og deres undersider fra sitet. - Afpublicering vil fjerne denne side og alle dets undersider fra websitet. + Afpublicering vil fjerne denne side og alle dens undersider fra websitet. Du har ikke-gemte ændringer. Hvis du ændrer dokumenttype, kasseres ændringerne. @@ -1071,8 +1071,8 @@ Relater det kopierede element til originalen - %0%]]> - Notificeringer er gemt for + %0%]]> + Notifikationer er gemt for
     RET       

Opdateringssammendrag:

%6%

Hav en fortsat god dag!

De bedste hilsner fra Umbraco robotten

]]> [%0%] Notificering om %1% udført på %2% - Notificeringer + Notifikationer Handlinger @@ -2054,25 +2054,25 @@ Mange hilsner fra Umbraco robotten Test bestået Test fejlet Åben backoffice søgning - Åben/Luk backoffice hjælp - Åben/Luk dine profil indstillinger + Åben/luk backoffice hjælp + Åben/luk dine profilindstillinger Tilføj domæne på %0% Opret ny node under %0% Opsæt offentlig adgang på %0% Opsæt rettigheder på %0% - Juster soterings rækkefølgen for %0% - Opret indholds skabelon baseret på %0% - Åben kontext menu for + Juster sorteringsrækkefølgen for %0% + Opret indholdsskabelon baseret på %0% + Åben kontekstmenu for Aktivt sprog Skift sprog til Opret ny mappe - Delvist View - Delvist View Macro + Partial View + Partial View Macro Medlem Data type Søg i viderestillings dashboardet - Søg i brugergruppe sektionen - Søg i bruger sektionen + Søg i brugergruppesektionen + Søg i brugersektionen Opret element Opret Rediger @@ -2096,16 +2096,16 @@ Mange hilsner fra Umbraco robotten Referencer - Denne Data Type har ingen referencer. - Brugt i Medie Typer - Brugt i Medlems Typer - Brugt af - Brugt i Dokumenter - Brugt i Medlemmer - Brugt i Medier + Denne Datatype har ingen referencer. + Bruges i Medietyper + Bruges i Medlemstyper + Bruges af + Bruges i Dokumenter + Bruges i Medlemmer + Bruges i Medier - Slet gemte søgning + Slet gemt søgning Log type Vælg alle Fravælg alle @@ -2137,7 +2137,7 @@ Mange hilsner fra Umbraco robotten Slet denne søgning Find logs med request Id Find logs med Namespace - Find logs med maskin navn + Find logs med maskinnavn Åben Henter Hver 2 sekunder @@ -2174,19 +2174,19 @@ Mange hilsner fra Umbraco robotten Redigerings udseende Data modeller katalog udseende - Baggrunds farve + Baggrundsfarve Ikon farve Indholds model Label Speciel visning Vis speciel visning beskrivelsen - Overskrift hvordan denne block præsenteres i backoffice interfacet. Vælg en - .html fil der indeholder din præsensation. + Overskriv hvordan denne blok præsenteres i backoffice. Vælg en + .html fil der indeholder din visning. Indstillings model - Rederings lagets størrelse + Redigeringsvinduets størrelse Tilføj speciel visning - Tilføj instillinger + Tilføj indstillinger %0%?]]> @@ -2211,7 +2211,7 @@ Mange hilsner fra Umbraco robotten Skjul indholdseditoren Skjul indholds redigerings knappen samt indholdseditoren i Blok Redigerings vinduet Direkte redigering - Tilføjer direkte redigering a det første felt. Yderligere felter optræder kun i redigerings vinduet. + Tilføjer direkte redigering af det første felt. Yderligere felter optræder kun i redigerings vinduet. Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem? Annuller oprettelse? @@ -2234,11 +2234,11 @@ Mange hilsner fra Umbraco robotten Alle blokke, der er oprettet i dette område, vil blive slettet. Layout-opsætning Struktur - Størrelses opsætning + Størrelsesopsætning Tilgængelige kolonne-størrelser - Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindre ikke blokken i at optræde i et mindre område. - TIlgængelige række-størrelser - Vælg hvor mange rækker denne blok på optage i layoutet. + Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindrer ikke blokken i at optræde i et mindre område. + Tilgængelige række-størrelser + Vælg hvor mange rækker denne blok må optage i layoutet. Tillad på rodniveau Gør denne blok tilgængelig i layoutets rodniveau. Hvis dette ikke er valgt, kan denne blok kun bruges inden for andre blokkes definerede områder. Blok-områder @@ -2252,11 +2252,11 @@ Mange hilsner fra Umbraco robotten - + Træk for at skalere Tilføj indhold label - Overskriv labellen for tilføj indholds knappen i dette område. - Tilføj skalerings muligheder + Overskriv labelen for tilføj indholds knappen i dette område. + Tilføj skaleringsmuligheder Tilføj Blok Tilføj gruppe Tilføj gruppe eller Blok @@ -2269,14 +2269,14 @@ Mange hilsner fra Umbraco robotten Avanceret Tilladelser Installer demo konfiguration - Dette indeholder Blokke for Overskrift, Beriget-Tekst, Billede og To-Koloners-Layout.]]> + Dette indeholder Blokke for Overskrift, Formateret Tekst, Billede og To-Kolonners-Layout.]]> Installer - Sortings tilstand - Afslut sortings tilstand + Sortingstilstand + Afslut sortingstilstand Dette område alias skal være unikt sammenlignet med andre områder af denne Blok. Konfigurer område Slet område - Tilføj mulighed for %0% koloner + Tilføj mulighed for %0% kolonner Hvad er Indholdsskabeloner? @@ -2306,12 +2306,12 @@ Mange hilsner fra Umbraco robotten Vis i nyt vindue Åben forhåndsvisning i nyt vindue Forhåndsvisning af indholdet? - Du har afslutet forhåndsvisning, vil du starte forhåndsvisning igen for at + Du har afsluttet forhåndsvisning, vil du starte forhåndsvisning igen for at se seneste gemte version af indholdet? Se udgivet indhold Se udgivet indhold? - Du er i forhåndsvisning, vil du afslutte for at se den udgivet + Du er i forhåndsvisning, vil du afslutte for at se den udgivne version? Se udgivet version @@ -2321,7 +2321,7 @@ Mange hilsner fra Umbraco robotten Mappeoprettelse Filskrivning for pakker Filskrivning - Medie mappeoprettelse + Mediemappeoprettelse resultat From b1b48ec5cdeb29972b6dd16f58992d703d1cd70d Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 31 Oct 2023 18:23:15 +0100 Subject: [PATCH 025/146] Set max height of media item in grid (#15066) * Set max height of media item in grid to avoid portrait images of take up much of height * Style image preview in for SVG * Set max height instead --- .../src/common/directives/components/umbmediagrid.directive.js | 1 + .../components/media/umbimagepreview/umb-image-preview.less | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index c073c89141..01de877b79 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -282,6 +282,7 @@ Use this directive to generate a thumbnail grid of media items. var flexStyle = { "flex": flex + " 1 " + imageMinFlexWidth + "px", "max-width": mediaItem.width + "px", + "max-height": itemMaxHeight + "px", "min-width": itemMinWidth + "px", "min-height": itemMinHeight + "px" }; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less index 13f934c251..b0ce88a446 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less @@ -5,5 +5,6 @@ img { width: 100%; + max-height: 50vh; } } From c19e747bbed1af5bae4a17ba4863da57d38c0b05 Mon Sep 17 00:00:00 2001 From: Nikolaj Brask-Nielsen Date: Wed, 1 Nov 2023 09:59:32 +0100 Subject: [PATCH 026/146] Fix custom dbcontexts extention methods (#14937) * test: Create failing test * feat: New extension methods for adding Umbraco DBContexts * test: Cleaned up integration tests --- .../SqlServerMigrationProvider.cs | 2 +- .../SqlServerMigrationProviderSetup.cs | 2 +- .../SqliteMigrationProvider.cs | 3 +- .../SqliteMigrationProviderSetup.cs | 2 +- .../Composition/UmbracoEFCoreComposer.cs | 2 +- .../Constants-ProviderNames.cs | 11 ++ ...mbracoEFCoreServiceCollectionExtensions.cs | 115 +++++++++++++++++- .../UmbracoDbContext.cs | 3 +- .../DbContext/CustomDbContextTests.cs | 101 +++++++++++++++ 9 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs index bac08556a3..57a3a634fe 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs @@ -10,7 +10,7 @@ public class SqlServerMigrationProvider : IMigrationProvider public SqlServerMigrationProvider(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - public string ProviderName => "Microsoft.Data.SqlClient"; + public string ProviderName => Constants.ProviderNames.SQLServer; public async Task MigrateAsync(EFCoreMigration migration) { diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs index 6b161fc47f..6425e712f6 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Persistence.EFCore.SqlServer; public class SqlServerMigrationProviderSetup : IMigrationProviderSetup { - public string ProviderName => "Microsoft.Data.SqlClient"; + public string ProviderName => Constants.ProviderNames.SQLServer; public void Setup(DbContextOptionsBuilder builder, string? connectionString) { diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs index 05d4024bb3..1ff7b1535e 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Umbraco.Cms.Persistence.EFCore.Migrations; using Umbraco.Extensions; +using Umbraco.Cms.Persistence.EFCore; namespace Umbraco.Cms.Persistence.EFCore.Sqlite; @@ -11,7 +12,7 @@ public class SqliteMigrationProvider : IMigrationProvider public SqliteMigrationProvider(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - public string ProviderName => "Microsoft.Data.Sqlite"; + public string ProviderName => Constants.ProviderNames.SQLLite; public async Task MigrateAsync(EFCoreMigration migration) { diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs index 4cba457768..bb6b9feac7 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Persistence.EFCore.Sqlite; public class SqliteMigrationProviderSetup : IMigrationProviderSetup { - public string ProviderName => "Microsoft.Data.Sqlite"; + public string ProviderName => Constants.ProviderNames.SQLLite; public void Setup(DbContextOptionsBuilder builder, string? connectionString) { diff --git a/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs b/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs index 8b8627df47..1b751c558b 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs @@ -20,7 +20,7 @@ public class UmbracoEFCoreComposer : IComposer builder.AddNotificationAsyncHandler(); builder.AddNotificationAsyncHandler(); - builder.Services.AddUmbracoEFCoreContext((options, connectionString, providerName) => + builder.Services.AddUmbracoDbContext((options) => { // Register the entity sets needed by OpenIddict. options.UseOpenIddict(); diff --git a/src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs b/src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs new file mode 100644 index 0000000000..0f29e5944c --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Cms.Persistence.EFCore; + +public static partial class Constants +{ + public static class ProviderNames + { + public const string SQLLite = "Microsoft.Data.Sqlite"; + + public const string SQLServer = "Microsoft.Data.SqlClient"; + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index ded5be40fd..8fb8e53617 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -1,7 +1,9 @@ +using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; @@ -16,6 +18,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions { public delegate void DefaultEFCoreOptionsAction(DbContextOptionsBuilder options, string? providerName, string? connectionString); + [Obsolete("Use AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) instead.")] public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { @@ -24,7 +27,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions sp => { SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(),optionsBuilder.Options); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); }); services.AddPooledDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -38,6 +41,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions return services; } + [Obsolete("Use AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) instead.")] public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, string connectionString, string providerName, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { @@ -52,8 +56,8 @@ public static class UmbracoEFCoreServiceCollectionExtensions services.TryAddSingleton>( sp => { - SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(),optionsBuilder.Options); + defaultEFCoreOptionsAction?.Invoke(optionsBuilder, providerName, connectionString); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); }); services.AddPooledDbContextFactory(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -67,12 +71,117 @@ public static class UmbracoEFCoreServiceCollectionExtensions return services; } + /// + /// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes. + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) + where T : DbContext + { + return AddUmbracoDbContext(services, (IServiceProvider _, DbContextOptionsBuilder options) => + { + optionsAction?.Invoke(options); + }); + } + + /// + /// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes. + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) + where T : DbContext + { + optionsAction ??= (sp, options) => { }; + + var optionsBuilder = new DbContextOptionsBuilder(); + + services.TryAddSingleton>(sp => + { + optionsAction.Invoke(sp, optionsBuilder); + return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); + }); + services.AddPooledDbContextFactory(optionsAction); + services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); + + services.AddUnique, AmbientEFCoreScopeStack>(); + services.AddUnique, EFCoreScopeAccessor>(); + services.AddUnique, EFCoreScopeProvider>(); + services.AddSingleton>(); + services.AddSingleton>(); + + return services; + } + + /// + /// Sets the database provider. I.E UseSqlite or UseSqlServer based on the provider name. + /// + /// + /// + /// + /// + /// + /// Only supports the databases normally supported in Umbraco. + /// + public static void UseDatabaseProvider(this DbContextOptionsBuilder builder, string providerName, string connectionString) + { + switch (providerName) + { + case Cms.Persistence.EFCore.Constants.ProviderNames.SQLServer: + builder.UseSqlServer(connectionString); + break; + case Cms.Persistence.EFCore.Constants.ProviderNames.SQLLite: + builder.UseSqlite(connectionString); + break; + default: + throw new InvalidDataException($"The provider {providerName} is not supported. Manually add the add the UseXXX statement to the options. I.E UseNpgsql()"); + } + } + + /// + /// Sets the database provider to use based on the Umbraco connection string. + /// + /// + /// + public static void UseUmbracoDatabaseProvider(this DbContextOptionsBuilder builder, IServiceProvider serviceProvider) + { + ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; + + // Replace data directory + string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); + if (string.IsNullOrEmpty(dataDirectory) is false) + { + connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); + } + + if (string.IsNullOrEmpty(connectionStrings.ProviderName)) + { + Log.Warning("No database provider was set. ProviderName is null"); + return; + } + + if (string.IsNullOrEmpty(connectionStrings.ConnectionString)) + { + Log.Warning("No database provider was set. Connection string is null"); + return; + } + + builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString); + } + + [Obsolete] private static void SetupDbContext(DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder) { ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider); defaultEFCoreOptionsAction?.Invoke(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName); } + [Obsolete] private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider) { ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; diff --git a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs index 042cf2a52f..60e519de4c 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Persistence.EFCore.Migrations; @@ -77,7 +76,7 @@ public class UmbracoDbContext : DbContext foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes()) { - entity.SetTableName(Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName()); + entity.SetTableName(Core.Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName()); } } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs new file mode 100644 index 0000000000..bfa3adb92b --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs @@ -0,0 +1,101 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Persistence.EFCore.DbContext; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] +public class CustomDbContextUmbracoProviderTests : UmbracoIntegrationTest +{ + [Test] + public void Can_Register_Custom_DbContext_And_Resolve() + { + var dbContext = Services.GetRequiredService(); + + Assert.IsNotNull(dbContext); + Assert.IsNotEmpty(dbContext.Database.GetConnectionString()); + } + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUmbracoDbContext((serviceProvider, options) => + { + options.UseUmbracoDatabaseProvider(serviceProvider); + }); + } + + internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext + { + public CustomDbContext(DbContextOptions options) + : base(options) + { + } + } +} + + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] +public class CustomDbContextCustomSqliteProviderTests : UmbracoIntegrationTest +{ + [Test] + public void Can_Register_Custom_DbContext_And_Resolve() + { + var dbContext = Services.GetRequiredService(); + + Assert.IsNotNull(dbContext); + Assert.IsNotEmpty(dbContext.Database.GetConnectionString()); + } + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUmbracoDbContext((serviceProvider, options) => + { + options.UseSqlite("Data Source=:memory:;Version=3;New=True;"); + }); + } + + internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext + { + public CustomDbContext(DbContextOptions options) + : base(options) + { + } + } +} + +[Obsolete] +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] +public class CustomDbContextLegacyExtensionProviderTests : UmbracoIntegrationTest +{ + [Test] + public void Can_Register_Custom_DbContext_And_Resolve() + { + var dbContext = Services.GetRequiredService(); + + Assert.IsNotNull(dbContext); + Assert.IsNotEmpty(dbContext.Database.GetConnectionString()); + } + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUmbracoEFCoreContext("Data Source=:memory:;Version=3;New=True;", "Microsoft.Data.Sqlite", (options, connectionString, providerName) => + { + options.UseSqlite(connectionString); + }); + } + + internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext + { + public CustomDbContext(DbContextOptions options) + : base(options) + { + } + } +} + From 67fe7e7e111a161a750570f37b4cea41ce4a3127 Mon Sep 17 00:00:00 2001 From: Jeroen Breuer Date: Tue, 31 Oct 2023 15:28:56 +0100 Subject: [PATCH 027/146] Fix warning CS0618: 'IHostingEnvironment.MapPathContentRoot(string)' is obsolete. --- tests/Umbraco.TestData/LoadTestController.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/Umbraco.TestData/LoadTestController.cs b/tests/Umbraco.TestData/LoadTestController.cs index 741fe4e94b..676b5c0256 100644 --- a/tests/Umbraco.TestData/LoadTestController.cs +++ b/tests/Umbraco.TestData/LoadTestController.cs @@ -7,6 +7,7 @@ using System.Threading; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -99,7 +100,7 @@ public class LoadTestController : Controller private readonly IDataTypeService _dataTypeService; private readonly IFileService _fileService; private readonly IHostApplicationLifetime _hostApplicationLifetime; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IHostEnvironment _hostEnvironment; private readonly IShortStringHelper _shortStringHelper; public LoadTestController( @@ -108,7 +109,7 @@ public class LoadTestController : Controller IDataTypeService dataTypeService, IFileService fileService, IShortStringHelper shortStringHelper, - IHostingEnvironment hostingEnvironment, + IHostEnvironment hostEnvironment, IHostApplicationLifetime hostApplicationLifetime) { _contentTypeService = contentTypeService; @@ -116,7 +117,7 @@ public class LoadTestController : Controller _dataTypeService = dataTypeService; _fileService = fileService; _shortStringHelper = shortStringHelper; - _hostingEnvironment = hostingEnvironment; + _hostEnvironment = hostEnvironment; _hostApplicationLifetime = hostApplicationLifetime; } @@ -321,7 +322,7 @@ public class LoadTestController : Controller public IActionResult ColdBootRestart() { Directory.Delete( - _hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData, "DistCache")), + _hostEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData, "DistCache")), true); DoRestart(); From c30ffa9ac3ae9c12508242855c63208567c55eb9 Mon Sep 17 00:00:00 2001 From: Andrew McKaskill <32095907+andrewmckaskill@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:15:14 +0000 Subject: [PATCH 028/146] Refactor hostedServices into background jobs (#14291) * Refactor jobs from HostedServices into BackgroundJobs * Clean up generics and DI setup * Add RecurringBackgroundJob Unit Tests * Add ServiceCollection helper * Add Obsolete attributes * Add Notification Classes * Add UnitTests for RecurringBackgroundJob HostedService * Add NotificationEvents * Add state to notifications * Update UnitTests * Add Obsolete Attributes to old hosted service classes * Updated xmldoc in IRecurringBackgroundJob.cs * Update Obsolete attribute messages to indicate classes will be removed in Umbraco 14 --- .../BackgroundJobs/DelayCalculator.cs | 67 ++++++ .../BackgroundJobs/IRecurringBackgroundJob.cs | 28 +++ .../Jobs/ContentVersionCleanupJob.cs | 66 ++++++ .../Jobs/HealthCheckNotifierJob.cs | 116 ++++++++++ .../BackgroundJobs/Jobs/KeepAliveJob.cs | 90 ++++++++ .../BackgroundJobs/Jobs/LogScrubberJob.cs | 73 ++++++ .../BackgroundJobs/Jobs/ReportSiteJob.cs | 92 ++++++++ .../Jobs/ScheduledPublishingJob.cs | 105 +++++++++ .../InstructionProcessJob.cs | 59 +++++ .../Jobs/ServerRegistration/TouchServerJob.cs | 102 +++++++++ .../BackgroundJobs/Jobs/TempFileCleanupJob.cs | 99 +++++++++ .../RecurringBackgroundJobHostedService.cs | 127 +++++++++++ ...curringBackgroundJobHostedServiceRunner.cs | 81 +++++++ .../Extensions/ServiceCollectionExtensions.cs | 31 +++ .../HostedServices/ContentVersionCleanup.cs | 1 + .../HostedServices/HealthCheckNotifier.cs | 1 + .../HostedServices/KeepAlive.cs | 1 + .../HostedServices/LogScrubber.cs | 1 + .../RecurringHostedServiceBase.cs | 4 +- .../HostedServices/ReportSiteTask.cs | 1 + .../HostedServices/ScheduledPublishing.cs | 1 + .../InstructionProcessTask.cs | 1 + .../ServerRegistration/TouchServerTask.cs | 1 + .../HostedServices/TempFileCleanup.cs | 1 + ...urringBackgroundJobExecutedNotification.cs | 12 + ...rringBackgroundJobExecutingNotification.cs | 12 + ...ecurringBackgroundJobFailedNotification.cs | 12 + ...curringBackgroundJobIgnoredNotification.cs | 12 + .../RecurringBackgroundJobNotification.cs | 12 + ...curringBackgroundJobStartedNotification.cs | 12 + ...urringBackgroundJobStartingNotification.cs | 12 + ...curringBackgroundJobStoppedNotification.cs | 12 + ...urringBackgroundJobStoppingNotification.cs | 12 + .../UmbracoBuilderExtensions.cs | 2 +- .../UmbracoBuilderExtensions.cs | 35 +++ .../Jobs/HealthCheckNotifierJobTests.cs | 139 ++++++++++++ .../BackgroundJobs/Jobs/KeepAliveJobTests.cs | 94 ++++++++ .../Jobs/LogScrubberJobTests.cs | 72 ++++++ .../Jobs/ScheduledPublishingJobTests.cs | 92 ++++++++ .../InstructionProcessJobTests.cs | 51 +++++ .../ServerRegistration/TouchServerJobTests.cs | 95 ++++++++ .../Jobs/TempFileCleanupJobTests.cs | 50 +++++ ...ecurringBackgroundJobHostedServiceTests.cs | 208 ++++++++++++++++++ .../HealthCheckNotifierTests.cs | 1 + .../HostedServices/KeepAliveTests.cs | 1 + .../HostedServices/LogScrubberTests.cs | 1 + .../ScheduledPublishingTests.cs | 1 + .../InstructionProcessTaskTests.cs | 1 + .../TouchServerTaskTests.cs | 1 + .../HostedServices/TempFileCleanupTests.cs | 1 + 50 files changed, 2099 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/DelayCalculator.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ContentVersionCleanupJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ReportSiteJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJob.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedService.cs create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs create mode 100644 src/Umbraco.Infrastructure/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutedNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutingNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobFailedNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobIgnoredNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartedNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartingNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppedNotification.cs create mode 100644 src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppingNotification.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJobTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJobTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJobTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJobTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceTests.cs diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/DelayCalculator.cs b/src/Umbraco.Infrastructure/BackgroundJobs/DelayCalculator.cs new file mode 100644 index 0000000000..42d016c066 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/DelayCalculator.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Configuration; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs +{ + public class DelayCalculator + { + /// + /// Determines the delay before the first run of a recurring task implemented as a hosted service when an optonal + /// configuration for the first run time is available. + /// + /// The configured time to first run the task in crontab format. + /// An instance of + /// The logger. + /// The default delay to use when a first run time is not configured. + /// The delay before first running the recurring task. + public static TimeSpan GetDelay( + string firstRunTime, + ICronTabParser cronTabParser, + ILogger logger, + TimeSpan defaultDelay) => GetDelay(firstRunTime, cronTabParser, logger, DateTime.Now, defaultDelay); + + /// + /// Determines the delay before the first run of a recurring task implemented as a hosted service when an optonal + /// configuration for the first run time is available. + /// + /// The configured time to first run the task in crontab format. + /// An instance of + /// The logger. + /// The current datetime. + /// The default delay to use when a first run time is not configured. + /// The delay before first running the recurring task. + /// Internal to expose for unit tests. + internal static TimeSpan GetDelay( + string firstRunTime, + ICronTabParser cronTabParser, + ILogger logger, + DateTime now, + TimeSpan defaultDelay) + { + // If first run time not set, start with just small delay after application start. + if (string.IsNullOrEmpty(firstRunTime)) + { + return defaultDelay; + } + + // If first run time not a valid cron tab, log, and revert to small delay after application start. + if (!cronTabParser.IsValidCronTab(firstRunTime)) + { + logger.LogWarning("Could not parse {FirstRunTime} as a crontab expression. Defaulting to default delay for hosted service start.", firstRunTime); + return defaultDelay; + } + + // Otherwise start at scheduled time according to cron expression, unless within the default delay period. + DateTime firstRunOccurance = cronTabParser.GetNextOccurrence(firstRunTime, now); + TimeSpan delay = firstRunOccurance - now; + return delay < defaultDelay + ? defaultDelay + : delay; + } + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs new file mode 100644 index 0000000000..c6be3dcec5 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs @@ -0,0 +1,28 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs; +/// +/// A recurring background job +/// +public interface IRecurringBackgroundJob +{ + static readonly TimeSpan DefaultDelay = System.TimeSpan.FromMinutes(3); + static readonly ServerRole[] DefaultServerRoles = new[] { ServerRole.Single, ServerRole.SchedulingPublisher }; + + /// Timespan representing how often the task should recur. + TimeSpan Period { get; } + + /// + /// Timespan representing the initial delay after application start-up before the first run of the task + /// occurs. + /// + TimeSpan Delay { get => DefaultDelay; } + + ServerRole[] ServerRoles { get => DefaultServerRoles; } + + event EventHandler PeriodChanged; + + Task RunJobAsync(); +} + diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ContentVersionCleanupJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ContentVersionCleanupJob.cs new file mode 100644 index 0000000000..cb89d600aa --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ContentVersionCleanupJob.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +/// +/// Recurring hosted service that executes the content history cleanup. +/// +public class ContentVersionCleanupJob : IRecurringBackgroundJob +{ + + public TimeSpan Period { get => TimeSpan.FromHours(1); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + private readonly ILogger _logger; + private readonly IContentVersionService _service; + private readonly IOptionsMonitor _settingsMonitor; + + + /// + /// Initializes a new instance of the class. + /// + public ContentVersionCleanupJob( + ILogger logger, + IOptionsMonitor settingsMonitor, + IContentVersionService service) + { + _logger = logger; + _settingsMonitor = settingsMonitor; + _service = service; + } + + /// + public Task RunJobAsync() + { + // Globally disabled by feature flag + if (!_settingsMonitor.CurrentValue.ContentVersionCleanupPolicy.EnableCleanup) + { + _logger.LogInformation( + "ContentVersionCleanup task will not run as it has been globally disabled via configuration"); + return Task.CompletedTask; + } + + + var count = _service.PerformContentVersionCleanup(DateTime.Now).Count; + + if (count > 0) + { + _logger.LogInformation("Deleted {count} ContentVersion(s)", count); + } + else + { + _logger.LogDebug("Task complete, no items were Deleted"); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs new file mode 100644 index 0000000000..ba78af33b4 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs @@ -0,0 +1,116 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.HealthChecks; +using Umbraco.Cms.Core.HealthChecks.NotificationMethods; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +/// +/// Hosted service implementation for recurring health check notifications. +/// +public class HealthCheckNotifierJob : IRecurringBackgroundJob +{ + + + public TimeSpan Period { get; private set; } + public TimeSpan Delay { get; private set; } + + private event EventHandler? _periodChanged; + public event EventHandler PeriodChanged + { + add { _periodChanged += value; } + remove { _periodChanged -= value; } + } + + private readonly HealthCheckCollection _healthChecks; + private readonly ILogger _logger; + private readonly HealthCheckNotificationMethodCollection _notifications; + private readonly IProfilingLogger _profilingLogger; + private readonly ICoreScopeProvider _scopeProvider; + private HealthChecksSettings _healthChecksSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration for health check settings. + /// The collection of healthchecks. + /// The collection of healthcheck notification methods. + /// Provides scopes for database operations. + /// The typed logger. + /// The profiling logger. + /// Parser of crontab expressions. + public HealthCheckNotifierJob( + IOptionsMonitor healthChecksSettings, + HealthCheckCollection healthChecks, + HealthCheckNotificationMethodCollection notifications, + ICoreScopeProvider scopeProvider, + ILogger logger, + IProfilingLogger profilingLogger, + ICronTabParser cronTabParser) + { + _healthChecksSettings = healthChecksSettings.CurrentValue; + _healthChecks = healthChecks; + _notifications = notifications; + _scopeProvider = scopeProvider; + _logger = logger; + _profilingLogger = profilingLogger; + + Period = healthChecksSettings.CurrentValue.Notification.Period; + Delay = DelayCalculator.GetDelay(healthChecksSettings.CurrentValue.Notification.FirstRunTime, cronTabParser, logger, TimeSpan.FromMinutes(3)); + + + healthChecksSettings.OnChange(x => + { + _healthChecksSettings = x; + Period = x.Notification.Period; + _periodChanged?.Invoke(this, EventArgs.Empty); + }); + } + + public async Task RunJobAsync() + { + if (_healthChecksSettings.Notification.Enabled == false) + { + return; + } + + // Ensure we use an explicit scope since we are running on a background thread and plugin health + // checks can be making service/database calls so we want to ensure the CallContext/Ambient scope + // isn't used since that can be problematic. + using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) + using (_profilingLogger.DebugDuration("Health checks executing", "Health checks complete")) + { + // Don't notify for any checks that are disabled, nor for any disabled just for notifications. + Guid[] disabledCheckIds = _healthChecksSettings.Notification.DisabledChecks + .Select(x => x.Id) + .Union(_healthChecksSettings.DisabledChecks + .Select(x => x.Id)) + .Distinct() + .ToArray(); + + IEnumerable checks = _healthChecks + .Where(x => disabledCheckIds.Contains(x.Id) == false); + + HealthCheckResults results = await HealthCheckResults.Create(checks); + results.LogResults(); + + // Send using registered notification methods that are enabled. + foreach (IHealthCheckNotificationMethod notificationMethod in _notifications.Where(x => x.Enabled)) + { + await notificationMethod.SendAsync(results); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJob.cs new file mode 100644 index 0000000000..a9849ddbb7 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJob.cs @@ -0,0 +1,90 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Sync; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +/// +/// Hosted service implementation for keep alive feature. +/// +public class KeepAliveJob : IRecurringBackgroundJob +{ + public TimeSpan Period { get => TimeSpan.FromMinutes(5); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IProfilingLogger _profilingLogger; + private KeepAliveSettings _keepAliveSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The current hosting environment + /// The configuration for keep alive settings. + /// The typed logger. + /// The profiling logger. + /// Factory for instances. + public KeepAliveJob( + IHostingEnvironment hostingEnvironment, + IOptionsMonitor keepAliveSettings, + ILogger logger, + IProfilingLogger profilingLogger, + IHttpClientFactory httpClientFactory) + { + _hostingEnvironment = hostingEnvironment; + _keepAliveSettings = keepAliveSettings.CurrentValue; + _logger = logger; + _profilingLogger = profilingLogger; + _httpClientFactory = httpClientFactory; + + keepAliveSettings.OnChange(x => _keepAliveSettings = x); + } + + public async Task RunJobAsync() + { + if (_keepAliveSettings.DisableKeepAliveTask) + { + return; + } + + using (_profilingLogger.DebugDuration("Keep alive executing", "Keep alive complete")) + { + var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl?.ToString(); + if (umbracoAppUrl.IsNullOrWhiteSpace()) + { + _logger.LogWarning("No umbracoApplicationUrl for service (yet), skip."); + return; + } + + // If the config is an absolute path, just use it + var keepAlivePingUrl = WebPath.Combine( + umbracoAppUrl!, + _hostingEnvironment.ToAbsolute(_keepAliveSettings.KeepAlivePingUrl)); + + try + { + var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl); + HttpClient httpClient = _httpClientFactory.CreateClient(Constants.HttpClients.IgnoreCertificateErrors); + _ = await httpClient.SendAsync(request); + } + catch (Exception ex) + { + _logger.LogError(ex, "Keep alive failed (at '{keepAlivePingUrl}').", keepAlivePingUrl); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs new file mode 100644 index 0000000000..1c745661cb --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs @@ -0,0 +1,73 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +/// +/// Log scrubbing hosted service. +/// +/// +/// Will only run on non-replica servers. +/// +public class LogScrubberJob : IRecurringBackgroundJob +{ + public TimeSpan Period { get => TimeSpan.FromHours(4); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + + private readonly IAuditService _auditService; + private readonly ILogger _logger; + private readonly IProfilingLogger _profilingLogger; + private readonly ICoreScopeProvider _scopeProvider; + private LoggingSettings _settings; + + /// + /// Initializes a new instance of the class. + /// + /// Service for handling audit operations. + /// The configuration for logging settings. + /// Provides scopes for database operations. + /// The typed logger. + /// The profiling logger. + public LogScrubberJob( + IAuditService auditService, + IOptionsMonitor settings, + ICoreScopeProvider scopeProvider, + ILogger logger, + IProfilingLogger profilingLogger) + { + + _auditService = auditService; + _settings = settings.CurrentValue; + _scopeProvider = scopeProvider; + _logger = logger; + _profilingLogger = profilingLogger; + settings.OnChange(x => _settings = x); + } + + public Task RunJobAsync() + { + + // Ensure we use an explicit scope since we are running on a background thread. + using (ICoreScope scope = _scopeProvider.CreateCoreScope()) + using (_profilingLogger.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) + { + _auditService.CleanLogs((int)_settings.MaxLogAge.TotalMinutes); + _ = scope.Complete(); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ReportSiteJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ReportSiteJob.cs new file mode 100644 index 0000000000..5d39b57add --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ReportSiteJob.cs @@ -0,0 +1,92 @@ +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Telemetry; +using Umbraco.Cms.Core.Telemetry.Models; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +public class ReportSiteJob : IRecurringBackgroundJob +{ + + public TimeSpan Period { get => TimeSpan.FromDays(1); } + public TimeSpan Delay { get => TimeSpan.FromMinutes(5); } + public ServerRole[] ServerRoles { get => Enum.GetValues(); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + + private static HttpClient _httpClient = new(); + private readonly ILogger _logger; + private readonly ITelemetryService _telemetryService; + + + public ReportSiteJob( + ILogger logger, + ITelemetryService telemetryService) + { + _logger = logger; + _telemetryService = telemetryService; + _httpClient = new HttpClient(); + } + + /// + /// Runs the background task to send the anonymous ID + /// to telemetry service + /// + public async Task RunJobAsync() + { + + if (_telemetryService.TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData) is false) + { + _logger.LogWarning("No telemetry marker found"); + + return; + } + + try + { + if (_httpClient.BaseAddress is null) + { + // Send data to LIVE telemetry + _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + +#if DEBUG + // Send data to DEBUG telemetry service + _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); +#endif + } + + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + + using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/")) + { + request.Content = new StringContent(JsonConvert.SerializeObject(telemetryReportData), Encoding.UTF8, + "application/json"); + + // Make a HTTP Post to telemetry service + // https://telemetry.umbraco.com/installs/ + // Fire & Forget, do not need to know if its a 200, 500 etc + using (await _httpClient.SendAsync(request)) + { + } + } + } + catch + { + // Silently swallow + // The user does not need the logs being polluted if our service has fallen over or is down etc + // Hence only logging this at a more verbose level (which users should not be using in production) + _logger.LogDebug("There was a problem sending a request to the Umbraco telemetry service"); + } + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJob.cs new file mode 100644 index 0000000000..f815366a21 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJob.cs @@ -0,0 +1,105 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Web; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +/// +/// Hosted service implementation for scheduled publishing feature. +/// +/// +/// Runs only on non-replica servers. +/// +public class ScheduledPublishingJob : IRecurringBackgroundJob +{ + public TimeSpan Period { get => TimeSpan.FromMinutes(1); } + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + + private readonly IContentService _contentService; + private readonly ILogger _logger; + private readonly ICoreScopeProvider _scopeProvider; + private readonly IServerMessenger _serverMessenger; + private readonly IUmbracoContextFactory _umbracoContextFactory; + + /// + /// Initializes a new instance of the class. + /// + public ScheduledPublishingJob( + IContentService contentService, + IUmbracoContextFactory umbracoContextFactory, + ILogger logger, + IServerMessenger serverMessenger, + ICoreScopeProvider scopeProvider) + { + _contentService = contentService; + _umbracoContextFactory = umbracoContextFactory; + _logger = logger; + _serverMessenger = serverMessenger; + _scopeProvider = scopeProvider; + } + + public Task RunJobAsync() + { + if (Suspendable.ScheduledPublishing.CanRun == false) + { + return Task.CompletedTask; + } + + try + { + // Ensure we run with an UmbracoContext, because this will run in a background task, + // and developers may be using the UmbracoContext in the event handlers. + + // TODO: or maybe not, CacheRefresherComponent already ensures a context when handling events + // - UmbracoContext 'current' needs to be refactored and cleaned up + // - batched messenger should not depend on a current HttpContext + // 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); + + /* 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. + * If we take a distributed write lock, we are certain that the multiple instances of the job will not run in parallel. + * It's possible that during the swapping process we may run this job more frequently than intended but this is not of great concern and it's + * only until the old SchedulingPublisher shuts down. */ + scope.EagerWriteLock(Constants.Locks.ScheduledPublishing); + try + { + // Run + IEnumerable result = _contentService.PerformScheduledPublish(DateTime.Now); + foreach (IGrouping grouped in result.GroupBy(x => x.Result)) + { + _logger.LogInformation( + "Scheduled publishing result: '{StatusCount}' items with status {Status}", + grouped.Count(), + grouped.Key); + } + } + finally + { + // If running on a temp context, we have to flush the messenger + if (contextReference.IsRoot) + { + _serverMessenger.SendMessages(); + } + } + } + catch (Exception ex) + { + // important to catch *everything* to ensure the task repeats + _logger.LogError(ex, "Failed."); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs new file mode 100644 index 0000000000..cc6e35bf83 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs @@ -0,0 +1,59 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; + +/// +/// Implements periodic database instruction processing as a hosted service. +/// +public class InstructionProcessJob : IRecurringBackgroundJob +{ + + public TimeSpan Period { get; } + public TimeSpan Delay { get => TimeSpan.FromMinutes(1); } + public ServerRole[] ServerRoles { get => Enum.GetValues(); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + private readonly ILogger _logger; + private readonly IServerMessenger _messenger; + + /// + /// Initializes a new instance of the class. + /// + /// Service broadcasting cache notifications to registered servers. + /// The typed logger. + /// The configuration for global settings. + public InstructionProcessJob( + IServerMessenger messenger, + ILogger logger, + IOptions globalSettings) + { + _messenger = messenger; + _logger = logger; + + Period = globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations; + } + + public Task RunJobAsync() + { + try + { + _messenger.Sync(); + } + catch (Exception e) + { + _logger.LogError(e, "Failed (will repeat)."); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJob.cs new file mode 100644 index 0000000000..8258da1a35 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJob.cs @@ -0,0 +1,102 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; + +/// +/// Implements periodic server "touching" (to mark as active/deactive) as a hosted service. +/// +public class TouchServerJob : IRecurringBackgroundJob +{ + public TimeSpan Period { get; private set; } + public TimeSpan Delay { get => TimeSpan.FromSeconds(15); } + + // Runs on all servers + public ServerRole[] ServerRoles { get => Enum.GetValues(); } + + private event EventHandler? _periodChanged; + public event EventHandler PeriodChanged { + add { _periodChanged += value; } + remove { _periodChanged -= value; } + } + + + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILogger _logger; + private readonly IServerRegistrationService _serverRegistrationService; + private readonly IServerRoleAccessor _serverRoleAccessor; + private GlobalSettings _globalSettings; + + /// + /// Initializes a new instance of the class. + /// + /// Services for server registrations. + /// The typed logger. + /// The configuration for global settings. + /// The hostingEnviroment. + /// The accessor for the server role + public TouchServerJob( + IServerRegistrationService serverRegistrationService, + IHostingEnvironment hostingEnvironment, + ILogger logger, + IOptionsMonitor globalSettings, + IServerRoleAccessor serverRoleAccessor) + { + _serverRegistrationService = serverRegistrationService ?? + throw new ArgumentNullException(nameof(serverRegistrationService)); + _hostingEnvironment = hostingEnvironment; + _logger = logger; + _globalSettings = globalSettings.CurrentValue; + _serverRoleAccessor = serverRoleAccessor; + + Period = _globalSettings.DatabaseServerRegistrar.WaitTimeBetweenCalls; + globalSettings.OnChange(x => + { + _globalSettings = x; + Period = x.DatabaseServerRegistrar.WaitTimeBetweenCalls; + + _periodChanged?.Invoke(this, EventArgs.Empty); + }); + } + + public Task RunJobAsync() + { + + // If the IServerRoleAccessor has been changed away from ElectedServerRoleAccessor this task no longer makes sense, + // since all it's used for is to allow the ElectedServerRoleAccessor + // to figure out what role a given server has, so we just stop this task. + if (_serverRoleAccessor is not ElectedServerRoleAccessor) + { + return Task.CompletedTask; + } + + var serverAddress = _hostingEnvironment.ApplicationMainUrl?.ToString(); + if (serverAddress.IsNullOrWhiteSpace()) + { + _logger.LogWarning("No umbracoApplicationUrl for service (yet), skip."); + return Task.CompletedTask; + } + + try + { + _serverRegistrationService.TouchServer( + serverAddress!, + _globalSettings.DatabaseServerRegistrar.StaleServerTimeout); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to update server record in database."); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJob.cs new file mode 100644 index 0000000000..ac88a8edce --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJob.cs @@ -0,0 +1,99 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +/// +/// Used to cleanup temporary file locations. +/// +/// +/// Will run on all servers - even though file upload should only be handled on the scheduling publisher, this will +/// ensure that in the case it happens on subscribers that they are cleaned up too. +/// +public class TempFileCleanupJob : IRecurringBackgroundJob +{ + public TimeSpan Period { get => TimeSpan.FromMinutes(60); } + + // Runs on all servers + public ServerRole[] ServerRoles { get => Enum.GetValues(); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + private readonly TimeSpan _age = TimeSpan.FromDays(1); + private readonly IIOHelper _ioHelper; + private readonly ILogger _logger; + private readonly DirectoryInfo[] _tempFolders; + + /// + /// Initializes a new instance of the class. + /// + /// Helper service for IO operations. + /// The typed logger. + public TempFileCleanupJob(IIOHelper ioHelper, ILogger logger) + { + _ioHelper = ioHelper; + _logger = logger; + + _tempFolders = _ioHelper.GetTempFolders(); + } + + public Task RunJobAsync() + { + foreach (DirectoryInfo folder in _tempFolders) + { + CleanupFolder(folder); + } + + return Task.CompletedTask; + } + + private void CleanupFolder(DirectoryInfo folder) + { + CleanFolderResult result = _ioHelper.CleanFolder(folder, _age); + switch (result.Status) + { + case CleanFolderResultStatus.FailedAsDoesNotExist: + _logger.LogDebug("The cleanup folder doesn't exist {Folder}", folder.FullName); + break; + case CleanFolderResultStatus.FailedWithException: + foreach (CleanFolderResult.Error error in result.Errors!) + { + _logger.LogError(error.Exception, "Could not delete temp file {FileName}", + error.ErroringFile.FullName); + } + + break; + } + + folder.Refresh(); // In case it's changed during runtime + if (!folder.Exists) + { + _logger.LogDebug("The cleanup folder doesn't exist {Folder}", folder.FullName); + return; + } + + FileInfo[] files = folder.GetFiles("*.*", SearchOption.AllDirectories); + foreach (FileInfo file in files) + { + if (DateTime.UtcNow - file.LastWriteTimeUtc > _age) + { + try + { + file.IsReadOnly = false; + file.Delete(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not delete temp file {FileName}", file.FullName); + } + } + } + } + +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedService.cs b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedService.cs new file mode 100644 index 0000000000..80afdb903c --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedService.cs @@ -0,0 +1,127 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Serilog.Core; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.HostedServices; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs; + +public static class RecurringBackgroundJobHostedService +{ + public static Func CreateHostedServiceFactory(IServiceProvider serviceProvider) => + (IRecurringBackgroundJob job) => + { + Type hostedServiceType = typeof(RecurringBackgroundJobHostedService<>).MakeGenericType(job.GetType()); + return (IHostedService)ActivatorUtilities.CreateInstance(serviceProvider, hostedServiceType, job); + }; +} + +/// +/// Runs a recurring background job inside a hosted service. +/// Generic version for DependencyInjection +/// +/// Type of the Job +public class RecurringBackgroundJobHostedService : RecurringHostedServiceBase where TJob : IRecurringBackgroundJob +{ + + private readonly ILogger> _logger; + private readonly IMainDom _mainDom; + private readonly IRuntimeState _runtimeState; + private readonly IServerRoleAccessor _serverRoleAccessor; + private readonly IEventAggregator _eventAggregator; + private readonly IRecurringBackgroundJob _job; + + public RecurringBackgroundJobHostedService( + IRuntimeState runtimeState, + ILogger> logger, + IMainDom mainDom, + IServerRoleAccessor serverRoleAccessor, + IEventAggregator eventAggregator, + TJob job) + : base(logger, job.Period, job.Delay) + { + _runtimeState = runtimeState; + _logger = logger; + _mainDom = mainDom; + _serverRoleAccessor = serverRoleAccessor; + _eventAggregator = eventAggregator; + _job = job; + + _job.PeriodChanged += (sender, e) => ChangePeriod(_job.Period); + } + + /// + public override async Task PerformExecuteAsync(object? state) + { + var executingNotification = new Notifications.RecurringBackgroundJobExecutingNotification(_job, new EventMessages()); + await _eventAggregator.PublishAsync(executingNotification); + + try + { + + if (_runtimeState.Level != RuntimeLevel.Run) + { + _logger.LogDebug("Job not running as runlevel not yet ready"); + await _eventAggregator.PublishAsync(new Notifications.RecurringBackgroundJobIgnoredNotification(_job, new EventMessages()).WithStateFrom(executingNotification)); + return; + } + + // Don't run on replicas nor unknown role servers + if (!_job.ServerRoles.Contains(_serverRoleAccessor.CurrentServerRole)) + { + _logger.LogDebug("Job not running on this server role"); + await _eventAggregator.PublishAsync(new Notifications.RecurringBackgroundJobIgnoredNotification(_job, new EventMessages()).WithStateFrom(executingNotification)); + return; + } + + // Ensure we do not run if not main domain, but do NOT lock it + if (!_mainDom.IsMainDom) + { + _logger.LogDebug("Job not running as not MainDom"); + await _eventAggregator.PublishAsync(new Notifications.RecurringBackgroundJobIgnoredNotification(_job, new EventMessages()).WithStateFrom(executingNotification)); + return; + } + + + await _job.RunJobAsync(); + await _eventAggregator.PublishAsync(new Notifications.RecurringBackgroundJobExecutedNotification(_job, new EventMessages()).WithStateFrom(executingNotification)); + + + } + catch (Exception ex) + { + await _eventAggregator.PublishAsync(new Notifications.RecurringBackgroundJobFailedNotification(_job, new EventMessages()).WithStateFrom(executingNotification)); + _logger.LogError(ex, "Unhandled exception in recurring background job."); + } + + } + + public override async Task StartAsync(CancellationToken cancellationToken) + { + var startingNotification = new Notifications.RecurringBackgroundJobStartingNotification(_job, new EventMessages()); + await _eventAggregator.PublishAsync(startingNotification); + + await base.StartAsync(cancellationToken); + + await _eventAggregator.PublishAsync(new Notifications.RecurringBackgroundJobStartedNotification(_job, new EventMessages()).WithStateFrom(startingNotification)); + + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + var stoppingNotification = new Notifications.RecurringBackgroundJobStoppingNotification(_job, new EventMessages()); + await _eventAggregator.PublishAsync(stoppingNotification); + + await base.StopAsync(cancellationToken); + + await _eventAggregator.PublishAsync(new Notifications.RecurringBackgroundJobStoppedNotification(_job, new EventMessages()).WithStateFrom(stoppingNotification)); + } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs new file mode 100644 index 0000000000..0e56bfa2b1 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs @@ -0,0 +1,81 @@ +using System.Linq; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.ModelsBuilder; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs; + +/// +/// A hosted service that discovers and starts hosted services for any recurring background jobs in the DI container. +/// +public class RecurringBackgroundJobHostedServiceRunner : IHostedService +{ + private readonly ILogger _logger; + private readonly List _jobs; + private readonly Func _jobFactory; + private IList _hostedServices = new List(); + + + public RecurringBackgroundJobHostedServiceRunner( + ILogger logger, + IEnumerable jobs, + Func jobFactory) + { + _jobs = jobs.ToList(); + _logger = logger; + _jobFactory = jobFactory; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Creating recurring background jobs hosted services"); + + // create hosted services for each background job + _hostedServices = _jobs.Select(_jobFactory).ToList(); + + _logger.LogInformation("Starting recurring background jobs hosted services"); + + foreach (IHostedService hostedService in _hostedServices) + { + try + { + _logger.LogInformation($"Starting background hosted service for {hostedService.GetType().Name}"); + await hostedService.StartAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.LogError(exception, $"Failed to start background hosted service for {hostedService.GetType().Name}"); + } + } + + _logger.LogInformation("Completed starting recurring background jobs hosted services"); + + + } + + public async Task StopAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Stopping recurring background jobs hosted services"); + + foreach (IHostedService hostedService in _hostedServices) + { + try + { + _logger.LogInformation($"Stopping background hosted service for {hostedService.GetType().Name}"); + await hostedService.StopAsync(stoppingToken).ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.LogError(exception, $"Failed to stop background hosted service for {hostedService.GetType().Name}"); + } + } + + _logger.LogInformation("Completed stopping recurring background jobs hosted services"); + + } +} diff --git a/src/Umbraco.Infrastructure/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Infrastructure/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..efaac24ceb --- /dev/null +++ b/src/Umbraco.Infrastructure/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Extensions; + +public static class ServiceCollectionExtensions +{ + /// + /// Adds a recurring background job with an implementation type of + /// to the specified . + /// + public static void AddRecurringBackgroundJob( + this IServiceCollection services) + where TJob : class, IRecurringBackgroundJob => + services.AddSingleton(); + + /// + /// Adds a recurring background job with an implementation type of + /// using the factory + /// to the specified . + /// + public static void AddRecurringBackgroundJob( + this IServiceCollection services, + Func implementationFactory) + where TJob : class, IRecurringBackgroundJob => + services.AddSingleton(implementationFactory); + +} + diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs index 37eeb668f9..1ecfcbf926 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs @@ -11,6 +11,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices; /// /// Recurring hosted service that executes the content history cleanup. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.ContentVersionCleanupJob instead. This class will be removed in Umbraco 14.")] public class ContentVersionCleanup : RecurringHostedServiceBase { private readonly ILogger _logger; diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index e1a10d9f71..5ee76d1a18 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -20,6 +20,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices; /// /// Hosted service implementation for recurring health check notifications. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.HealthCheckNotifierJob instead. This class will be removed in Umbraco 14.")] public class HealthCheckNotifier : RecurringHostedServiceBase { private readonly HealthCheckCollection _healthChecks; diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 5db59ff225..978ffa2dd1 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -17,6 +17,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices; /// /// Hosted service implementation for keep alive feature. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.KeepAliveJob instead. This class will be removed in Umbraco 14.")] public class KeepAlive : RecurringHostedServiceBase { private readonly IHostingEnvironment _hostingEnvironment; diff --git a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs index 9ae0dfe656..4c3df658c6 100644 --- a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs @@ -18,6 +18,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices; /// /// Will only run on non-replica servers. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.LogScrubberJob instead. This class will be removed in Umbraco 14.")] public class LogScrubber : RecurringHostedServiceBase { private readonly IAuditService _auditService; diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index c100da0ab2..a35f7aa956 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -107,7 +107,7 @@ public abstract class RecurringHostedServiceBase : IHostedService, IDisposable } /// - public Task StartAsync(CancellationToken cancellationToken) + public virtual Task StartAsync(CancellationToken cancellationToken) { using (!ExecutionContext.IsFlowSuppressed() ? (IDisposable)ExecutionContext.SuppressFlow() : null) { @@ -118,7 +118,7 @@ public abstract class RecurringHostedServiceBase : IHostedService, IDisposable } /// - public Task StopAsync(CancellationToken cancellationToken) + public virtual Task StopAsync(CancellationToken cancellationToken) { _period = Timeout.InfiniteTimeSpan; _timer?.Change(Timeout.Infinite, 0); diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index 7184aaf16e..77800ae7d6 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Telemetry.Models; namespace Umbraco.Cms.Infrastructure.HostedServices; +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.ReportSiteJob instead. This class will be removed in Umbraco 14.")] public class ReportSiteTask : RecurringHostedServiceBase { private static HttpClient _httpClient = new(); diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index da1fbaf157..efbd8017df 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -17,6 +17,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices; /// /// Runs only on non-replica servers. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.ScheduledPublishingJob instead. This class will be removed in Umbraco 14.")] public class ScheduledPublishing : RecurringHostedServiceBase { private readonly IContentService _contentService; diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index e4e5700496..fbbdab8878 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -13,6 +13,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration; /// /// Implements periodic database instruction processing as a hosted service. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.ServerRegistration.InstructionProcessJob instead. This class will be removed in Umbraco 14.")] public class InstructionProcessTask : RecurringHostedServiceBase { private readonly ILogger _logger; diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index 730282c6b0..a844c33ad6 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -15,6 +15,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration; /// /// Implements periodic server "touching" (to mark as active/deactive) as a hosted service. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.ServerRegistration.TouchServerJob instead. This class will be removed in Umbraco 14.")] public class TouchServerTask : RecurringHostedServiceBase { private readonly IHostingEnvironment _hostingEnvironment; diff --git a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs index cf46e38750..81de651e79 100644 --- a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs @@ -14,6 +14,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices; /// Will run on all servers - even though file upload should only be handled on the scheduling publisher, this will /// ensure that in the case it happens on subscribers that they are cleaned up too. /// +[Obsolete("Use Umbraco.Cms.Infrastructure.BackgroundJobs.TempFileCleanupJob instead. This class will be removed in Umbraco 14.")] public class TempFileCleanup : RecurringHostedServiceBase { private readonly TimeSpan _age = TimeSpan.FromDays(1); diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutedNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutedNotification.cs new file mode 100644 index 0000000000..8d2fbf96aa --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutedNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobExecutedNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobExecutedNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutingNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutingNotification.cs new file mode 100644 index 0000000000..71f5cf3edc --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobExecutingNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobExecutingNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobExecutingNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobFailedNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobFailedNotification.cs new file mode 100644 index 0000000000..594f01fc7b --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobFailedNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobFailedNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobFailedNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobIgnoredNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobIgnoredNotification.cs new file mode 100644 index 0000000000..8c3a0079d7 --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobIgnoredNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobIgnoredNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobIgnoredNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobNotification.cs new file mode 100644 index 0000000000..f9185cb412 --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public class RecurringBackgroundJobNotification : ObjectNotification + { + public IRecurringBackgroundJob Job { get; } + public RecurringBackgroundJobNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) => Job = target; + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartedNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartedNotification.cs new file mode 100644 index 0000000000..dca1e69d40 --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartedNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobStartedNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobStartedNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartingNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartingNotification.cs new file mode 100644 index 0000000000..3ee8d2a710 --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStartingNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobStartingNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobStartingNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppedNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppedNotification.cs new file mode 100644 index 0000000000..a1df71a4ee --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppedNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobStoppedNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobStoppedNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppingNotification.cs b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppingNotification.cs new file mode 100644 index 0000000000..985a20e286 --- /dev/null +++ b/src/Umbraco.Infrastructure/Notifications/RecurringBackgroundJobStoppingNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Cms.Infrastructure.Notifications +{ + public sealed class RecurringBackgroundJobStoppingNotification : RecurringBackgroundJobNotification + { + public RecurringBackgroundJobStoppingNotification(IRecurringBackgroundJob target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 994493e761..300c398a1f 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -45,7 +45,7 @@ public static partial class UmbracoBuilderExtensions .AddMvcAndRazor(configureMvc) .AddWebServer() .AddPreviewSupport() - .AddHostedServices() + .AddRecurringBackgroundJobs() .AddNuCache() .AddDistributedCache() .TryAddModelsBuilderDashboard() diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 0fa1d7fc4b..ceacba26b0 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog.Extensions.Logging; @@ -37,6 +38,9 @@ using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Core.WebAssets; +using Umbraco.Cms.Infrastructure.BackgroundJobs; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; using Umbraco.Cms.Infrastructure.DependencyInjection; using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration; @@ -176,6 +180,7 @@ public static partial class UmbracoBuilderExtensions /// /// Add Umbraco hosted services /// + [Obsolete("Use AddRecurringBackgroundJobs instead")] public static IUmbracoBuilder AddHostedServices(this IUmbracoBuilder builder) { builder.Services.AddHostedService(); @@ -191,6 +196,36 @@ public static partial class UmbracoBuilderExtensions new ReportSiteTask( provider.GetRequiredService>(), provider.GetRequiredService())); + + + return builder; + } + + /// + /// Add Umbraco recurring background jobs + /// + public static IUmbracoBuilder AddRecurringBackgroundJobs(this IUmbracoBuilder builder) + { + // Add background jobs + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(provider => + new ReportSiteJob( + provider.GetRequiredService>(), + provider.GetRequiredService())); + + + builder.Services.AddHostedService(); + builder.Services.AddSingleton(RecurringBackgroundJobHostedService.CreateHostedServiceFactory); + builder.Services.AddHostedService(); + + return builder; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs new file mode 100644 index 0000000000..cf9883603b --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.HealthChecks; +using Umbraco.Cms.Core.HealthChecks.NotificationMethods; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Cms.Tests.Common; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs; + +[TestFixture] +public class HealthCheckNotifierJobTests +{ + private Mock _mockNotificationMethod; + + private const string Check1Id = "00000000-0000-0000-0000-000000000001"; + private const string Check2Id = "00000000-0000-0000-0000-000000000002"; + private const string Check3Id = "00000000-0000-0000-0000-000000000003"; + + [Test] + public async Task Does_Not_Execute_When_Not_Enabled() + { + var sut = CreateHealthCheckNotifier(false); + await sut.RunJobAsync(); + VerifyNotificationsNotSent(); + } + + [Test] + public async Task Does_Not_Execute_With_No_Enabled_Notification_Methods() + { + var sut = CreateHealthCheckNotifier(notificationEnabled: false); + await sut.RunJobAsync(); + VerifyNotificationsNotSent(); + } + + [Test] + public async Task Executes_With_Enabled_Notification_Methods() + { + var sut = CreateHealthCheckNotifier(); + await sut.RunJobAsync(); + VerifyNotificationsSent(); + } + + [Test] + public async Task Executes_Only_Enabled_Checks() + { + var sut = CreateHealthCheckNotifier(); + await sut.RunJobAsync(); + _mockNotificationMethod.Verify( + x => x.SendAsync( + It.Is(y => + y.ResultsAsDictionary.Count == 1 && y.ResultsAsDictionary.ContainsKey("Check1"))), + Times.Once); + } + + private HealthCheckNotifierJob CreateHealthCheckNotifier( + bool enabled = true, + bool notificationEnabled = true) + { + var settings = new HealthChecksSettings + { + Notification = new HealthChecksNotificationSettings + { + Enabled = enabled, + DisabledChecks = new List { new() { Id = Guid.Parse(Check3Id) } }, + }, + DisabledChecks = new List { new() { Id = Guid.Parse(Check2Id) } }, + }; + var checks = new HealthCheckCollection(() => new List + { + new TestHealthCheck1(), + new TestHealthCheck2(), + new TestHealthCheck3(), + }); + + _mockNotificationMethod = new Mock(); + _mockNotificationMethod.SetupGet(x => x.Enabled).Returns(notificationEnabled); + var notifications = new HealthCheckNotificationMethodCollection(() => + new List { _mockNotificationMethod.Object }); + + + var mockScopeProvider = new Mock(); + var mockLogger = new Mock>(); + var mockProfilingLogger = new Mock(); + + return new HealthCheckNotifierJob( + new TestOptionsMonitor(settings), + checks, + notifications, + mockScopeProvider.Object, + mockLogger.Object, + mockProfilingLogger.Object, + Mock.Of()); + } + + private void VerifyNotificationsNotSent() => VerifyNotificationsSentTimes(Times.Never()); + + private void VerifyNotificationsSent() => VerifyNotificationsSentTimes(Times.Once()); + + private void VerifyNotificationsSentTimes(Times times) => + _mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny()), times); + + [HealthCheck(Check1Id, "Check1")] + private class TestHealthCheck1 : TestHealthCheck + { + } + + [HealthCheck(Check2Id, "Check2")] + private class TestHealthCheck2 : TestHealthCheck + { + } + + [HealthCheck(Check3Id, "Check3")] + private class TestHealthCheck3 : TestHealthCheck + { + } + + private class TestHealthCheck : HealthCheck + { + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => new("Check message"); + + public override async Task> GetStatus() => Enumerable.Empty(); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJobTests.cs new file mode 100644 index 0000000000..6821cbcccc --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/KeepAliveJobTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using Moq.Protected; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Tests.Common; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs; + +[TestFixture] +public class KeepAliveJobTests +{ + private Mock _mockHttpMessageHandler; + + private const string ApplicationUrl = "https://mysite.com"; + + [Test] + public async Task Does_Not_Execute_When_Not_Enabled() + { + var sut = CreateKeepAlive(false); + await sut.RunJobAsync(); + VerifyKeepAliveRequestNotSent(); + } + + + [Test] + public async Task Executes_And_Calls_Ping_Url() + { + var sut = CreateKeepAlive(); + await sut.RunJobAsync(); + VerifyKeepAliveRequestSent(); + } + + private KeepAliveJob CreateKeepAlive( + bool enabled = true) + { + var settings = new KeepAliveSettings { DisableKeepAliveTask = !enabled }; + + var mockHostingEnvironment = new Mock(); + mockHostingEnvironment.SetupGet(x => x.ApplicationMainUrl).Returns(new Uri(ApplicationUrl)); + mockHostingEnvironment.Setup(x => x.ToAbsolute(It.IsAny())) + .Returns((string s) => s.TrimStart('~')); + + var mockScopeProvider = new Mock(); + var mockLogger = new Mock>(); + var mockProfilingLogger = new Mock(); + + _mockHttpMessageHandler = new Mock(); + _mockHttpMessageHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)) + .Verifiable(); + _mockHttpMessageHandler.As().Setup(s => s.Dispose()); + var httpClient = new HttpClient(_mockHttpMessageHandler.Object); + + var mockHttpClientFactory = new Mock(MockBehavior.Strict); + mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny())).Returns(httpClient); + + return new KeepAliveJob( + mockHostingEnvironment.Object, + new TestOptionsMonitor(settings), + mockLogger.Object, + mockProfilingLogger.Object, + mockHttpClientFactory.Object); + } + + private void VerifyKeepAliveRequestNotSent() => VerifyKeepAliveRequestSentTimes(Times.Never()); + + private void VerifyKeepAliveRequestSent() => VerifyKeepAliveRequestSentTimes(Times.Once()); + + private void VerifyKeepAliveRequestSentTimes(Times times) => _mockHttpMessageHandler.Protected() + .Verify( + "SendAsync", + times, + ItExpr.Is(x => x.RequestUri.ToString() == $"{ApplicationUrl}/api/keepalive/ping"), + ItExpr.IsAny()); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJobTests.cs new file mode 100644 index 0000000000..6dd479364c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJobTests.cs @@ -0,0 +1,72 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Data; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Tests.Common; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs; + +[TestFixture] +public class LogScrubberJobTests +{ + private Mock _mockAuditService; + + private const int MaxLogAgeInMinutes = 60; + + [Test] + public async Task Executes_And_Scrubs_Logs() + { + var sut = CreateLogScrubber(); + await sut.RunJobAsync(); + VerifyLogsScrubbed(); + } + + private LogScrubberJob CreateLogScrubber() + { + var settings = new LoggingSettings { MaxLogAge = TimeSpan.FromMinutes(MaxLogAgeInMinutes) }; + + var mockScope = new Mock(); + var mockScopeProvider = new Mock(); + mockScopeProvider + .Setup(x => x.CreateCoreScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(mockScope.Object); + var mockLogger = new Mock>(); + var mockProfilingLogger = new Mock(); + + _mockAuditService = new Mock(); + + return new LogScrubberJob( + _mockAuditService.Object, + new TestOptionsMonitor(settings), + mockScopeProvider.Object, + mockLogger.Object, + mockProfilingLogger.Object); + } + + private void VerifyLogsNotScrubbed() => VerifyLogsScrubbed(Times.Never()); + + private void VerifyLogsScrubbed() => VerifyLogsScrubbed(Times.Once()); + + private void VerifyLogsScrubbed(Times times) => + _mockAuditService.Verify(x => x.CleanLogs(It.Is(y => y == MaxLogAgeInMinutes)), times); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJobTests.cs new file mode 100644 index 0000000000..eb1f0695c8 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ScheduledPublishingJobTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Data; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; +using Umbraco.Cms.Infrastructure.HostedServices; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs; + +[TestFixture] +public class ScheduledPublishingJobTests +{ + private Mock _mockContentService; + private Mock> _mockLogger; + + [Test] + public async Task Does_Not_Execute_When_Not_Enabled() + { + var sut = CreateScheduledPublishing(enabled: false); + await sut.RunJobAsync(); + VerifyScheduledPublishingNotPerformed(); + } + + [Test] + public async Task Executes_And_Performs_Scheduled_Publishing() + { + var sut = CreateScheduledPublishing(); + await sut.RunJobAsync(); + VerifyScheduledPublishingPerformed(); + } + + private ScheduledPublishingJob CreateScheduledPublishing( + bool enabled = true) + { + if (enabled) + { + Suspendable.ScheduledPublishing.Resume(); + } + else + { + Suspendable.ScheduledPublishing.Suspend(); + } + + _mockContentService = new Mock(); + + var mockUmbracoContextFactory = new Mock(); + mockUmbracoContextFactory.Setup(x => x.EnsureUmbracoContext()) + .Returns(new UmbracoContextReference(null, false, null)); + + _mockLogger = new Mock>(); + + var mockServerMessenger = new Mock(); + + var mockScopeProvider = new Mock(); + mockScopeProvider + .Setup(x => x.CreateCoreScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of()); + + return new ScheduledPublishingJob( + _mockContentService.Object, + mockUmbracoContextFactory.Object, + _mockLogger.Object, + mockServerMessenger.Object, + mockScopeProvider.Object); + } + + private void VerifyScheduledPublishingNotPerformed() => VerifyScheduledPublishingPerformed(Times.Never()); + + private void VerifyScheduledPublishingPerformed() => VerifyScheduledPublishingPerformed(Times.Once()); + + private void VerifyScheduledPublishingPerformed(Times times) => + _mockContentService.Verify(x => x.PerformScheduledPublish(It.IsAny()), times); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs new file mode 100644 index 0000000000..f2e6e574ef --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; + +[TestFixture] +public class InstructionProcessJobTests +{ + private Mock _mockDatabaseServerMessenger; + + + [Test] + public async Task Executes_And_Touches_Server() + { + var sut = CreateInstructionProcessJob(); + await sut.RunJobAsync(); + VerifyMessengerSynced(); + } + + private InstructionProcessJob CreateInstructionProcessJob() + { + + var mockLogger = new Mock>(); + + _mockDatabaseServerMessenger = new Mock(); + + var settings = new GlobalSettings(); + + return new InstructionProcessJob( + _mockDatabaseServerMessenger.Object, + mockLogger.Object, + Options.Create(settings)); + } + + private void VerifyMessengerNotSynced() => VerifyMessengerSyncedTimes(Times.Never()); + + private void VerifyMessengerSynced() => VerifyMessengerSyncedTimes(Times.Once()); + + private void VerifyMessengerSyncedTimes(Times times) => _mockDatabaseServerMessenger.Verify(x => x.Sync(), times); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJobTests.cs new file mode 100644 index 0000000000..0da3b15e1b --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/TouchServerJobTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; +using Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration; +using Umbraco.Cms.Tests.Common; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; + +[TestFixture] +public class TouchServerJobTests +{ + private Mock _mockServerRegistrationService; + + private const string ApplicationUrl = "https://mysite.com/"; + private readonly TimeSpan _staleServerTimeout = TimeSpan.FromMinutes(2); + + + [Test] + public async Task Does_Not_Execute_When_Application_Url_Is_Not_Available() + { + var sut = CreateTouchServerTask(applicationUrl: string.Empty); + await sut.RunJobAsync(); + VerifyServerNotTouched(); + } + + [Test] + public async Task Executes_And_Touches_Server() + { + var sut = CreateTouchServerTask(); + await sut.RunJobAsync(); + VerifyServerTouched(); + } + + [Test] + public async Task Does_Not_Execute_When_Role_Accessor_Is_Not_Elected() + { + var sut = CreateTouchServerTask(useElection: false); + await sut.RunJobAsync(); + VerifyServerNotTouched(); + } + + private TouchServerJob CreateTouchServerTask( + RuntimeLevel runtimeLevel = RuntimeLevel.Run, + string applicationUrl = ApplicationUrl, + bool useElection = true) + { + var mockRequestAccessor = new Mock(); + mockRequestAccessor.SetupGet(x => x.ApplicationMainUrl) + .Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(ApplicationUrl) : null); + + var mockRunTimeState = new Mock(); + mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel); + + var mockLogger = new Mock>(); + + _mockServerRegistrationService = new Mock(); + + var settings = new GlobalSettings + { + DatabaseServerRegistrar = new DatabaseServerRegistrarSettings { StaleServerTimeout = _staleServerTimeout }, + }; + + IServerRoleAccessor roleAccessor = useElection + ? new ElectedServerRoleAccessor(_mockServerRegistrationService.Object) + : new SingleServerRoleAccessor(); + + return new TouchServerJob( + _mockServerRegistrationService.Object, + mockRequestAccessor.Object, + mockLogger.Object, + new TestOptionsMonitor(settings), + roleAccessor); + } + + private void VerifyServerNotTouched() => VerifyServerTouchedTimes(Times.Never()); + + private void VerifyServerTouched() => VerifyServerTouchedTimes(Times.Once()); + + private void VerifyServerTouchedTimes(Times times) => _mockServerRegistrationService + .Verify( + x => x.TouchServer( + It.Is(y => y == ApplicationUrl), + It.Is(y => y == _staleServerTimeout)), + times); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs new file mode 100644 index 0000000000..c37094e6ac --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs +{ + [TestFixture] + public class TempFileCleanupJobTests + { + private Mock _mockIOHelper; + private readonly string _testPath = Path.Combine(TestContext.CurrentContext.TestDirectory.Split("bin")[0], "App_Data", "TEMP"); + + + [Test] + public async Task Executes_And_Cleans_Files() + { + TempFileCleanupJob sut = CreateTempFileCleanupJob(); + await sut.RunJobAsync(); + VerifyFilesCleaned(); + } + + private TempFileCleanupJob CreateTempFileCleanupJob() + { + + _mockIOHelper = new Mock(); + _mockIOHelper.Setup(x => x.GetTempFolders()) + .Returns(new DirectoryInfo[] { new(_testPath) }); + _mockIOHelper.Setup(x => x.CleanFolder(It.IsAny(), It.IsAny())) + .Returns(CleanFolderResult.Success()); + + var mockLogger = new Mock>(); + + return new TempFileCleanupJob(_mockIOHelper.Object,mockLogger.Object); + } + + private void VerifyFilesNotCleaned() => VerifyFilesCleaned(Times.Never()); + + private void VerifyFilesCleaned() => VerifyFilesCleaned(Times.Once()); + + private void VerifyFilesCleaned(Times times) => _mockIOHelper.Verify(x => x.CleanFolder(It.Is(y => y.FullName == _testPath), It.IsAny()), times); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceTests.cs new file mode 100644 index 0000000000..806520bf41 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceTests.cs @@ -0,0 +1,208 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Infrastructure.Notifications; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs; + +[TestFixture] +public class RecurringBackgroundJobHostedServiceTests +{ + + [TestCase(RuntimeLevel.Boot)] + [TestCase(RuntimeLevel.Install)] + [TestCase(RuntimeLevel.Unknown)] + [TestCase(RuntimeLevel.Upgrade)] + [TestCase(RuntimeLevel.BootFailed)] + public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) + { + var mockJob = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, runtimeLevel: runtimeLevel); + await sut.PerformExecuteAsync(null); + + mockJob.Verify(job => job.RunJobAsync(), Times.Never); + } + + [Test] + public async Task Publishes_Ignored_Notification_When_Runtime_State_Is_Not_Run() + { + var mockJob = new Mock(); + var mockEventAggregator = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, runtimeLevel: RuntimeLevel.Unknown, mockEventAggregator: mockEventAggregator); + await sut.PerformExecuteAsync(null); + + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [TestCase(ServerRole.Unknown)] + [TestCase(ServerRole.Subscriber)] + public async Task Does_Not_Execute_When_Server_Role_Is_NotDefault(ServerRole serverRole) + { + var mockJob = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, serverRole: serverRole); + await sut.PerformExecuteAsync(null); + + mockJob.Verify(job => job.RunJobAsync(), Times.Never); + } + + [TestCase(ServerRole.Single)] + [TestCase(ServerRole.SchedulingPublisher)] + public async Task Does_Executes_When_Server_Role_Is_Default(ServerRole serverRole) + { + var mockJob = new Mock(); + mockJob.Setup(x => x.ServerRoles).Returns(IRecurringBackgroundJob.DefaultServerRoles); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, serverRole: serverRole); + await sut.PerformExecuteAsync(null); + + mockJob.Verify(job => job.RunJobAsync(), Times.Once); + } + + [Test] + public async Task Does_Execute_When_Server_Role_Is_Subscriber_And_Specified() + { + var mockJob = new Mock(); + mockJob.Setup(x => x.ServerRoles).Returns(new ServerRole[] { ServerRole.Subscriber }); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, serverRole: ServerRole.Subscriber); + await sut.PerformExecuteAsync(null); + + mockJob.Verify(job => job.RunJobAsync(), Times.Once); + } + + [Test] + public async Task Publishes_Ignored_Notification_When_Server_Role_Is_Not_Allowed() + { + var mockJob = new Mock(); + var mockEventAggregator = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, serverRole: ServerRole.Unknown, mockEventAggregator: mockEventAggregator); + await sut.PerformExecuteAsync(null); + + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public async Task Does_Not_Execute_When_Not_Main_Dom() + { + var mockJob = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, isMainDom: false); + await sut.PerformExecuteAsync(null); + + mockJob.Verify(job => job.RunJobAsync(), Times.Never); + } + + [Test] + public async Task Publishes_Ignored_Notification_When_Not_Main_Dom() + { + var mockJob = new Mock(); + var mockEventAggregator = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, isMainDom: false, mockEventAggregator: mockEventAggregator); + await sut.PerformExecuteAsync(null); + + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + + + [Test] + public async Task Publishes_Executed_Notification_When_Run() + { + var mockJob = new Mock(); + mockJob.Setup(x => x.ServerRoles).Returns(IRecurringBackgroundJob.DefaultServerRoles); + var mockEventAggregator = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, mockEventAggregator: mockEventAggregator); + await sut.PerformExecuteAsync(null); + + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public async Task Publishes_Failed_Notification_When_Fails() + { + var mockJob = new Mock(); + mockJob.Setup(x => x.ServerRoles).Returns(IRecurringBackgroundJob.DefaultServerRoles); + mockJob.Setup(x => x.RunJobAsync()).Throws(); + var mockEventAggregator = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, mockEventAggregator: mockEventAggregator); + await sut.PerformExecuteAsync(null); + + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public async Task Publishes_Start_And_Stop_Notifications() + { + var mockJob = new Mock(); + var mockEventAggregator = new Mock(); + + var sut = CreateRecurringBackgroundJobHostedService(mockJob, isMainDom: false, mockEventAggregator: mockEventAggregator); + await sut.StartAsync(CancellationToken.None); + await sut.StopAsync(CancellationToken.None); + + + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + + + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockEventAggregator.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + + } + + + private RecurringHostedServiceBase CreateRecurringBackgroundJobHostedService( + Mock mockJob, + RuntimeLevel runtimeLevel = RuntimeLevel.Run, + ServerRole serverRole = ServerRole.Single, + bool isMainDom = true, + Mock mockEventAggregator = null) + { + var mockRunTimeState = new Mock(); + mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel); + + var mockServerRegistrar = new Mock(); + mockServerRegistrar.Setup(x => x.CurrentServerRole).Returns(serverRole); + + var mockMainDom = new Mock(); + mockMainDom.SetupGet(x => x.IsMainDom).Returns(isMainDom); + + var mockLogger = new Mock>>(); + if (mockEventAggregator == null) + { + mockEventAggregator = new Mock(); + } + + return new RecurringBackgroundJobHostedService( + mockRunTimeState.Object, + mockLogger.Object, + mockMainDom.Object, + mockServerRegistrar.Object, + mockEventAggregator.Object, + mockJob.Object); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs index 626129b3b7..03d7f344a6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs @@ -23,6 +23,7 @@ using Umbraco.Cms.Tests.Common; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices; [TestFixture] +[Obsolete("Replaced by BackgroundJobs.Jobs.HealthCheckNotifierJobTests")] public class HealthCheckNotifierTests { private Mock _mockNotificationMethod; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs index f0ef4cd278..4631bb21a1 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs @@ -21,6 +21,7 @@ using Umbraco.Cms.Tests.Common; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices; [TestFixture] +[Obsolete("Replaced by BackgroundJobs.Jobs.KeepAliveJobTests")] public class KeepAliveTests { private Mock _mockHttpMessageHandler; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs index 553a4f451c..98fdf4c453 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs @@ -19,6 +19,7 @@ using Umbraco.Cms.Tests.Common; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices; [TestFixture] +[Obsolete("Replaced by BackgroundJobs.Jobs.LogScrubberJobTests")] public class LogScrubberTests { private Mock _mockAuditService; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs index 609dfbb7fa..3eb7756d7f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs @@ -19,6 +19,7 @@ using Umbraco.Cms.Infrastructure.HostedServices; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices; [TestFixture] +[Obsolete("Replaced by BackgroundJobs.Jobs.ScheduledPublishingJobTests")] public class ScheduledPublishingTests { private Mock _mockContentService; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs index fd24d60019..1513c6a5d4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRegistration; [TestFixture] +[Obsolete("Replaced by BackgroundJobs.Jobs.ServerRegistration.InstructionProcessJobTests")] public class InstructionProcessTaskTests { private Mock _mockDatabaseServerMessenger; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs index b379f8d34b..91d156e519 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs @@ -16,6 +16,7 @@ using Umbraco.Cms.Tests.Common; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRegistration; [TestFixture] +[Obsolete("Replaced by BackgroundJobs.Jobs.ServerRegistration.TouchServerJobTests")] public class TouchServerTaskTests { private Mock _mockServerRegistrationService; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs index 851afc269b..2128f917b9 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Infrastructure.HostedServices; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { [TestFixture] + [Obsolete("Replaced by BackgroundJobs.Jobs.TempFileCleanupTests")] public class TempFileCleanupTests { private Mock _mockIOHelper; From 69139eda522840a09c1b84705311542133a7c059 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 4 Oct 2023 11:11:30 +0200 Subject: [PATCH 029/146] Allow dashboard content to grow --- .../src/less/components/application/umb-dashboard.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less index 03153973ff..1bc54c13a0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less @@ -19,6 +19,7 @@ } .umb-dashboard__content { + flex: 1; padding: 20px; overflow: auto; } From 7a57e8861d292caf38fd9765e38c3620e41e086e Mon Sep 17 00:00:00 2001 From: Richard Jackson Date: Thu, 2 Nov 2023 13:46:12 +0000 Subject: [PATCH 030/146] Removed parenthesis from compiler directive Removes warning from Umbraco.Web.UI as part of #15015 --- src/Umbraco.Web.UI/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Startup.cs b/src/Umbraco.Web.UI/Startup.cs index 1d7f49b1e5..b468436eec 100644 --- a/src/Umbraco.Web.UI/Startup.cs +++ b/src/Umbraco.Web.UI/Startup.cs @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Web.UI { app.UseDeveloperExceptionPage(); } -#if (UseHttpsRedirect) +#if UseHttpsRedirect app.UseHttpsRedirection(); #endif From 7b0ae3c5d9d12cdefde815e7d216da1e7fa2a44a Mon Sep 17 00:00:00 2001 From: Zeb Sadiq Date: Thu, 2 Nov 2023 14:31:42 +0000 Subject: [PATCH 031/146] Fixed one warning for Umbraco.Infrastructure . 'AesCryptoServiceProvider' is obsolete. --- src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs index 3c7e4eabee..10b5974c52 100644 --- a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs @@ -131,7 +131,9 @@ public class MemberPasswordHasher : UmbracoPasswordHasher switch (algorithmName) { case "AES": - algorithm = new AesCryptoServiceProvider { Key = StringToByteArray(decryptionKey), IV = new byte[16] }; + algorithm = Aes.Create(); + algorithm.Key = StringToByteArray(decryptionKey); + algorithm.IV = new byte[16]; break; default: throw new NotSupportedException($"The algorithm ({algorithmName}) is not supported"); From c7f9417063c1794c6c1362a91f31ea9575078e40 Mon Sep 17 00:00:00 2001 From: Jason Elkin Date: Thu, 2 Nov 2023 13:40:11 +0000 Subject: [PATCH 032/146] Remove legacy test project --- legacy/Umbraco.Tests/App.config | 119 ---------- .../Umbraco.Tests/Properties/AssemblyInfo.cs | 41 ---- .../Umbraco.Tests/Published/ModelTypeTests.cs | 43 ---- .../Routing/BaseUrlProviderTest.cs | 45 ---- .../Routing/MediaUrlProviderTests.cs | 206 ------------------ legacy/Umbraco.Tests/Umbraco.Tests.csproj | 199 ----------------- .../AuthenticationControllerTests.cs | 122 ----------- .../Umbraco.Tests/unit-test-log4net.CI.config | 6 - legacy/Umbraco.Tests/unit-test-logger.config | 20 -- 9 files changed, 801 deletions(-) delete mode 100644 legacy/Umbraco.Tests/App.config delete mode 100644 legacy/Umbraco.Tests/Properties/AssemblyInfo.cs delete mode 100644 legacy/Umbraco.Tests/Published/ModelTypeTests.cs delete mode 100644 legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs delete mode 100644 legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs delete mode 100644 legacy/Umbraco.Tests/Umbraco.Tests.csproj delete mode 100644 legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs delete mode 100644 legacy/Umbraco.Tests/unit-test-log4net.CI.config delete mode 100644 legacy/Umbraco.Tests/unit-test-logger.config diff --git a/legacy/Umbraco.Tests/App.config b/legacy/Umbraco.Tests/App.config deleted file mode 100644 index 3bb668535d..0000000000 --- a/legacy/Umbraco.Tests/App.config +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/legacy/Umbraco.Tests/Properties/AssemblyInfo.cs b/legacy/Umbraco.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index ec1ddca2f8..0000000000 --- a/legacy/Umbraco.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Umbraco.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Umbraco.Tests")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("33ddf9b7-505c-4a12-8370-7fee9de5df6d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -// Internals must be visible to DynamicProxyGenAssembly2 -// in order to mock loggers loggers with types from the assembly -// I.E. Mock.Of>() -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/legacy/Umbraco.Tests/Published/ModelTypeTests.cs b/legacy/Umbraco.Tests/Published/ModelTypeTests.cs deleted file mode 100644 index 2702b82812..0000000000 --- a/legacy/Umbraco.Tests/Published/ModelTypeTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using NUnit.Framework; -using Umbraco.Cms.Core.Models.PublishedContent; - -namespace Umbraco.Tests.Published -{ - [TestFixture] - public class ModelTypeTests - { - - //TODO these is not easy to move to the Unittest project due to underlysing NotImplementedException of Type.IsSZArray - [Test] - public void ModelTypeToStringTests() - { - var modelType = ModelType.For("alias1"); - var modelTypeArray = modelType.MakeArrayType(); - - Assert.AreEqual("{alias1}", modelType.ToString()); - - // there's an "*" there because the arrays are not true SZArray - but that changes when we map - - Assert.AreEqual("{alias1}[*]", modelTypeArray.ToString()); - var enumArray = typeof(IEnumerable<>).MakeGenericType(modelTypeArray); - Assert.AreEqual("System.Collections.Generic.IEnumerable`1[{alias1}[*]]", enumArray.ToString()); - } - - [Test] - public void ModelTypeFullNameTests() - { - Assert.AreEqual("{alias1}", ModelType.For("alias1").FullName); - - Type type = ModelType.For("alias1"); - Assert.AreEqual("{alias1}", type.FullName); - - // there's an "*" there because the arrays are not true SZArray - but that changes when we map - Assert.AreEqual("{alias1}[*]", ModelType.For("alias1").MakeArrayType().FullName); - // note the inner assembly qualified name - Assert.AreEqual("System.Collections.Generic.IEnumerable`1[[{alias1}[*], Umbraco.Core, Version=0.5.0.0, Culture=neutral, PublicKeyToken=null]]", typeof(IEnumerable<>).MakeGenericType(ModelType.For("alias1").MakeArrayType()).FullName); - } - - } -} diff --git a/legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs b/legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs deleted file mode 100644 index 6be2b72ad1..0000000000 --- a/legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Routing -{ - public abstract class BaseUrlProviderTest : BaseWebTest - { - protected IUmbracoContextAccessor UmbracoContextAccessor { get; } = new TestUmbracoContextAccessor(); - - protected abstract bool HideTopLevelNodeFromPath { get; } - - protected override void Compose() - { - base.Compose(); - Builder.Services.AddTransient(); - } - - protected override void ComposeSettings() - { - var contentSettings = new ContentSettings(); - var userPasswordConfigurationSettings = new UserPasswordConfigurationSettings(); - - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(contentSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings)); - } - - protected IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider) - { - var webRoutingSettings = new WebRoutingSettings(); - return new UrlProvider( - new TestUmbracoContextAccessor(umbracoContext), - Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(new[] { urlProvider }), - new MediaUrlProviderCollection(Enumerable.Empty()), - Mock.Of()); - } - } -} diff --git a/legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs deleted file mode 100644 index ac79dee6cf..0000000000 --- a/legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Linq; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.PublishedContent; -using Umbraco.Tests.TestHelpers; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class MediaUrlProviderTests : BaseWebTest - { - private DefaultMediaUrlProvider _mediaUrlProvider; - - public override void SetUp() - { - base.SetUp(); - - var loggerFactory = NullLoggerFactory.Instance; - var mediaFileManager = new MediaFileManager(Mock.Of(), Mock.Of(), - loggerFactory.CreateLogger(), Mock.Of()); - var contentSettings = new ContentSettings(); - var dataTypeService = Mock.Of(); - var propertyEditors = new MediaUrlGeneratorCollection(new IMediaUrlGenerator[] - { - new FileUploadPropertyEditor(DataValueEditorFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, UploadAutoFillProperties, ContentService), - new ImageCropperPropertyEditor(DataValueEditorFactory, loggerFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, IOHelper, UploadAutoFillProperties, ContentService), - }); - _mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors, UriUtility); - } - - public override void TearDown() - { - base.TearDown(); - - _mediaUrlProvider = null; - } - - [Test] - public void Get_Media_Url_Resolves_Url_From_Upload_Property_Editor() - { - const string expected = "/media/rfeiw584/test.jpg"; - - var umbracoContext = GetUmbracoContext("/"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Resolves_Url_From_Image_Cropper_Property_Editor() - { - const string expected = "/media/rfeiw584/test.jpg"; - - var configuration = new ImageCropperConfiguration(); - var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue - { - Src = expected - }); - - var umbracoContext = GetUmbracoContext("/"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Can_Resolve_Absolute_Url() - { - const string mediaUrl = "/media/rfeiw584/test.jpg"; - var expected = $"http://localhost{mediaUrl}"; - - var umbracoContext = GetUmbracoContext("http://localhost"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute() - { - const string expected = "http://localhost/media/rfeiw584/test.jpg"; - - var umbracoContext = GetUmbracoContext("http://localhost"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Relative); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported() - { - var umbracoContext = GetUmbracoContext("/"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute, propertyAlias: "test"); - - Assert.AreEqual(string.Empty, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Can_Resolve_Variant_Property_Url() - { - var umbracoContext = GetUmbracoContext("http://localhost"); - - var umbracoFilePropertyType = CreatePropertyType(Constants.PropertyEditors.Aliases.UploadField, null, ContentVariation.Culture); - - const string enMediaUrl = "/media/rfeiw584/en.jpg"; - const string daMediaUrl = "/media/uf8ewud2/da.jpg"; - - var property = new SolidPublishedPropertyWithLanguageVariants - { - Alias = "umbracoFile", - PropertyType = umbracoFilePropertyType, - }; - - property.SetSourceValue("en", enMediaUrl, true); - property.SetSourceValue("da", daMediaUrl); - - var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); - var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto, "da"); - Assert.AreEqual(daMediaUrl, resolvedUrl); - } - - private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext) - { - var webRoutingSettings = new WebRoutingSettings(); - return new UrlProvider( - new TestUmbracoContextAccessor(umbracoContext), - Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(Enumerable.Empty()), - new MediaUrlProviderCollection(new []{_mediaUrlProvider}), - Mock.Of() - ); - } - - private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration) - { - var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); - - var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), - new[] {umbracoFilePropertyType}, ContentVariation.Nothing); - - return new SolidPublishedContent(contentType) - { - Id = 1234, - Key = Guid.NewGuid(), - Properties = new[] - { - new SolidPublishedProperty - { - Alias = "umbracoFile", - SolidSourceValue = propertyValue, - SolidHasValue = true, - PropertyType = umbracoFilePropertyType - } - } - }; - } - - private static PublishedPropertyType CreatePropertyType(string propertyEditorAlias, object dataTypeConfiguration, ContentVariation variation) - { - var uploadDataType = new PublishedDataType(1234, propertyEditorAlias, new Lazy(() => dataTypeConfiguration)); - - var propertyValueConverters = new PropertyValueConverterCollection(new IPropertyValueConverter[] - { - new UploadPropertyConverter(), - new ImageCropperValueConverter(Mock.Of>()), - }); - - var publishedModelFactory = Mock.Of(); - var publishedContentTypeFactory = new Mock(); - publishedContentTypeFactory.Setup(x => x.GetDataType(It.IsAny())) - .Returns(uploadDataType); - - return new PublishedPropertyType("umbracoFile", 42, true, variation, propertyValueConverters, publishedModelFactory, publishedContentTypeFactory.Object); - } - } -} diff --git a/legacy/Umbraco.Tests/Umbraco.Tests.csproj b/legacy/Umbraco.Tests/Umbraco.Tests.csproj deleted file mode 100644 index a83c0d6453..0000000000 --- a/legacy/Umbraco.Tests/Umbraco.Tests.csproj +++ /dev/null @@ -1,199 +0,0 @@ - - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5D3B8245-ADA6-453F-A008-50ED04BFE770} - Library - Properties - Umbraco.Tests - Umbraco.Tests - v4.7.2 - 512 - ..\ - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - - - true - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - 8 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - latest - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.0.0 - - - - - 1.11.31 - - - - - - - - - - - - 5.0.0 - - - - - 5.0.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Always - - - - - {29aa69d9-b597-4395-8d42-43b1263c240a} - Umbraco.Core - - - {0fad7d2a-d7dd-45b1-91fd-488bb6cdacea} - Umbraco.Examine.Lucene - - - {3ae7bf57-966b-45a5-910a-954d7c554441} - Umbraco.Infrastructure - - - {33085570-9bf2-4065-a9b0-a29d920d13ba} - Umbraco.Persistence.SqlCe - - - {f6de8da0-07cc-4ef2-8a59-2bc81dbb3830} - Umbraco.PublishedCache.NuCache - - - {a499779c-1b3b-48a8-b551-458e582e6e96} - Umbraco.Tests.Common - - - {651E1350-91B6-44B7-BD60-7207006D7003} - Umbraco.Web - - - - - - - - - $(NuGetPackageFolders.Split(';')[0]) - - - - - - - - - - \ No newline at end of file diff --git a/legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs deleted file mode 100644 index a138c6ce94..0000000000 --- a/legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Features; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Web.Controllers -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.None)] - public class AuthenticationControllerTests : TestWithDatabaseBase - { - protected override void ComposeApplication(bool withApplication) - { - base.ComposeApplication(withApplication); - //if (!withApplication) return; - - // replace the true IUserService implementation with a mock - // so that each test can configure the service to their liking - Builder.Services.AddUnique(f => Mock.Of()); - - // kill the true IEntityService too - Builder.Services.AddUnique(f => Mock.Of()); - - Builder.Services.AddUnique(); - } - - - // TODO Reintroduce when moved to .NET Core - // [Test] - // public async System.Threading.Tasks.Task GetCurrentUser_Fips() - // { - // ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) - // { - // //setup some mocks - // var userServiceMock = Mock.Get(ServiceContext.UserService); - // userServiceMock.Setup(service => service.GetUserById(It.IsAny())) - // .Returns(() => null); - // - // if (Thread.GetDomain().GetData(".appPath") != null) - // { - // HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter())); - // } - // else - // { - // var baseDir = IOHelper.MapPath("").TrimEnd(Path.DirectorySeparatorChar); - // HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter())); - // } - // - // var usersController = new AuthenticationController( - // new TestUserPasswordConfig(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // umbracoContextAccessor, - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance() - // ); - // return usersController; - // } - // - // Mock.Get(Current.SqlContext) - // .Setup(x => x.Query()) - // .Returns(new Query(Current.SqlContext)); - // - // var syntax = new SqlCeSyntaxProvider(); - // - // Mock.Get(Current.SqlContext) - // .Setup(x => x.SqlSyntax) - // .Returns(syntax); - // - // var mappers = new MapperCollection(new[] - // { - // new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) - // }); - // - // Mock.Get(Current.SqlContext) - // .Setup(x => x.Mappers) - // .Returns(mappers); - // - // // Testing what happens if the system were configured to only use FIPS-compliant algorithms - // var typ = typeof(CryptoConfig); - // var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); - // var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); - // var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); - // var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; - // - // try - // { - // if (!originalFipsValue) - // { - // haveFld.SetValue(null, true); - // isFld.SetValue(null, true); - // } - // - // var runner = new TestRunner(CtrlFactory); - // var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get); - // - // var obj = JsonConvert.DeserializeObject(response.Item2); - // Assert.AreEqual(-1, obj.UserId); - // } - // finally - // { - // if (!originalFipsValue) - // { - // haveFld.SetValue(null, false); - // isFld.SetValue(null, false); - // } - // } - // } - } -} diff --git a/legacy/Umbraco.Tests/unit-test-log4net.CI.config b/legacy/Umbraco.Tests/unit-test-log4net.CI.config deleted file mode 100644 index d7035032ef..0000000000 --- a/legacy/Umbraco.Tests/unit-test-log4net.CI.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/legacy/Umbraco.Tests/unit-test-logger.config b/legacy/Umbraco.Tests/unit-test-logger.config deleted file mode 100644 index 62fa1353b2..0000000000 --- a/legacy/Umbraco.Tests/unit-test-logger.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - From 7886af4397a8d17465d0b4291931f38f2814b984 Mon Sep 17 00:00:00 2001 From: Mike Masey Date: Thu, 2 Nov 2023 14:59:58 +0000 Subject: [PATCH 033/146] fix: ensure alt tag isn't remove on build There seems to be an odd bug where on build, the alt tag is removed is it is alt="". Simply adding a space fixes this. --- .../src/views/components/upload/umb-file-dropzone.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index 985eec7e99..89f95f688d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -20,7 +20,7 @@

- +  From e17fa957d6c6a0ee38e5c07bcb43c26b1fe4cae2 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 3 Nov 2023 13:49:41 +0100 Subject: [PATCH 035/146] Don't allow to select folder in create dialog under media type (#14840) * Only allow creating folder at root or under another folder * Fix typos in error message * Add data-element to member type create dialog --- .../common/resources/contenttype.resource.js | 2 +- .../common/resources/mediatype.resource.js | 2 +- .../src/views/mediaTypes/create.controller.js | 5 +++-- .../src/views/mediaTypes/create.html | 20 ++++++++++-------- .../views/memberTypes/create.controller.js | 5 +++-- .../src/views/memberTypes/create.html | 21 ++++++++++--------- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index dfac875e5e..ccc80dee37 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -323,7 +323,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca "contentTypeApiBaseUrl", "DeleteContainer", [{ id: id }])), - 'Failed to delete content type contaier'); + 'Failed to delete content type container'); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index f7bba87ad5..a46ac9184e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -166,7 +166,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter, locali "mediaTypeApiBaseUrl", "DeleteContainer", [{ id: id }])), - 'Failed to delete content type contaier'); + 'Failed to delete content type container'); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js index 435ece4bb9..df26473720 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js @@ -9,8 +9,9 @@ function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) { $scope.model = { - folderName: "", - creatingFolder: false + allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container', + folderName: "", + creatingFolder: false }; var node = $scope.currentNode; diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html index bd6ef087f4..36a0fce9f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html @@ -10,20 +10,21 @@
    -
  • -
  • -
  • -
@@ -35,6 +36,7 @@ Do something else +
diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js index 550ad7ed35..4210d162d3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js @@ -9,8 +9,9 @@ function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) { $scope.model = { - folderName: "", - creatingFolder: false + allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container', + folderName: "", + creatingFolder: false }; var node = $scope.currentNode; diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html index 5bcf6c42bf..ac6f633db8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html @@ -1,4 +1,5 @@
+ From d709994c616e3f4c95282406b79090f876e589ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:39:52 +0000 Subject: [PATCH 042/146] Bump tinymce from 6.7.1 to 6.7.3 in /src/Umbraco.Web.UI.Client Bumps [tinymce](https://github.com/tinymce/tinymce/tree/HEAD/modules/tinymce) from 6.7.1 to 6.7.3. - [Changelog](https://github.com/tinymce/tinymce/blob/develop/modules/tinymce/CHANGELOG.md) - [Commits](https://github.com/tinymce/tinymce/commits/6.7.3/modules/tinymce) --- updated-dependencies: - dependency-name: tinymce dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- src/Umbraco.Web.UI.Client/package-lock.json | 8 ++++---- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 171573e389..0bc999dda2 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -39,7 +39,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.7.1", + "tinymce": "6.7.3", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" @@ -16518,9 +16518,9 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "node_modules/tinymce": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.7.1.tgz", - "integrity": "sha512-SIGJgWk2d/X59VbO+i81QfNx2EP1P5t+sza2/1So3OLGtmMBhEJMag7sN/Mo8sq4s0niwb65Z51yLju32jP11g==" + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.7.3.tgz", + "integrity": "sha512-J7WmYIi/gt1RvZ6Ap2oQiUjzAoiS9pfV+d4GnKuZuPu8agmlAEAInNmMvMjfCNBzHv4JnZXY7qlHUAI0IuYQVA==" }, "node_modules/to-absolute-glob": { "version": "2.0.2", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index bf2a4c9b0c..a873516ada 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -51,7 +51,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.7.1", + "tinymce": "6.7.3", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" From 7bad6b9fd8a610eb786f3aa32698ba18dac3b70c Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Fri, 17 Nov 2023 16:53:08 +0100 Subject: [PATCH 043/146] Fix button styling in block list & block grid overlay (#14929) * Update blocklist.blockconfiguration.overlay.less Change noValue color * Update blockgrid.blockconfiguration.overlay.less Change noValue color for block-grid --- .../prevalue/blockgrid.blockconfiguration.overlay.less | 2 +- .../prevalue/blocklist.blockconfiguration.overlay.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.less index 5861cfdda5..e2e422d267 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.less @@ -69,7 +69,7 @@ border-radius: @baseBorderRadius; transition: color 120ms; &.--hideText { - color: white; + color: @ui-action-discreet-type; } &:hover, &:focus { color: @ui-action-discreet-type-hover; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less index d2d875aa94..40454189db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less @@ -67,7 +67,7 @@ &.--noValue { text-align: center; border-radius: @baseBorderRadius; - color: white; + color: @ui-action-discreet-type; transition: color 120ms; &:hover, &:focus { color: @ui-action-discreet-type-hover; From 6aca62b202a47bc8c1894db939de45ec3155e8f9 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 22 Nov 2023 08:22:07 +0100 Subject: [PATCH 044/146] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 1c88857220..617555fa04 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "12.3.3", + "version": "12.3.4", "assemblyVersion": { "precision": "build" }, From d34be8ce34e91a5fbc9fea247380997b41a4851f Mon Sep 17 00:00:00 2001 From: Erik <49444441+Programeerik@users.noreply.github.com> Date: Wed, 22 Nov 2023 10:11:33 +0100 Subject: [PATCH 045/146] Improved punctuation and sentence structure for better readability. (#15279) * Improved punctuation and sentence structure for better readability. * To be coherent with the rest of the written text --- .../EmbeddedResources/Tours/getting-started.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json b/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json index 502b1fe47e..72bfee09d2 100644 --- a/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json +++ b/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json @@ -126,7 +126,7 @@ "steps": [ { "title": "Create your first Document Type", - "content": "

Step 1 of any site is to create a Document Type.
A Document Type is a template for content. For each type of content you want to create you'll create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.

When you have at least one Document Type in place you can start creating content and this content can then be used in a template.

In this tour you will learn how to set up a basic Document Type with a property to enter a short text.

", + "content": "

Step 1 of any site is to create a Document Type.
A Document Type is a template for content. For each type of content you want to create, you will need to create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.

When you have at least one Document Type in place you can start creating content and this content can then be used in a template.

In this tour you will learn how to set up a basic Document Type with a property to enter a short text.

", "type": "intro" }, { From 58e693a75d386f70f1f5d29ab7bd5d329cb92225 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sat, 18 Nov 2023 13:02:29 +0100 Subject: [PATCH 046/146] Fix typo in umbImageGravity --- .../propertyeditors/imagecropper/imagecropper.controller.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index 453347bc1b..80d0cc16c9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -20,8 +20,9 @@ angular.module('umbraco') var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; $scope.acceptFileExt = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); + /** - * Called when the umgImageGravity component updates the focal point value + * Called when the umbImageGravity component updates the focal point value * @param {any} left * @param {any} top */ @@ -81,7 +82,7 @@ angular.module('umbraco') function imageLoaded(isCroppable, hasDimensions) { $scope.isCroppable = isCroppable; $scope.hasDimensions = hasDimensions; - }; + } /** * Called when the file collection changes From 1dacab8b9a3ec9ebde3dacb129d4d178668f4c7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:38:31 +0000 Subject: [PATCH 047/146] Bump axios and wait-on in /tests/Umbraco.Tests.AcceptanceTest Bumps [axios](https://github.com/axios/axios) to 1.6.2 and updates ancestor dependency [wait-on](https://github.com/jeffbski/wait-on). These dependencies need to be updated together. Updates `axios` from 0.25.0 to 1.6.2 - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.25.0...v1.6.2) Updates `wait-on` from 6.0.1 to 7.2.0 - [Release notes](https://github.com/jeffbski/wait-on/releases) - [Commits](https://github.com/jeffbski/wait-on/compare/v6.0.1...v7.2.0) --- updated-dependencies: - dependency-name: axios dependency-type: indirect - dependency-name: wait-on dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- .../package-lock.json | 779 +----------------- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- 2 files changed, 35 insertions(+), 746 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 45b0ebbe91..02c46ac329 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -23,7 +23,7 @@ "prompt": "^1.2.0", "tslib": "^2.4.0", "typescript": "^4.8.3", - "wait-on": "^6.0.1" + "wait-on": "^7.2.0" } }, "node_modules/@colors/colors": { @@ -121,12 +121,6 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "dev": true }, - "node_modules/@types/node": { - "version": "14.17.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.33.tgz", - "integrity": "sha512-noEeJ06zbn3lOh4gqe2v7NMGS33jrulfNqYFDjjEbhpDEHR5VTxgYNQSBqBlJIsBJW3uEYDgD6kvMnrrhGzq8g==", - "dev": true - }, "node_modules/@umbraco/json-models-builders": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.6.tgz", @@ -183,12 +177,14 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/axios": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", - "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "dev": true, "dependencies": { - "follow-redirects": "^1.14.7" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -370,9 +366,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "dev": true, "funding": [ { @@ -569,15 +565,15 @@ "dev": true }, "node_modules/joi": { - "version": "17.6.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.3.tgz", - "integrity": "sha512-YlQsIaS9MHYekzf1Qe11LjTkNzx9qhYluK3172z38RxYoAUf82XMX1p1DG1H4Wtk2ED/vPdSn9OggqtDu+aTow==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "dev": true, "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", + "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, @@ -641,9 +637,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -783,6 +779,12 @@ "node": ">= 6.0.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -873,9 +875,9 @@ } }, "node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -936,22 +938,22 @@ } }, "node_modules/wait-on": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", - "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", "dev": true, "dependencies": { - "axios": "^0.25.0", - "joi": "^17.6.0", + "axios": "^1.6.1", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^7.5.4" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" }, "bin": { "wait-on": "bin/wait-on" }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" } }, "node_modules/webidl-conversions": { @@ -999,718 +1001,5 @@ "node": ">= 6" } } - }, - "dependencies": { - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "dev": true - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dev": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@playwright/test": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", - "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", - "dev": true, - "requires": { - "playwright": "1.38.0" - } - }, - "@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "dev": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true - }, - "@types/node": { - "version": "14.17.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.33.tgz", - "integrity": "sha512-noEeJ06zbn3lOh4gqe2v7NMGS33jrulfNqYFDjjEbhpDEHR5VTxgYNQSBqBlJIsBJW3uEYDgD6kvMnrrhGzq8g==", - "dev": true - }, - "@umbraco/json-models-builders": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.6.tgz", - "integrity": "sha512-bXwfXcpuqG1Ye714L9KJEGXuSzJfckysE/6CuPjdG8FqHWTE1brv28teR2oMw+ih8ca2u2zUboRgdzLEU/1D3Q==", - "requires": { - "camelize": "^1.0.0", - "faker": "^4.1.0" - } - }, - "@umbraco/playwright-testhelpers": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.25.tgz", - "integrity": "sha512-6H452J6LhP0EHjF4jR7V7i0U8WPTiAbSyhN1J459BbbYEJ4QX1A2ZlCdA6VSBAsK1xYdMXD+yxsVJq7AAwiy9A==", - "requires": { - "@umbraco/json-models-builders": "^1.0.6", - "camelize": "^1.0.0", - "faker": "^4.1.0", - "form-data": "^4.0.0", - "node-fetch": "^2.6.7", - "xhr2": "^0.2.1" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "axios": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", - "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", - "dev": true, - "requires": { - "follow-redirects": "^1.14.7" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", - "dev": true - }, - "del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dev": true, - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dotenv": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", - "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==" - }, - "eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", - "dev": true - }, - "faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "joi": { - "version": "17.6.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.3.tgz", - "integrity": "sha512-YlQsIaS9MHYekzf1Qe11LjTkNzx9qhYluK3172z38RxYoAUf82XMX1p1DG1H4Wtk2ED/vPdSn9OggqtDu+aTow==", - "dev": true, - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" - }, - "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "requires": { - "mime-db": "1.51.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "playwright": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", - "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", - "dev": true, - "requires": { - "fsevents": "2.3.2", - "playwright-core": "1.38.0" - } - }, - "playwright-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", - "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", - "dev": true - }, - "prompt": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", - "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "async": "3.2.3", - "read": "1.0.x", - "revalidator": "0.1.x", - "winston": "2.x" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "dev": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "revalidator": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", - "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", - "dev": true - }, - "wait-on": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", - "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", - "dev": true, - "requires": { - "axios": "^0.25.0", - "joi": "^17.6.0", - "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^7.5.4" - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "winston": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", - "integrity": "sha512-J5Zu4p0tojLde8mIOyDSsmLmcP8I3Z6wtwpTDHx1+hGcdhxcJaAmG4CFtagkb+NiN1M9Ek4b42pzMWqfc9jm8w==", - "dev": true, - "requires": { - "async": "^3.2.3", - "colors": "1.0.x", - "cycle": "1.0.x", - "eyes": "0.1.x", - "isstream": "0.1.x", - "stack-trace": "0.0.x" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "xhr2": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", - "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==" - } } } diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 2e86d70d5e..adcdf64f1d 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -16,7 +16,7 @@ "del": "^6.0.0", "ncp": "^2.0.0", "prompt": "^1.2.0", - "wait-on": "^6.0.1" + "wait-on": "^7.2.0" }, "dependencies": { "@umbraco/json-models-builders": "^1.0.6", From e80d117a2a21f6a93787d5d487182a075bda3c2c Mon Sep 17 00:00:00 2001 From: Yuval-Amidror <39709687+Yuval-Amidror@users.noreply.github.com> Date: Thu, 23 Nov 2023 01:43:18 +0200 Subject: [PATCH 048/146] Redirect is broken with Hebrew chars on Linux (#15261) * add HttpUtility.UrlPathEncode encode so paths that are not in english will not throw errors of NON-ASCII characters * remove unnecessary using --- src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index 6b5e1590c1..c6f8a49fcd 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -1,3 +1,4 @@ +using System.Web; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; @@ -89,7 +90,7 @@ public class ContentFinderByRedirectUrl : IContentFinder } frequest - .SetRedirectPermanent(url) + .SetRedirectPermanent(HttpUtility.UrlPathEncode(url)) // From: http://stackoverflow.com/a/22468386/5018 // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 From ade61f7ec574ae1790f6c217c0da0816ff77b2c3 Mon Sep 17 00:00:00 2001 From: Steve <6131869+SteveVaneeckhout@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:55:19 +0100 Subject: [PATCH 049/146] Add missing translations in EN (#15257) And remove the ones that don't exist anymore in the leading dictionary EN-US. --- .../EmbeddedResources/Lang/en.xml | 135 ++++++++++++++++-- 1 file changed, 123 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 2a6b4e1728..152cf9944e 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -56,6 +56,8 @@ Create Content Template Resend Invitation Hide unavailable options + Change Data Type + Edit content Content @@ -158,6 +160,7 @@ Confirm More publishing options Submit + Generate models and close Media deleted @@ -198,6 +201,8 @@ Save Save History (all variants) + Content unpublished for languages: %0% + Unpublish The folder name cannot contain illegal characters. @@ -325,6 +330,12 @@ Create new Paste from clipboard This item is in the Recycle Bin + No content can be added for this item + Save is not allowed + Publish is not allowed + Send for approval is not allowed + Schedule is not allowed + Unpublish is not allowed %0%]]> @@ -349,12 +360,19 @@ Failed to rename the folder with id %0% Drag and drop your file(s) into the area One or more file security validations have failed + Parent and destination folders cannot be the same + Upload is not allowed in this location. Create a new member All Members Member groups have no additional properties for editing. Two-Factor Authentication + A member with this login already exists + The member is already in group '%0%' + The member already has a password set + Lockout is not enabled for this member + The member is not in group '%0%' Failed to copy content type @@ -476,7 +494,6 @@ Link Anchor / querystring Name - Manage hostnames Close this window Are you sure you want to delete %0% of %1% items]]> @@ -579,6 +596,7 @@ Yes, remove You are deleting the layout Modifying layout will result in loss of data for any existing content that is based on this configuration. + Select configuration @@ -687,6 +705,9 @@ Select the folder to move to in the tree structure below was moved underneath + Changing a property editor on a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json. + %0% will delete the properties and their data from the following items]]> + I understand this action will delete the properties and data based on this Data Type Your data has been saved, but before you can publish this page there are some @@ -727,6 +748,8 @@ Please place cursor at the left of the two cells you wish to merge You cannot split a cell that hasn't been merged. This property is invalid + An unknown failure has occurred + Optimistic concurrency failure, object has been modified About @@ -844,7 +867,6 @@ Search Sorry, we can not find what you are looking for. No items have been added - Select all Server Settings Shared @@ -892,6 +914,12 @@ Last Updated Skip to menu Skip to content + Change + Crop section + Generic + Media + Revert + Validate Blue @@ -918,6 +946,7 @@ General Editor Toggle allow culture variants + Add tab Background colour @@ -1366,6 +1395,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont so uninstall with caution. If in doubt, contact the package author.]]> Package version Verified to work on Umbraco Cloud + Package migrations have successfully completed. + All package migrations have successfully completed. Paste with full formatting (Not recommended) @@ -1423,6 +1454,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Publish to publish %0% and thereby making its content publicly available.

You can publish this page and all its subpages by checking Include unpublished subpages below. ]]>
+ Insufficient user permissions to publish all descendant documents + %0% could not be published because the item is in the recycle bin. + Validation failed for required language '%0%'. This language was saved but not published. You have not configured any approved colours @@ -1489,20 +1523,13 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Edit script file - Concierge Content - Courier - Developer Forms - Help - Umbraco Configuration Wizard Media Members - Newsletters Packages Marketplace Settings - Statistics Translation Users @@ -1543,6 +1570,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont column headers to sort the entire collection of items
+ This node has no child nodes to sort Validation @@ -1554,7 +1582,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Operation was cancelled by a 3rd party add-in This file is being uploaded as part of a folder, but creating a new folder is not allowed here Creating a new folder is not allowed here - Publishing was cancelled by a 3rd party add-in Property type already exists Property type created DataType: %1%]]> @@ -1568,7 +1595,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Stylesheet saved without any errors Datatype saved Dictionary item saved - Publishing failed because the parent page isn't published Content published and visible on the website Content Template saved @@ -1635,6 +1661,24 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Your system information has successfully been copied to the clipboard Could not copy your system information to the clipboard + Saved. To view the changes please reload your browser + %0% documents published and visible on the website + %0% published and visible on the website + %0% documents published for languages %1% and visible on the website + A schedule for publishing has been updated + %0% saved + %0% changes have been sent for approval + Content variation %0% unpublished + The mandatory language '%0%' was unpublished. All languages for this content item are now unpublished. + Cannot publish the document since the required '%0%' is not published + Validation failed for language '%0%' + The release date cannot be in the past + Cannot schedule the document for publishing since the required '%0%' is not published + Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language + The expire date cannot be in the past + The expire date cannot be before the release date + An error occurred while enabling version cleanup for %0% + An error occurred while disabling version cleanup for %0% Add style @@ -1898,6 +1942,18 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Prevent cleanup Enable cleanup NOTE! The cleanup of historically content versions are disabled globally. These settings will not take effect before it is enabled.]]> + You can't move the group %0% to this tab because the group will get the same alias as a tab: "%1%". Rename the group to continue. + Available configurations + Create a new configuration + %0%?]]> + %0%?]]> + %0%?]]> + This will also delete all items below this tab. + This will also delete all items below this group. + Add tab + Convert to tab + Drag properties here to place directly on the tab + Changing a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json. Add language @@ -1916,6 +1972,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Fall back language none + %0% is shared across languages and segments.]]> + %0% is shared across all languages.]]> + %0% is shared across all segments.]]> + Shared: Languages + Shared: Segments Add parameter @@ -2268,10 +2329,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont This two-factor provider is now disabled Something went wrong with trying to disable this two-factor provider Do you want to disable this two-factor provider for this user? + A user with this login already exists + The password must have at least one digit ('0'-'9') + The password must have at least one lowercase ('a'-'z') + The password must have at least one non alphanumeric character + The password must use at least %0% different characters + The password must have at least one uppercase ('A'-'Z') + The password must be at least %0% characters long + Allow access to all languages + The user already has a password set + The user is already in group '%0%' + Lockout is not enabled for this user + The user is not in group '%0%' + Configure Two-Factor Validation - No validation Validate as an email address Validate as a number Validate as a URL @@ -2298,6 +2371,14 @@ To manage your website, simply open the Umbraco backoffice and start adding cont %1% more.]]> %1% too many.]]> The content amount requirements are not met for one or more areas. + Invalid member group name + Invalid user group name + Invalid token + Invalid username + Email '%0%' is already taken + User group name '%0%' is already taken + Member group name '%0%' is already taken + Username '%0%' is already taken - - + -
+
Invalid code entered @@ -37,14 +37,14 @@ button-style="success" size="m" label-key="general_validate" - state="vm.stateValidateButton" - disabled="vm.code.length === 0"> + state="cvm.stateValidateButton" + disabled="cvm.code.length === 0"> + action="cvm.goBack()">
From 320310fbc029c8d12fef76fba3fd37f704db3ce7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 26 Oct 2023 09:01:25 +0200 Subject: [PATCH 061/146] Added "pr: none" to nightly trigger (#15044) --- build/nightly-build-trigger.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/build/nightly-build-trigger.yml b/build/nightly-build-trigger.yml index f8147da05c..1b70e6c9e5 100644 --- a/build/nightly-build-trigger.yml +++ b/build/nightly-build-trigger.yml @@ -1,5 +1,6 @@ name: Nightly_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r) +pr: none trigger: none schedules: From d088ed8bf68c59c526d0b8c7eaa0da5954d01ff5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 30 Nov 2023 14:41:45 +0000 Subject: [PATCH 062/146] V13: Webhook all the things (#15161) --- src/Umbraco.Core/Constants-WebhookEvents.cs | 51 ++++ .../EmbeddedResources/Lang/en.xml | 1 + .../EmbeddedResources/Lang/en_us.xml | 1 + .../Content/ContentCopiedWebhookEvent.cs | 37 +++ .../ContentDeletedBlueprintWebhookEvent.cs | 32 +++ .../ContentDeletedVersionsWebhookEvent.cs | 37 +++ .../ContentDeletedWebhookEvent.cs} | 8 +- .../ContentEmptiedRecycleBinWebhookEvent.cs | 41 ++++ .../ContentMovedToRecycleBinWebhookEvent.cs | 29 +++ .../Content/ContentMovedWebhookEvent.cs | 29 +++ .../ContentPublishedWebhookEvent.cs} | 8 +- .../Events/Content/ContentRolledBack.cs | 52 +++++ .../ContentSavedBlueprintWebhookEvent.cs | 33 +++ .../Content/ContentSavedWebhookEvent.cs | 52 +++++ .../Content/ContentSortedWebhookEvent.cs | 53 +++++ .../ContentUnpublishedWebhookEvent.cs} | 8 +- .../DataType/DataTypeDeletedWebhookEvent.cs | 25 ++ .../DataType/DataTypeMovedWebhookEvent.cs | 25 ++ .../DataType/DataTypeSavedWebhookEvent.cs | 25 ++ .../DictionaryItemDeletedWebhookEvent.cs | 25 ++ .../DictionaryItemSavedWebhookEvent.cs | 25 ++ .../Domain/DomainDeletedWebhookEvent.cs | 25 ++ .../Events/Domain/DomainSavedWebhookEvent.cs | 25 ++ .../Language/LanguageDeletedWebhookEvent.cs | 25 ++ .../Language/LanguageSavedWebhookEvent.cs | 26 +++ .../MediaDeletedWebhookEvent.cs} | 8 +- .../MediaEmptiedRecycleBinWebhookEvent.cs | 31 +++ .../MediaMovedToRecycleBinWebhookEvent.cs | 31 +++ .../Events/Media/MediaMovedWebhookEvent.cs | 31 +++ .../MediaSavedWebhookEvent.cs} | 16 +- .../MediaType/MediaTypeChangedWebhookEvent.cs | 25 ++ .../MediaType/MediaTypeDeletedWebhookEvent.cs | 25 ++ .../MediaType/MediaTypeMovedWebhookEvent.cs | 25 ++ .../MediaType/MediaTypeSavedWebhookEvent.cs | 25 ++ .../Member/AssignedMemberRolesWebhookEvent.cs | 22 ++ .../Member/ExportedMemberWebhookEvent.cs | 32 +++ .../Member/MemberDeletedWebhookEvent.cs | 39 ++++ .../Member/MemberGroupDeletedWebhookEvent.cs | 25 ++ .../Member/MemberGroupSavedWebhookEvent.cs | 25 ++ .../Events/Member/MemberSavedWebhookEvent.cs | 39 ++++ .../Member/RemovedMemberRolesWebhookEvent.cs | 22 ++ .../MemberTypeChangedWebhookEvent.cs | 25 ++ .../MemberTypeDeletedWebhookEvent.cs | 25 ++ .../MemberType/MemberTypeMovedWebhookEvent.cs | 25 ++ .../MemberType/MemberTypeSavedWebhookEvent.cs | 25 ++ .../Package/ImportedPackageWebhookEvent.cs | 22 ++ .../PublicAccessEntryDeletedWebhookEvent.cs | 24 ++ .../PublicAccessEntrySavedWebhookEvent.cs | 25 ++ .../Relation/RelationDeletedWebhookEvent.cs | 25 ++ .../Relation/RelationSavedWebhookEvent.cs | 25 ++ .../RelationTypeDeletedWebhookEvent.cs | 26 +++ .../RelationTypeSavedWebhookEvent.cs | 25 ++ .../Script/ScriptDeletedWebhookEvent.cs | 25 ++ .../Events/Script/ScriptSavedWebhookEvent.cs | 25 ++ .../StylesheetDeletedWebhookEvent.cs | 25 ++ .../Stylesheet/StylesheetSavedWebhookEvent.cs | 25 ++ .../PartialViewDeletedWebhookEvent.cs | 25 ++ .../Template/PartialViewSavedWebhookEvent.cs | 25 ++ .../Template/TemplateDeletedWebhookEvent.cs | 25 ++ .../Template/TemplateSavedWebhookEvent.cs | 33 +++ ...ssignedUserGroupPermissionsWebhookEvent.cs | 25 ++ .../Events/User/UserDeletedWebhookEvent.cs | 39 ++++ ...UserForgotPasswordRequestedWebhookEvent.cs | 23 ++ ...rForgottenPasswordRequestedWebhookEvent.cs | 22 ++ .../User/UserGroupDeletedWebhookEvent.cs | 24 ++ .../Events/User/UserGroupSavedWebhookEvent.cs | 24 ++ .../Events/User/UserLockedWebhookEvent.cs | 22 ++ .../User/UserLoginFailedWebhookEvent.cs | 22 ++ ...erLoginRequiresVerificationWebhookEvent.cs | 23 ++ .../User/UserLoginSuccessWebhookEvent.cs | 22 ++ .../User/UserLogoutSuccessWebhookEvent.cs | 22 ++ .../User/UserPasswordChangedWebhookEvent.cs | 23 ++ .../User/UserPasswordResetWebhookEvent.cs | 22 ++ .../Events/User/UserSavedWebhookEvent.cs | 39 ++++ .../UserTwoFactorRequestedWebhookEvent.cs | 23 ++ .../Events/User/UserUnlockedWebhookEvent.cs | 22 ++ src/Umbraco.Core/Webhooks/WebhookEventBase.cs | 12 +- .../Webhooks/WebhookEventCollectionBuilder.cs | 220 ++++++++++++++++-- .../eventpicker/eventpicker.html | 64 +++-- 79 files changed, 2234 insertions(+), 58 deletions(-) create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs rename src/Umbraco.Core/Webhooks/Events/{ContentDeleteWebhookEvent.cs => Content/ContentDeletedWebhookEvent.cs} (77%) create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs rename src/Umbraco.Core/Webhooks/Events/{ContentPublishWebhookEvent.cs => Content/ContentPublishedWebhookEvent.cs} (86%) create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs rename src/Umbraco.Core/Webhooks/Events/{ContentUnpublishWebhookEvent.cs => Content/ContentUnpublishedWebhookEvent.cs} (76%) create mode 100644 src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Language/LanguageDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs rename src/Umbraco.Core/Webhooks/Events/{MediaDeleteWebhookEvent.cs => Media/MediaDeletedWebhookEvent.cs} (78%) create mode 100644 src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs rename src/Umbraco.Core/Webhooks/Events/{MediaSaveWebhookEvent.cs => Media/MediaSavedWebhookEvent.cs} (68%) create mode 100644 src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeChangedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs diff --git a/src/Umbraco.Core/Constants-WebhookEvents.cs b/src/Umbraco.Core/Constants-WebhookEvents.cs index afd57b5188..41b9849f08 100644 --- a/src/Umbraco.Core/Constants-WebhookEvents.cs +++ b/src/Umbraco.Core/Constants-WebhookEvents.cs @@ -6,6 +6,57 @@ public static partial class Constants { public static class Aliases { + + /// + /// Webhook event alias for content versions deleted + /// + public const string ContentDeletedVersions = "Umbraco.ContentDeletedVersions"; + + /// + /// Webhook event alias for content blueprint saved + /// + public const string ContentSavedBlueprint = "Umbraco.ContentSavedBlueprint"; + + /// + /// Webhook event alias for content blueprint deleted + /// + public const string ContentDeletedBlueprint = "Umbraco.ContentDeletedBlueprint"; + + /// + /// Webhook event alias for content moved into the recycle bin. + /// + public const string ContentMovedToRecycleBin = "Umbraco.ContentMovedToRecycleBin"; + + /// + /// Webhook event alias for content sorted. + /// + public const string ContentSorted = "Umbraco.ContentSorted"; + + /// + /// Webhook event alias for content moved. + /// + public const string ContentMoved = "Umbraco.ContentMoved"; + + /// + /// Webhook event alias for content copied. + /// + public const string ContentCopied = "Umbraco.ContentCopied"; + + /// + /// Webhook event alias for content emptied recycle bin. + /// + public const string ContentEmptiedRecycleBin = "Umbraco.ContentEmptiedRecycleBin"; + + /// + /// Webhook event alias for content rolled back. + /// + public const string ContentRolledBack = "Umbraco.ContentRolledBack"; + + /// + /// Webhook event alias for content saved. + /// + public const string ContentSaved = "Umbraco.ContentSaved"; + /// /// Webhook event alias for content publish. /// diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 572fe05dd4..492cb762cf 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1941,6 +1941,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Create header Logs No webhook headers have been added + No events were found. Add language diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 33901735ee..12fdf928c2 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2023,6 +2023,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Create header Logs No webhook headers have been added + No events were found. Add language diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs new file mode 100644 index 0000000000..3d6f0163d7 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Copied", Constants.WebhookEvents.Types.Content)] +public class ContentCopiedWebhookEvent : WebhookEventBase +{ + public ContentCopiedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentCopied; + + public override object? ConvertNotificationToRequestPayload(ContentCopiedNotification notification) + { + return new + { + notification.Copy, + notification.Original, + notification.ParentId, + notification.RelateToOriginal + }; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs new file mode 100644 index 0000000000..996b8abd15 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Template [Blueprint] Deleted", Constants.WebhookEvents.Types.Content)] +public class ContentDeletedBlueprintWebhookEvent : WebhookEventContentBase +{ + public ContentDeletedBlueprintWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentDeletedBlueprint; + + protected override IEnumerable GetEntitiesFromNotification(ContentDeletedBlueprintNotification notification) => + notification.DeletedBlueprints; + + protected override object ConvertEntityToRequestPayload(IContent entity) => entity; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs new file mode 100644 index 0000000000..c543bfe3ca --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Versions Deleted", Constants.WebhookEvents.Types.Content)] +public class ContentDeletedVersionsWebhookEvent : WebhookEventBase +{ + public ContentDeletedVersionsWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentDeletedVersions; + + public override object? ConvertNotificationToRequestPayload(ContentDeletedVersionsNotification notification) + { + return new + { + notification.Id, + notification.DeletePriorVersions, + notification.SpecificVersion, + notification.DateToRetain + }; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentDeleteWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs similarity index 77% rename from src/Umbraco.Core/Webhooks/Events/ContentDeleteWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs index 85a1f39ba9..1282822995 100644 --- a/src/Umbraco.Core/Webhooks/Events/ContentDeleteWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs @@ -5,12 +5,12 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Content; -[WebhookEvent("Content was deleted", Constants.WebhookEvents.Types.Content)] -public class ContentDeleteWebhookEvent : WebhookEventContentBase +[WebhookEvent("Content Deleted", Constants.WebhookEvents.Types.Content)] +public class ContentDeletedWebhookEvent : WebhookEventContentBase { - public ContentDeleteWebhookEvent( + public ContentDeletedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs new file mode 100644 index 0000000000..8670d23c49 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Recycle Bin Emptied", Constants.WebhookEvents.Types.Content)] +public class ContentEmptiedRecycleBinWebhookEvent : WebhookEventContentBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentEmptiedRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentEmptiedRecycleBin; + + protected override IEnumerable GetEntitiesFromNotification(ContentEmptiedRecycleBinNotification notification) => + notification.DeletedEntities; + + protected override object? ConvertEntityToRequestPayload(IContent entity) => entity; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs new file mode 100644 index 0000000000..6ed74d8c66 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Moved to Recycle Bin", Constants.WebhookEvents.Types.Content)] +public class ContentMovedToRecycleBinWebhookEvent : WebhookEventBase +{ + public ContentMovedToRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentMovedToRecycleBin; + + public override object? ConvertNotificationToRequestPayload(ContentMovedToRecycleBinNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs new file mode 100644 index 0000000000..46d487c499 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Moved", Constants.WebhookEvents.Types.Content)] +public class ContentMovedWebhookEvent : WebhookEventBase +{ + public ContentMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentMoved; + + public override object? ConvertNotificationToRequestPayload(ContentMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentPublishWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs similarity index 86% rename from src/Umbraco.Core/Webhooks/Events/ContentPublishWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs index e1ba7125ec..5847fb450e 100644 --- a/src/Umbraco.Core/Webhooks/Events/ContentPublishWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs @@ -8,15 +8,15 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Content; -[WebhookEvent("Content was published", Constants.WebhookEvents.Types.Content)] -public class ContentPublishWebhookEvent : WebhookEventContentBase +[WebhookEvent("Content Published", Constants.WebhookEvents.Types.Content)] +public class ContentPublishedWebhookEvent : WebhookEventContentBase { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IApiContentBuilder _apiContentBuilder; - public ContentPublishWebhookEvent( + public ContentPublishedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs new file mode 100644 index 0000000000..f38ff571f9 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Rolled Back", Constants.WebhookEvents.Types.Content)] +public class ContentRolledBackWebhookEvent : WebhookEventContentBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentRolledBackWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentRolledBack; + + protected override IEnumerable GetEntitiesFromNotification(ContentRolledBackNotification notification) => + new List { notification.Entity }; + + protected override object? ConvertEntityToRequestPayload(IContent entity) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + + // Get preview/saved version of content for a rollback + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(true, entity.Key); + return publishedContent is null ? null : _apiContentBuilder.Build(publishedContent); + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs new file mode 100644 index 0000000000..40630b453f --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Template [Blueprint] Saved", Constants.WebhookEvents.Types.Content)] +public class ContentSavedBlueprintWebhookEvent : WebhookEventContentBase +{ + public ContentSavedBlueprintWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentSavedBlueprint; + + protected override IEnumerable + GetEntitiesFromNotification(ContentSavedBlueprintNotification notification) + => new List { notification.SavedBlueprint }; + + protected override object ConvertEntityToRequestPayload(IContent entity) => entity; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs new file mode 100644 index 0000000000..fac16de822 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Saved", Constants.WebhookEvents.Types.Content)] +public class ContentSavedWebhookEvent : WebhookEventContentBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentSaved; + + protected override IEnumerable GetEntitiesFromNotification(ContentSavedNotification notification) => + notification.SavedEntities; + + protected override object? ConvertEntityToRequestPayload(IContent entity) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + + // Get preview/saved version of content + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(true, entity.Key); + return publishedContent is null ? null : _apiContentBuilder.Build(publishedContent); + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs new file mode 100644 index 0000000000..d2a95028e2 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Sorted", Constants.WebhookEvents.Types.Content)] +public class ContentSortedWebhookEvent : WebhookEventBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentSortedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentSorted; + + public override object? ConvertNotificationToRequestPayload(ContentSortedNotification notification) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + var sortedEntities = new List(); + foreach (var entity in notification.SortedEntities) + { + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(entity.Key); + object? payload = publishedContent is null ? null : _apiContentBuilder.Build(publishedContent); + sortedEntities.Add(payload); + } + return sortedEntities; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentUnpublishWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs similarity index 76% rename from src/Umbraco.Core/Webhooks/Events/ContentUnpublishWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs index 76499eb277..81245f9b75 100644 --- a/src/Umbraco.Core/Webhooks/Events/ContentUnpublishWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs @@ -5,12 +5,12 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Content; -[WebhookEvent("Content was unpublished", Constants.WebhookEvents.Types.Content)] -public class ContentUnpublishWebhookEvent : WebhookEventContentBase +[WebhookEvent("Content Unpublished", Constants.WebhookEvents.Types.Content)] +public class ContentUnpublishedWebhookEvent : WebhookEventContentBase { - public ContentUnpublishWebhookEvent( + public ContentUnpublishedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs new file mode 100644 index 0000000000..b121c6f394 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.DataType; + +[WebhookEvent("Data Type Deleted")] +public class DataTypeDeletedWebhookEvent : WebhookEventBase +{ + public DataTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dataTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(DataTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs new file mode 100644 index 0000000000..03c0954305 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.DataType; + +[WebhookEvent("Data Type Moved")] +public class DataTypeMovedWebhookEvent : WebhookEventBase +{ + public DataTypeMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dataTypeMoved"; + + public override object? ConvertNotificationToRequestPayload(DataTypeMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs new file mode 100644 index 0000000000..a6e5afe189 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.DataType; + +[WebhookEvent("Data Type Saved")] +public class DataTypeSavedWebhookEvent : WebhookEventBase +{ + public DataTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dataTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(DataTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs new file mode 100644 index 0000000000..fa3c95cea5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Dictionary; + +[WebhookEvent("Dictionary Item Deleted")] +public class DictionaryItemDeletedWebhookEvent : WebhookEventBase +{ + public DictionaryItemDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dictionaryItemDeleted"; + + public override object? ConvertNotificationToRequestPayload(DictionaryItemDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs new file mode 100644 index 0000000000..7edd4e7849 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Dictionary; + +[WebhookEvent("Dictionary Item Saved")] +public class DictionaryItemSavedWebhookEvent : WebhookEventBase +{ + public DictionaryItemSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dictionaryItemSaved"; + + public override object? ConvertNotificationToRequestPayload(DictionaryItemSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs new file mode 100644 index 0000000000..30605a28de --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Domain; + +[WebhookEvent("Domain Deleted")] +public class DomainDeletedWebhookEvent : WebhookEventBase +{ + public DomainDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "domainDeleted"; + + public override object? ConvertNotificationToRequestPayload(DomainDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs new file mode 100644 index 0000000000..5cd80f743b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Domain; + +[WebhookEvent("Domain Saved")] +public class DomainSavedWebhookEvent : WebhookEventBase +{ + public DomainSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "domainSaved"; + + public override object? ConvertNotificationToRequestPayload(DomainSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Language/LanguageDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Language/LanguageDeletedWebhookEvent.cs new file mode 100644 index 0000000000..98ebd4d5c8 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Language/LanguageDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Language; + +[WebhookEvent("Language Deleted")] +public class LanguageDeletedWebhookEvent : WebhookEventBase +{ + public LanguageDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "languageDeleted"; + + public override object? ConvertNotificationToRequestPayload(LanguageDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs new file mode 100644 index 0000000000..ccc52f4245 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Language; + +[WebhookEvent("Language Saved")] +public class LanguageSavedWebhookEvent : WebhookEventBase +{ + public LanguageSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "languageSaved"; + + public override object? ConvertNotificationToRequestPayload(LanguageSavedNotification notification) + => notification.SavedEntities; +} + diff --git a/src/Umbraco.Core/Webhooks/Events/MediaDeleteWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs similarity index 78% rename from src/Umbraco.Core/Webhooks/Events/MediaDeleteWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs index ba9fb2333a..659917652d 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaDeleteWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs @@ -5,12 +5,12 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Media; -[WebhookEvent("Media was deleted", Constants.WebhookEvents.Types.Media)] -public class MediaDeleteWebhookEvent : WebhookEventContentBase +[WebhookEvent("Media Deleted", Constants.WebhookEvents.Types.Media)] +public class MediaDeletedWebhookEvent : WebhookEventContentBase { - public MediaDeleteWebhookEvent( + public MediaDeletedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs new file mode 100644 index 0000000000..29d0e44e46 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Media; + +[WebhookEvent("Media Recycle Bin Emptied", Constants.WebhookEvents.Types.Media)] +public class MediaEmptiedRecycleBinWebhookEvent : WebhookEventContentBase +{ + public MediaEmptiedRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webHookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => "mediaEmptiedRecycleBin"; + + protected override IEnumerable GetEntitiesFromNotification(MediaEmptiedRecycleBinNotification notification) => notification.DeletedEntities; + + protected override object ConvertEntityToRequestPayload(IMedia entity) => new DefaultPayloadModel { Id = entity.Key }; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs new file mode 100644 index 0000000000..1bd05c6d7b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Media; + +[WebhookEvent("Media Moved to Recycle Bin", Constants.WebhookEvents.Types.Media)] +public class MediaMovedToRecycleBinWebhookEvent : WebhookEventContentBase +{ + public MediaMovedToRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webHookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => "mediaMovedToRecycleBin"; + + protected override IEnumerable GetEntitiesFromNotification(MediaMovedToRecycleBinNotification notification) => notification.MoveInfoCollection.Select(x => x.Entity); + + protected override object ConvertEntityToRequestPayload(IMedia entity) => new DefaultPayloadModel { Id = entity.Key }; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs new file mode 100644 index 0000000000..60b235f4cc --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Media; + +[WebhookEvent("Media Moved", Constants.WebhookEvents.Types.Media)] +public class MediaMovedWebhookEvent : WebhookEventContentBase +{ + public MediaMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webHookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => "mediaMoved"; + + protected override IEnumerable GetEntitiesFromNotification(MediaMovedNotification notification) => notification.MoveInfoCollection.Select(x => x.Entity); + + protected override object ConvertEntityToRequestPayload(IMedia entity) => new DefaultPayloadModel { Id = entity.Key }; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaSaveWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs similarity index 68% rename from src/Umbraco.Core/Webhooks/Events/MediaSaveWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs index cea7181d51..6c68e8edb1 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaSaveWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs @@ -8,15 +8,15 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Media; -[WebhookEvent("Media was saved", Constants.WebhookEvents.Types.Media)] -public class MediaSaveWebhookEvent : WebhookEventContentBase +[WebhookEvent("Media Saved", Constants.WebhookEvents.Types.Media)] +public class MediaSavedWebhookEvent : WebhookEventContentBase { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IApiMediaBuilder _apiMediaBuilder; - public MediaSaveWebhookEvent( + public MediaSavedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, @@ -29,8 +29,8 @@ public class MediaSaveWebhookEvent : WebhookEventContentBase Constants.WebhookEvents.Aliases.MediaSave; @@ -39,12 +39,12 @@ public class MediaSaveWebhookEvent : WebhookEventContentBase +{ + public MediaTypeChangedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeChanged"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeChangedNotification notification) + => notification.Changes; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs new file mode 100644 index 0000000000..7b46967d06 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; + +[WebhookEvent("Media Type Deleted")] +public class MediaTypeDeletedWebhookEvent : WebhookEventBase +{ + public MediaTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs new file mode 100644 index 0000000000..ae2a35995e --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; + +[WebhookEvent("Media Type Moved")] +public class MediaTypeMovedWebhookEvent : WebhookEventBase +{ + public MediaTypeMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeMoved"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs new file mode 100644 index 0000000000..818b38f71b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; + +[WebhookEvent("Media Type Saved")] +public class MediaTypeSavedWebhookEvent : WebhookEventBase +{ + public MediaTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs new file mode 100644 index 0000000000..4812bdcc73 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Roles Assigned")] +public class AssignedMemberRolesWebhookEvent : WebhookEventBase +{ + public AssignedMemberRolesWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "assignedMemberRoles"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs new file mode 100644 index 0000000000..3f72587c8f --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Exported")] +public class ExportedMemberWebhookEvent : WebhookEventBase +{ + public ExportedMemberWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "exportedMember"; + + public override object? ConvertNotificationToRequestPayload(ExportedMemberNotification notification) + { + // No need to return the original member in the notification as well + return new + { + exportedMember = notification.Exported + }; + + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs new file mode 100644 index 0000000000..c29e22003f --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Deleted")] +public class MemberDeletedWebhookEvent : WebhookEventBase +{ + public MemberDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberDeleted"; + + public override object? ConvertNotificationToRequestPayload(MemberDeletedNotification notification) + { + // TODO: Map more stuff here + var result = notification.DeletedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.ContentTypeAlias, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs new file mode 100644 index 0000000000..ab41dfed07 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Group Deleted")] +public class MemberGroupDeletedWebhookEvent : WebhookEventBase +{ + public MemberGroupDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberGroupDeleted"; + + public override object? ConvertNotificationToRequestPayload(MemberGroupDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs new file mode 100644 index 0000000000..19d3e7d4b2 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Group Saved")] +public class MemberGroupSavedWebhookEvent : WebhookEventBase +{ + public MemberGroupSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberGroupSaved"; + + public override object? ConvertNotificationToRequestPayload(MemberGroupSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs new file mode 100644 index 0000000000..edb82cb518 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Saved")] +public class MemberSavedWebhookEvent : WebhookEventBase +{ + public MemberSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberSaved"; + + public override object? ConvertNotificationToRequestPayload(MemberSavedNotification notification) + { + // TODO: Map more stuff here + var result = notification.SavedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.ContentTypeAlias, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs new file mode 100644 index 0000000000..8925b24059 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Roles Removed")] +public class RemovedMemberRolesWebhookEvent : WebhookEventBase +{ + public RemovedMemberRolesWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "removedMemberRoles"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs new file mode 100644 index 0000000000..68c4424e48 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Changed")] +public class MemberTypeChangedWebhookEvent : WebhookEventBase +{ + public MemberTypeChangedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeChanged"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeChangedNotification notification) + => notification.Changes; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs new file mode 100644 index 0000000000..005143826c --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Deleted")] +public class MemberTypeDeletedWebhookEvent : WebhookEventBase +{ + public MemberTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs new file mode 100644 index 0000000000..ddef29a0c2 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Moved")] +public class MemberTypeMovedWebhookEvent : WebhookEventBase +{ + public MemberTypeMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeMoved"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs new file mode 100644 index 0000000000..db4bcf8ad4 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Saved")] +public class MemberTypeSavedWebhookEvent : WebhookEventBase +{ + public MemberTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeSavedNotification notification) => + notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs new file mode 100644 index 0000000000..8495dde4c2 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Package; + +[WebhookEvent("Package Imported")] +public class ImportedPackageWebhookEvent : WebhookEventBase +{ + public ImportedPackageWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "packageImported"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs new file mode 100644 index 0000000000..00f5a2ca86 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.PublicAccess; + +[WebhookEvent("Public Access Entry Deleted")] +public class PublicAccessEntryDeletedWebhookEvent : WebhookEventBase +{ + public PublicAccessEntryDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "publicAccessEntryDeleted"; + + public override object? ConvertNotificationToRequestPayload(PublicAccessEntryDeletedNotification notification) => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs new file mode 100644 index 0000000000..3977db2832 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.PublicAccess; + +[WebhookEvent("Public Access Entry Saved")] +public class PublicAccessEntrySavedWebhookEvent : WebhookEventBase +{ + public PublicAccessEntrySavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "publicAccessEntrySaved"; + + public override object? ConvertNotificationToRequestPayload(PublicAccessEntrySavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs new file mode 100644 index 0000000000..2ade4bd08e --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Relation; + +[WebhookEvent("Relation Deleted")] +public class RelationDeletedWebhookEvent : WebhookEventBase +{ + public RelationDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "relationDeleted"; + + public override object? ConvertNotificationToRequestPayload(RelationDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs new file mode 100644 index 0000000000..4cc775aa21 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Relation; + +[WebhookEvent("Relation Saved")] +public class RelationSavedWebhookEvent : WebhookEventBase +{ + public RelationSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "relationSaved"; + + public override object? ConvertNotificationToRequestPayload(RelationSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs new file mode 100644 index 0000000000..41f9619a41 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.RelationType; + +[WebhookEvent("Relation Type Deleted")] +public class RelationTypeDeletedWebhookEvent : WebhookEventBase +{ + public RelationTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + + public override string Alias => "relationTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(RelationTypeDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs new file mode 100644 index 0000000000..915e95c6cb --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.RelationType; + +[WebhookEvent("Relation Type Saved")] +public class RelationTypeSavedWebhookEvent : WebhookEventBase +{ + public RelationTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "relationTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(RelationTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs new file mode 100644 index 0000000000..1dad592cb1 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Script; + +[WebhookEvent("Script Deleted")] +public class ScriptDeletedWebhookEvent : WebhookEventBase +{ + public ScriptDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "scriptDeleted"; + + public override object? ConvertNotificationToRequestPayload(ScriptDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs new file mode 100644 index 0000000000..4d026b9f6c --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Script; + +[WebhookEvent("Script Saved")] +public class ScriptSavedWebhookEvent : WebhookEventBase +{ + public ScriptSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "scriptSaved"; + + public override object? ConvertNotificationToRequestPayload(ScriptDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs new file mode 100644 index 0000000000..5214dc39cb --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Stylesheet; + +[WebhookEvent("Stylesheet Deleted")] +public class StylesheetDeletedWebhookEvent : WebhookEventBase +{ + public StylesheetDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "stylesheetDeleted"; + + public override object? ConvertNotificationToRequestPayload(StylesheetDeletedNotification notification) => + notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs new file mode 100644 index 0000000000..a135f70131 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Stylesheet; + +[WebhookEvent("Stylesheet Saved")] +public class StylesheetSavedWebhookEvent : WebhookEventBase +{ + public StylesheetSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "stylesheetSaved"; + + public override object? ConvertNotificationToRequestPayload(StylesheetSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs new file mode 100644 index 0000000000..1f3fc756fb --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Partial View Deleted")] +public class PartialViewDeletedWebhookEvent : WebhookEventBase +{ + public PartialViewDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "partialViewDeleted"; + + public override object? ConvertNotificationToRequestPayload(PartialViewDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs new file mode 100644 index 0000000000..224d71c837 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Partial View Saved")] +public class PartialViewSavedWebhookEvent : WebhookEventBase +{ + public PartialViewSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "partialViewSaved"; + + public override object? ConvertNotificationToRequestPayload(PartialViewSavedNotification notification) => + notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs new file mode 100644 index 0000000000..2e18dd9932 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Template Deleted")] +public class TemplateDeletedWebhookEvent : WebhookEventBase +{ + public TemplateDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "templateDeleted"; + + public override object? ConvertNotificationToRequestPayload(TemplateDeletedNotification notification) => + notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs new file mode 100644 index 0000000000..3950e7e370 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Template Saved")] +public class TemplateSavedWebhookEvent : WebhookEventBase +{ + public TemplateSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "templateSaved"; + + public override object? ConvertNotificationToRequestPayload(TemplateSavedNotification notification) + { + // Create a new anonymous object with the properties we want + return new + { + notification.CreateTemplateForContentType, + notification.ContentTypeAlias, + notification.SavedEntities + }; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs new file mode 100644 index 0000000000..1b8b1002bc --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Group Permissions Assigned")] +public class AssignedUserGroupPermissionsWebhookEvent : WebhookEventBase +{ + public AssignedUserGroupPermissionsWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "assignedUserGroupPermissions"; + + public override object? ConvertNotificationToRequestPayload(AssignedUserGroupPermissionsNotification notification) + => notification.EntityPermissions; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs new file mode 100644 index 0000000000..3d7a6b2afa --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Deleted")] +public class UserDeletedWebhookEvent : WebhookEventBase +{ + public UserDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userDeleted"; + + public override object? ConvertNotificationToRequestPayload(UserDeletedNotification notification) + { + // TODO: Map more stuff here + var result = notification.DeletedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.Language, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs new file mode 100644 index 0000000000..0426d7a1e6 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Forgot Password Requested")] +public class UserForgotPasswordRequestedWebhookEvent : WebhookEventBase +{ + public UserForgotPasswordRequestedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userForgotPasswordRequested"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs new file mode 100644 index 0000000000..7bd15f5a51 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Forgotten Password Requested")] +public class UserForgottenPasswordRequestedWebhookEvent : WebhookEventBase +{ + public UserForgottenPasswordRequestedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userForgottenPasswordRequested"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs new file mode 100644 index 0000000000..c05bacb377 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Group Deleted")] +public class UserGroupDeletedWebhookEvent : WebhookEventBase +{ + public UserGroupDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userGroupDeleted"; + + public override object? ConvertNotificationToRequestPayload(UserGroupDeletedNotification notification) => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs new file mode 100644 index 0000000000..0bfe438a7b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Group Saved")] +public class UserGroupSavedWebhookEvent : WebhookEventBase +{ + public UserGroupSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userGroupSaved"; + + public override object? ConvertNotificationToRequestPayload(UserGroupSavedNotification notification) => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs new file mode 100644 index 0000000000..eb3106687f --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Locked")] +public class UserLockedWebhookEvent : WebhookEventBase +{ + public UserLockedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLocked"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs new file mode 100644 index 0000000000..e6957d39dc --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Login Failed")] +public class UserLoginFailedWebhookEvent : WebhookEventBase +{ + public UserLoginFailedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLoginFailed"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs new file mode 100644 index 0000000000..a642827150 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Login Requires Verification")] +public class UserLoginRequiresVerificationWebhookEvent : WebhookEventBase +{ + public UserLoginRequiresVerificationWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLoginRequiresVerification"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs new file mode 100644 index 0000000000..58b98d8e07 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Login Success")] +public class UserLoginSuccessWebhookEvent : WebhookEventBase +{ + public UserLoginSuccessWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLoginSuccess"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs new file mode 100644 index 0000000000..dc0f8d7bb2 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Logout Success")] +public class UserLogoutSuccessWebhookEvent : WebhookEventBase +{ + public UserLogoutSuccessWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLogoutSuccess"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs new file mode 100644 index 0000000000..99c654aba3 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Password Changed")] +public class UserPasswordChangedWebhookEvent : WebhookEventBase +{ + public UserPasswordChangedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userPasswordChanged"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs new file mode 100644 index 0000000000..368116df0b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Password Reset")] +public class UserPasswordResetWebhookEvent : WebhookEventBase +{ + public UserPasswordResetWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userPasswordReset"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs new file mode 100644 index 0000000000..ee65e9b2eb --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Saved")] +public class UserSavedWebhookEvent : WebhookEventBase +{ + public UserSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userSaved"; + + public override object? ConvertNotificationToRequestPayload(UserSavedNotification notification) + { + // TODO: Map more stuff here + var result = notification.SavedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.Language, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs new file mode 100644 index 0000000000..afcac294ca --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Two Factor Requested")] +public class UserTwoFactorRequestedWebhookEvent : WebhookEventBase +{ + public UserTwoFactorRequestedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userTwoFactorRequested"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs new file mode 100644 index 0000000000..a6aea62196 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Unlocked")] +public class UserUnlockedWebhookEvent : WebhookEventBase +{ + public UserUnlockedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userUnlocked"; +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventBase.cs b/src/Umbraco.Core/Webhooks/WebhookEventBase.cs index 5d4ee69d3b..138ebc56fd 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventBase.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventBase.cs @@ -62,7 +62,7 @@ public abstract class WebhookEventBase : IWebhookEvent, INotifica continue; } - await WebhookFiringService.FireAsync(webhook, Alias, notification, cancellationToken); + await WebhookFiringService.FireAsync(webhook, Alias, ConvertNotificationToRequestPayload(notification), cancellationToken); } } @@ -95,4 +95,14 @@ public abstract class WebhookEventBase : IWebhookEvent, INotifica await ProcessWebhooks(notification, webhooks, cancellationToken); } + + /// + /// Use this method if you wish to change the shape of the object to be serialised + /// for the JSON webhook payload. + /// For example excluding sensitive data + /// + /// + /// + public virtual object? ConvertNotificationToRequestPayload(TNotification notification) + => notification; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs index e0eeb186e8..9ebc7ee13f 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs @@ -2,35 +2,43 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Webhooks.Events; +using Umbraco.Cms.Core.Webhooks.Events.Content; +using Umbraco.Cms.Core.Webhooks.Events.DataType; +using Umbraco.Cms.Core.Webhooks.Events.Dictionary; +using Umbraco.Cms.Core.Webhooks.Events.Domain; +using Umbraco.Cms.Core.Webhooks.Events.Language; +using Umbraco.Cms.Core.Webhooks.Events.Media; +using Umbraco.Cms.Core.Webhooks.Events.MediaType; +using Umbraco.Cms.Core.Webhooks.Events.Member; +using Umbraco.Cms.Core.Webhooks.Events.MemberType; +using Umbraco.Cms.Core.Webhooks.Events.Package; +using Umbraco.Cms.Core.Webhooks.Events.PublicAccess; +using Umbraco.Cms.Core.Webhooks.Events.Relation; +using Umbraco.Cms.Core.Webhooks.Events.RelationType; +using Umbraco.Cms.Core.Webhooks.Events.Script; +using Umbraco.Cms.Core.Webhooks.Events.Stylesheet; +using Umbraco.Cms.Core.Webhooks.Events.Template; +using Umbraco.Cms.Core.Webhooks.Events.User; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Webhooks; -public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase +public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase { protected override WebhookEventCollectionBuilder This => this; public override void RegisterWith(IServiceCollection services) { // register the collection - services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection, ServiceLifetime.Singleton)); + services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection, + ServiceLifetime.Singleton)); // register the types RegisterTypes(services); base.RegisterWith(services); } - public WebhookEventCollectionBuilder AddCoreWebhooks() - { - Append(); - Append(); - Append(); - Append(); - Append(); - return this; - } - private void RegisterTypes(IServiceCollection services) { Type[] types = GetRegisteringTypes(GetTypes()).ToArray(); @@ -68,7 +76,8 @@ public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase typeof(INotification).IsAssignableFrom(arg)); + Type? notificationType = + genericArguments.FirstOrDefault(arg => typeof(INotification).IsAssignableFrom(arg)); if (notificationType is not null) { @@ -78,4 +87,187 @@ public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase + this.AddContentWebhooks() + .AddDataTypeWebhooks() + .AddDictionaryWebhooks() + .AddDomainWebhooks() + .AddLanguageWebhooks() + .AddMediaWebhooks() + .AddMemberWebhooks() + .AddMemberTypeWebhooks() + .AddPackageWebhooks() + .AddPublicAccessWebhooks() + .AddRelationWebhooks() + .AddScriptWebhooks() + .AddStylesheetWebhooks() + .AddTemplateWebhooks() + .AddUserWebhooks(); + + public WebhookEventCollectionBuilder AddContentWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddCoreWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddDataTypeWebhooks() + { + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddDictionaryWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddDomainWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddLanguageWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddMediaWebhooks() + { + // Even though these two are in the AddCoreWebhooks() + // The job of the CollectionBuilder should be removing duplicates + // Would allow someone to use .AddCoreWebhooks().AddMediaWebhooks() + // Or if they explicitly they could skip over CoreWebHooks and just add this perhaps + Append(); + Append(); + + Append(); + Append(); + Append(); + + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddMemberWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddMemberTypeWebhooks() + { + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddPackageWebhooks() + { + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddPublicAccessWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddRelationWebhooks() + { + Append(); + Append(); + + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddScriptWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddStylesheetWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddTemplateWebhooks() + { + Append(); + Append(); + + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddUserWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html index e65a5f767f..e87674791c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html @@ -2,29 +2,55 @@ - + - - - - - + -
    -
  • -
    - -
    -
  • -
+ +
+ + +
+ + + +
+
    +
  • +
    + +
    +
  • +
+
+ + + No events were found. +
From c0e0e7bad80087eeb4dcf32bbbb355187dfde5dc Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Thu, 30 Nov 2023 15:53:05 +0100 Subject: [PATCH 063/146] Add HealthCheckCompletedNotification (#15276) --- .../HealthChecks/HealthCheckResults.cs | 2 ++ .../HealthCheckCompletedNotification.cs | 13 ++++++++ .../Jobs/HealthCheckNotifierJob.cs | 31 ++++++++++++++++++- .../HealthChecks/HealthCheckController.cs | 27 ++++++++++++++++ .../Jobs/HealthCheckNotifierJobTests.cs | 4 ++- 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Core/Notifications/HealthCheckCompletedNotification.cs diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs index afeb8ba9fa..f88b96c9d6 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs @@ -53,6 +53,8 @@ public class HealthCheckResults return new HealthCheckResults(results, allChecksSuccessful); } + public static async Task Create(HealthCheck check) => await Create(new List() { check }); + public void LogResults() { Logger.LogInformation("Scheduled health check results:"); diff --git a/src/Umbraco.Core/Notifications/HealthCheckCompletedNotification.cs b/src/Umbraco.Core/Notifications/HealthCheckCompletedNotification.cs new file mode 100644 index 0000000000..67df86deb1 --- /dev/null +++ b/src/Umbraco.Core/Notifications/HealthCheckCompletedNotification.cs @@ -0,0 +1,13 @@ +using Umbraco.Cms.Core.HealthChecks; + +namespace Umbraco.Cms.Core.Notifications; + +public class HealthCheckCompletedNotification : INotification +{ + public HealthCheckCompletedNotification(HealthCheckResults healthCheckResults) + { + HealthCheckResults = healthCheckResults; + } + + public HealthCheckResults HealthCheckResults { get; } +} diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs index ba78af33b4..a3dc6ec779 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs @@ -1,14 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Runtime; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; @@ -38,9 +42,30 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob private readonly ILogger _logger; private readonly HealthCheckNotificationMethodCollection _notifications; private readonly IProfilingLogger _profilingLogger; + private readonly IEventAggregator _eventAggregator; private readonly ICoreScopeProvider _scopeProvider; private HealthChecksSettings _healthChecksSettings; + [Obsolete("Use constructor that accepts IEventAggregator as a parameter, scheduled for removal in V14")] + public HealthCheckNotifierJob( + IOptionsMonitor healthChecksSettings, + HealthCheckCollection healthChecks, + HealthCheckNotificationMethodCollection notifications, + ICoreScopeProvider scopeProvider, + ILogger logger, + IProfilingLogger profilingLogger, + ICronTabParser cronTabParser) + : this( + healthChecksSettings, + healthChecks, + notifications, + scopeProvider, + logger, + profilingLogger, + cronTabParser, + StaticServiceProvider.Instance.GetRequiredService()) + { } + /// /// Initializes a new instance of the class. /// @@ -58,7 +83,8 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob ICoreScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger, - ICronTabParser cronTabParser) + ICronTabParser cronTabParser, + IEventAggregator eventAggregator) { _healthChecksSettings = healthChecksSettings.CurrentValue; _healthChecks = healthChecks; @@ -66,6 +92,7 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob _scopeProvider = scopeProvider; _logger = logger; _profilingLogger = profilingLogger; + _eventAggregator = eventAggregator; Period = healthChecksSettings.CurrentValue.Notification.Period; Delay = DelayCalculator.GetDelay(healthChecksSettings.CurrentValue.Notification.FirstRunTime, cronTabParser, logger, TimeSpan.FromMinutes(3)); @@ -106,6 +133,8 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob HealthCheckResults results = await HealthCheckResults.Create(checks); results.LogResults(); + _eventAggregator.Publish(new HealthCheckCompletedNotification(results)); + // Send using registered notification methods that are enabled. foreach (IHealthCheckNotificationMethod notificationMethod in _notifications.Where(x => x.Enabled)) { diff --git a/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs index 9744ed8c5f..1880b26ced 100644 --- a/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs @@ -3,11 +3,15 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.HealthChecks; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; @@ -24,14 +28,27 @@ public class HealthCheckController : UmbracoAuthorizedJsonController private readonly HealthCheckCollection _checks; private readonly IList _disabledCheckIds; private readonly ILogger _logger; + private readonly IEventAggregator _eventAggregator; + private readonly HealthChecksSettings _healthChecksSettings; /// /// Initializes a new instance of the class. /// + [Obsolete("Use constructor that accepts IEventAggregator as a parameter, scheduled for removal in V14")] public HealthCheckController(HealthCheckCollection checks, ILogger logger, IOptions healthChecksSettings) + : this(checks, logger, healthChecksSettings, StaticServiceProvider.Instance.GetRequiredService()) + { } + + /// + /// Initializes a new instance of the class. + /// + [ActivatorUtilitiesConstructor] + public HealthCheckController(HealthCheckCollection checks, ILogger logger, IOptions healthChecksSettings, IEventAggregator eventAggregator) { _checks = checks ?? throw new ArgumentNullException(nameof(checks)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _eventAggregator = eventAggregator ?? throw new ArgumentException(nameof(eventAggregator)); + _healthChecksSettings = healthChecksSettings?.Value ?? throw new ArgumentException(nameof(healthChecksSettings)); HealthChecksSettings healthCheckConfig = healthChecksSettings.Value ?? throw new ArgumentNullException(nameof(healthChecksSettings)); @@ -80,6 +97,16 @@ public class HealthCheckController : UmbracoAuthorizedJsonController { _logger.LogDebug("Running health check: " + check.Name); } + + if (!_healthChecksSettings.Notification.Enabled) + { + return await check.GetStatus(); + } + + HealthCheckResults results = await HealthCheckResults.Create(check); + _eventAggregator.Publish(new HealthCheckCompletedNotification(results)); + + return await check.GetStatus(); } catch (Exception ex) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs index cf9883603b..316605a04e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; using Umbraco.Cms.Core.Logging; @@ -105,7 +106,8 @@ public class HealthCheckNotifierJobTests mockScopeProvider.Object, mockLogger.Object, mockProfilingLogger.Object, - Mock.Of()); + Mock.Of(), + Mock.Of()); } private void VerifyNotificationsNotSent() => VerifyNotificationsSentTimes(Times.Never()); From e7f7492ce99aad967f0cfcac07e86ffd8403fe40 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 15 Nov 2023 16:08:57 +0100 Subject: [PATCH 064/146] Configure Angular cookie using defaults from antiforgery options and fix logging --- .../Security/BackOfficeAntiforgery.cs | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs index a6e4e96d8b..cc1fc0c4bc 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; @@ -20,25 +22,35 @@ namespace Umbraco.Cms.Web.BackOffice.Security; public class BackOfficeAntiforgery : IBackOfficeAntiforgery { private readonly IAntiforgery _internalAntiForgery; - private GlobalSettings _globalSettings; + private readonly CookieBuilder _angularCookieBuilder; + [Obsolete($"Please use the constructor that accepts {nameof(ILoggerFactory)}. Will be removed in V14.")] public BackOfficeAntiforgery(IOptionsMonitor globalSettings) + : this(globalSettings, NullLoggerFactory.Instance) + { } + + public BackOfficeAntiforgery(IOptionsMonitor globalSettings, ILoggerFactory loggerFactory) { + CookieSecurePolicy cookieSecurePolicy = globalSettings.CurrentValue.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest; + // NOTE: This is the only way to create a separate IAntiForgery service :( // Everything in netcore is internal. I have logged an issue here https://github.com/dotnet/aspnetcore/issues/22217 // but it will not be handled so we have to revert to this. - var services = new ServiceCollection(); - services.AddLogging(); - services.AddAntiforgery(x => - { - x.HeaderName = Constants.Web.AngularHeadername; - x.Cookie.Name = Constants.Web.CsrfValidationCookieName; - x.Cookie.SecurePolicy = globalSettings.CurrentValue.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest; - }); - ServiceProvider container = services.BuildServiceProvider(); - _internalAntiForgery = container.GetRequiredService(); - _globalSettings = globalSettings.CurrentValue; - globalSettings.OnChange(x => _globalSettings = x); + _internalAntiForgery = new ServiceCollection() + .AddSingleton(loggerFactory) + .AddAntiforgery(x => + { + x.HeaderName = Constants.Web.AngularHeadername; + x.Cookie.Name = Constants.Web.CsrfValidationCookieName; + x.Cookie.SecurePolicy = cookieSecurePolicy; + }) + .BuildServiceProvider() + .GetRequiredService(); + + // Configure cookie builder using defaults from antiforgery options + _angularCookieBuilder = new AntiforgeryOptions().Cookie; + _angularCookieBuilder.HttpOnly = false; // Needs to be accessed from JavaScript + _angularCookieBuilder.SecurePolicy = cookieSecurePolicy; } /// @@ -68,15 +80,6 @@ public class BackOfficeAntiforgery : IBackOfficeAntiforgery // We need to set 2 cookies: // The cookie value that angular will use to set a header value on each request - we need to manually set this here // The validation cookie value generated by the anti-forgery helper that we validate the header token against - set above in GetAndStoreTokens - httpContext.Response.Cookies.Append( - Constants.Web.AngularCookieName, - set.RequestToken, - new CookieOptions - { - Path = "/", - //must be js readable - HttpOnly = false, - Secure = _globalSettings.UseHttps - }); + httpContext.Response.Cookies.Append(Constants.Web.AngularCookieName, set.RequestToken, _angularCookieBuilder.Build(httpContext)); } } From feaac58027aa5eede34d4f41acceafbcfb9850b1 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Thu, 30 Nov 2023 16:05:38 +0100 Subject: [PATCH 065/146] localization for webhooks (#15191) --- src/Umbraco.Core/EmbeddedResources/Lang/en.xml | 7 +++++++ src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 7 +++++++ src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html | 10 +++++----- .../src/views/webhooks/webhooks.html | 8 ++++---- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 492cb762cf..00cb113ddf 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1942,6 +1942,13 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Logs No webhook headers have been added No events were found. + Enabled + Events + Event + Url + Types + Webhook key + Retry count Add language diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 12fdf928c2..c6c0280997 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2024,6 +2024,13 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Logs No webhook headers have been added No events were found. + Enabled + Events + Event + Url + Types + Webhook key + Retry count Add language diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html index 9b1a59a0db..3a92c04a56 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html @@ -3,11 +3,11 @@ - Webhook key - Date - Url - Event - Retry count + Webhook key + Date + Url + Event + Retry count diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html index 70d2a0be05..44e69cd499 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html @@ -17,10 +17,10 @@ - - - - + + + + From 212b5662144d6b3ab58c1112ca0fbcecdf58f23a Mon Sep 17 00:00:00 2001 From: Michael Latouche Date: Thu, 30 Nov 2023 16:07:15 +0100 Subject: [PATCH 066/146] Add webhook labels to FR file (#15183) --- src/Umbraco.Core/EmbeddedResources/Lang/fr.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml index b30dba4a69..24da7d0066 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml @@ -1585,6 +1585,13 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Ceci n'est pas d'application pour un Type d'Elément Vous avez apporté des modifications à cette propriété. Etes-vous certain.e de vouloir les annuler? + + Créer un webhook + Ajouter un header au webhook + Logs + Ajouter un Type de Document + Ajouter un Type de Media + Ajouter une langue Langue obligatoire @@ -1715,6 +1722,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Configuration Modélisation Parties Tierces + Webhooks Nouvelle mise à jour disponible From ecc31a308aa75e32de88a91db7e5017a2ed69acf Mon Sep 17 00:00:00 2001 From: Brett Smith Date: Fri, 1 Dec 2023 05:21:09 +1300 Subject: [PATCH 067/146] =?UTF-8?q?Added=20BlockEditorDataConverter=20meth?= =?UTF-8?q?od=20to=20BlockListPropertyEditorBase=20=E2=80=A6=20(#14960)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Blocks/BlockListEditorDataConverter.cs | 19 ++++++++++++++++++- .../BlockListPropertyEditorBase.cs | 12 +++++++++--- .../DataValueEditorReuseTests.cs | 10 ++++++---- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs b/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs index 9d89b41643..6644ee7fb5 100644 --- a/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs +++ b/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs @@ -6,15 +6,32 @@ using Newtonsoft.Json.Linq; namespace Umbraco.Cms.Core.Models.Blocks; /// -/// Data converter for the block list property editor +/// Handles the conversion of data for the block list property editor. /// public class BlockListEditorDataConverter : BlockEditorDataConverter { + /// + /// Initializes a new instance of the class with a default alias. + /// public BlockListEditorDataConverter() : base(Constants.PropertyEditors.Aliases.BlockList) { } + /// + /// Initializes a new instance of the class with a provided alias. + /// + /// The alias of the property editor. + public BlockListEditorDataConverter(string propertyEditorAlias) + : base(propertyEditorAlias) + { + } + + /// + /// Extracts block references from the provided JSON layout. + /// + /// The JSON layout containing the block references. + /// A collection of objects extracted from the JSON layout. protected override IEnumerable? GetBlockReferences(JToken jsonLayout) { IEnumerable? blockListLayout = jsonLayout.ToObject>(); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index 194383560e..a2f5616518 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -35,16 +35,22 @@ public abstract class BlockListPropertyEditorBase : DataEditor public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory; - #region Value Editor + /// + /// Instantiates a new for use with the block list editor property value editor. + /// + /// A new instance of . + protected virtual BlockEditorDataConverter CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(); + protected override IDataValueEditor CreateValueEditor() => - DataValueEditorFactory.Create(Attribute!); + DataValueEditorFactory.Create(Attribute!, CreateBlockEditorDataConverter()); internal class BlockListEditorPropertyValueEditor : BlockEditorPropertyValueEditor { public BlockListEditorPropertyValueEditor( DataEditorAttribute attribute, + BlockEditorDataConverter blockEditorDataConverter, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, @@ -56,7 +62,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor IPropertyValidationService propertyValidationService) : base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper) { - BlockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, logger); + BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, contentTypeService, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService)); Validators.Add(new MinMaxValidator(BlockEditorValues, textService)); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index d88a9689ab..67dd5162bf 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -1,8 +1,9 @@ -using System.Linq; +using System.Linq; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -34,9 +35,10 @@ public class DataValueEditorReuseTests _dataValueEditorFactoryMock .Setup(m => - m.Create(It.IsAny())) + m.Create(It.IsAny(), It.IsAny())) .Returns(() => new BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor( new DataEditorAttribute("a", "b", "c"), + new BlockListEditorDataConverter(), _propertyEditorCollection, Mock.Of(), Mock.Of(), @@ -105,7 +107,7 @@ public class DataValueEditorReuseTests Assert.NotNull(dataValueEditor2); Assert.AreNotSame(dataValueEditor1, dataValueEditor2); _dataValueEditorFactoryMock.Verify( - m => m.Create(It.IsAny()), + m => m.Create(It.IsAny(), It.IsAny()), Times.Exactly(2)); } @@ -128,7 +130,7 @@ public class DataValueEditorReuseTests Assert.AreEqual("config", ((DataValueEditor)dataValueEditor2).Configuration); Assert.AreNotSame(dataValueEditor1, dataValueEditor2); _dataValueEditorFactoryMock.Verify( - m => m.Create(It.IsAny()), + m => m.Create(It.IsAny(), It.IsAny()), Times.Exactly(2)); } } From 71a3452361b65c3fc95fa1e3ac9b8cb680b48364 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 1 Oct 2023 12:05:27 +0200 Subject: [PATCH 068/146] Added [NotNullWhen(true)] attribute to IPublishedSnapshotAccessor and UmbracoContextPublishedSnapshotAccessor --- src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs | 4 +++- .../PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs index 0f9cc8fca9..8abc0906ce 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace Umbraco.Cms.Core.PublishedCache; /// @@ -6,5 +8,5 @@ namespace Umbraco.Cms.Core.PublishedCache; /// public interface IPublishedSnapshotAccessor { - bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot); + bool TryGetPublishedSnapshot([NotNullWhen(true)] out IPublishedSnapshot? publishedSnapshot); } diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs index 8f3e4fe827..91e32e6db4 100644 --- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Web; namespace Umbraco.Cms.Core.PublishedCache; @@ -29,7 +30,7 @@ public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccesso set => throw new NotSupportedException(); // not ok to set } - public bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) + public bool TryGetPublishedSnapshot([NotNullWhen(true)] out IPublishedSnapshot? publishedSnapshot) { if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { From c893027f7c905f3effece323ca99eded1f5061bd Mon Sep 17 00:00:00 2001 From: Amalie Wowern Date: Tue, 3 Oct 2023 19:35:57 +0200 Subject: [PATCH 069/146] Added ReSharperTestRunner to KnownAssemblyExclusionFilter --- src/Umbraco.Core/Composing/TypeFinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index e22df693e9..e3b7ddef9b 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -34,7 +34,7 @@ public class TypeFinder : ITypeFinder "ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog "System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.", "Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite", - "ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension + "ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension }; private static readonly ConcurrentDictionary TypeNamesCache = new(); From 1d5a684fd55d52c56995c337e94ceadc57cc3ef1 Mon Sep 17 00:00:00 2001 From: Kevin Fu <3350190+kevinfu2@users.noreply.github.com> Date: Sun, 8 Oct 2023 15:26:09 +0800 Subject: [PATCH 070/146] fixed #14832 user without HasAccessToSensitiveData change sensitive data: IsApproved IsApproved IsTwoFactorEnabled --- .../Controllers/MemberController.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index c190891217..8851a73a2b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -376,7 +376,10 @@ public class MemberController : ContentControllerBase } // map the custom properties - this will already be set for new entities in our member binder - contentItem.PersistedContent.IsApproved = contentItem.IsApproved; + if (_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? false) + { + contentItem.PersistedContent.IsApproved = contentItem.IsApproved; + } contentItem.PersistedContent.Email = contentItem.Email.Trim(); contentItem.PersistedContent.Username = contentItem.Username; } @@ -548,6 +551,13 @@ public class MemberController : ContentControllerBase } } } + //thoese properties defaulting to sensitive, change the value of the contentItem model to the persisted value + if (contentItem.PersistedContent is not null) + { + contentItem.IsApproved = contentItem.PersistedContent.IsApproved; + contentItem.IsLockedOut = contentItem.PersistedContent.IsLockedOut; + } + contentItem.IsTwoFactorEnabled = await _twoFactorLoginService.IsTwoFactorEnabledAsync(contentItem.Key); } if (contentItem.PersistedContent is not null) From eaf07d2189f9142f7de27bd8609ce816f2938563 Mon Sep 17 00:00:00 2001 From: Matthew Wise <6782865+Matthew-Wise@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:19:25 +0100 Subject: [PATCH 071/146] Added logging around adding a property value during examine indexing --- .../Examine/ContentValueSetBuilder.cs | 58 +++++++++++++++++-- .../UmbracoExamine/IndexInitializer.cs | 3 +- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs index 228610879d..f8e339df56 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs @@ -1,5 +1,7 @@ using Examine; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; @@ -27,6 +29,7 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal private readonly UrlSegmentProviderCollection _urlSegmentProviders; private readonly IUserService _userService; private readonly ILocalizationService _localizationService; + private readonly ILogger _logger; public ContentValueSetBuilder( PropertyEditorCollection propertyEditors, @@ -35,7 +38,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal IShortStringHelper shortStringHelper, IScopeProvider scopeProvider, bool publishedValuesOnly, - ILocalizationService localizationService) + ILocalizationService localizationService, + ILogger logger) : base(propertyEditors, publishedValuesOnly) { _urlSegmentProviders = urlSegmentProviders; @@ -43,9 +47,31 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal _shortStringHelper = shortStringHelper; _scopeProvider = scopeProvider; _localizationService = localizationService; + _logger = logger; } - [Obsolete("Use the constructor that takes an ILocalizationService, scheduled for removal in v14")] + [Obsolete("Use the constructor that takes an ILogger, scheduled for removal in v14")] + public ContentValueSetBuilder( + PropertyEditorCollection propertyEditors, + UrlSegmentProviderCollection urlSegmentProviders, + IUserService userService, + IShortStringHelper shortStringHelper, + IScopeProvider scopeProvider, + bool publishedValuesOnly, + ILocalizationService localizationService) + : this( + propertyEditors, + urlSegmentProviders, + userService, + shortStringHelper, + scopeProvider, + publishedValuesOnly, + localizationService, + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + [Obsolete("Use the constructor that takes an ILocalizationService and ILogger, scheduled for removal in v14")] public ContentValueSetBuilder( PropertyEditorCollection propertyEditors, UrlSegmentProviderCollection urlSegmentProviders, @@ -60,7 +86,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal shortStringHelper, scopeProvider, publishedValuesOnly, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>()) { } @@ -162,13 +189,34 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal { if (!property.PropertyType.VariesByCulture()) { - AddPropertyValue(property, null, null, values, availableCultures); + try + { + AddPropertyValue(property, null, null, values, availableCultures); + } + catch (JsonSerializationException ex) + { + _logger.LogError(ex, "Failed to add property '{PropertyAlias}' to index for content {ContentId}", property.Alias, c.Id); + throw; + } } else { foreach (var culture in c.AvailableCultures) { - AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures); + try + { + AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures); + } + catch (JsonSerializationException ex) + { + _logger.LogError( + ex, + "Failed to add property '{PropertyAlias}' to index for content {ContentId} in culture {Culture}", + property.Alias, + c.Id, + culture); + throw; + } } } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs index 01ca9284f7..52a45c8aca 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs @@ -83,7 +83,8 @@ public class IndexInitializer _shortStringHelper, _scopeProvider, publishedValuesOnly, - _localizationService); + _localizationService, + _loggerFactory.CreateLogger()); return contentValueSetBuilder; } From 7e26d09f77c14de695e098cbe12bee9995008bf1 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Thu, 30 Nov 2023 19:21:48 +0100 Subject: [PATCH 072/146] Bugfix: #14481 - Landmark roles missing (#14980) --- .../umbraco/UmbracoBackOffice/Default.cshtml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/da.xml | 1 + src/Umbraco.Core/EmbeddedResources/Lang/en.xml | 1 + src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 1 + src/Umbraco.Web.UI.Client/src/index.html | 4 ++-- .../src/views/components/application/umb-app-header.html | 4 ++-- .../src/views/components/application/umb-navigation.html | 6 +++--- .../src/views/components/application/umb-search.html | 4 ++-- .../src/views/components/application/umb-sections.html | 4 ++-- 9 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml index 51a9d3d9fa..e83a90f5ee 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml @@ -71,9 +71,9 @@
-
+
-
+
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 16d193c925..c2c5821b12 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -868,6 +868,7 @@ Avatar til Overskrift system felt + Primær Blå diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 152cf9944e..fb60b6813f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -914,6 +914,7 @@ Last Updated Skip to menu Skip to content + Primary Change Crop section Generic diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 8a1b63ed11..be8e0b3347 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -921,6 +921,7 @@ Last Updated Skip to menu Skip to content + Primary Blue diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index a74540ab74..a468de3b9b 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -14,8 +14,8 @@
-
-
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index fae3543429..95e20b42be 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -1,5 +1,5 @@
-
+
-
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index ff647ed411..24a3cb4265 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -1,8 +1,8 @@
- diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html index 5a6a0fc766..48430b579e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html @@ -12,7 +12,7 @@

-
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index a58aac7b8d..fd2a87d5b2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -1,5 +1,5 @@
-
+
+
From ee8339d563e6d9c4067baf8e8fa304d97fb9374b Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 30 Nov 2023 19:25:03 +0100 Subject: [PATCH 073/146] Add contentTypeEditor to editorService (#15034) --- .../components/umbgroupsbuilder.directive.js | 13 ++----- .../src/common/services/editor.service.js | 34 +++++++++++++++++++ .../views/datatype.info.controller.js | 13 ++----- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 2faa9cbeda..227193128e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -509,6 +509,7 @@ scope.openContentType = (contentTypeId) => { const editor = { id: contentTypeId, + entityType: scope.contentType, submit: () => { const args = { node: scope.model }; eventsService.emit("editors.documentType.reload", args); @@ -519,17 +520,7 @@ } }; - switch (scope.contentType) { - case "documentType": - editorService.documentTypeEditor(editor); - break; - case "mediaType": - editorService.mediaTypeEditor(editor); - break; - case "memberType": - editorService.memberTypeEditor(editor); - break; - } + editorService.contentTypeEditor(editor); }; /* ---------- TABS ---------- */ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index cabb9b0139..5418aa399c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -635,6 +635,39 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#contentTypeEditor + * @methodOf umbraco.services.editorService + * + * @description + * Opens the content type editor in infinite editing, the submit callback returns the alias of the saved content type. + * + * @param {object} editor rendering options. + * @param {string} editor.entityType Entity type to open - default document type. + * @param {number} editor.id Indicates the ID of the content type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the content type editor for creating a new content type. + * @param {boolean} editor.create Set to `true` to open the content type editor for creating a new content type. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. + * @returns {object} editor object. + */ + function contentTypeEditor(editor) { + + if (!editor.entityType) editor.entityType = "documentType"; + + switch (editor.entityType) { + case "documentType": + documentTypeEditor(editor); + break; + case "mediaType": + mediaTypeEditor(editor); + break; + case "memberType": + memberTypeEditor(editor); + break; + } + } + /** * @ngdoc method * @name umbraco.services.editorService#documentTypeEditor @@ -1158,6 +1191,7 @@ When building a custom infinite editor view you can use the same components as a linkPicker: linkPicker, mediaPicker: mediaPicker, iconPicker: iconPicker, + contentTypeEditor: contentTypeEditor, documentTypeEditor: documentTypeEditor, mediaTypeEditor: mediaTypeEditor, memberTypeEditor: memberTypeEditor, diff --git a/src/Umbraco.Web.UI.Client/src/views/dataTypes/views/datatype.info.controller.js b/src/Umbraco.Web.UI.Client/src/views/dataTypes/views/datatype.info.controller.js index 2cb075e80d..ffee8b023c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dataTypes/views/datatype.info.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dataTypes/views/datatype.info.controller.js @@ -60,6 +60,7 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, $timeout const editor = { id: id, + entityType: type, submit: function (model) { editorService.close(); vm.view.loading = true; @@ -71,17 +72,7 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, $timeout } }; - switch (type) { - case "documentType": - editorService.documentTypeEditor(editor); - break; - case "mediaType": - editorService.mediaTypeEditor(editor); - break; - case "memberType": - editorService.memberTypeEditor(editor); - break; - } + editorService.contentTypeEditor(editor); } loadRelations(); From e947daff4518fa0f5f1098b4e38815bb9ae769f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Mastrup?= Date: Wed, 25 Oct 2023 11:36:17 +0200 Subject: [PATCH 074/146] Added password toggle to Umbraco installer form --- .../src/installer/steps/user.controller.js | 7 +++++ .../src/installer/steps/user.html | 12 ++++++-- .../src/less/installer.less | 28 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 8b42bdbe27..c7f0bdb1c3 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -86,6 +86,13 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f } }; + $scope.togglePassword = function () { + var elem = $("form[name='installerForm'] input[name='installer.current.model.password']"); + elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); + elem.focus(); + $(".password-text.show, .password-text.hide").toggle(); + } + function onChangeConsent(values) { const result = Math.round(Number(values[0]) - 1); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index ea487e046a..d1acf0691b 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -49,9 +49,9 @@
- + + +
+ +
+ At least {{installer.current.model.minCharLength}} characters long Date: Thu, 30 Nov 2023 19:29:06 +0100 Subject: [PATCH 075/146] Make Block List configuration consistent with Block Grid (#15087) --- .../blockcard/umb-block-card-grid.less | 2 +- .../blockgrid.blockconfiguration.less | 51 +++++++++---------- .../blocklist.blockconfiguration.html | 7 +-- .../blocklist.blockconfiguration.less | 6 +++ 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less index 1e9538d670..42ba81a6bb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less @@ -1,7 +1,7 @@ .umb-block-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - grid-auto-rows: minmax(30px, auto); + grid-auto-rows: minmax(150px, auto); gap: 20px; margin-bottom: 20px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less index 43151fcabb..771d6c2af8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less @@ -1,32 +1,29 @@ .umb-block-grid-block-configuration { + margin-bottom: 20px; - margin-bottom: 20px; + uui-button { + --uui-button-border-radius: 6px; + font-weight: 700; + min-height: 80px; + } - uui-button { - font-weight: 700; - --uui-button-border-radius: 6px; - min-height: 80px; + .__get-sample-box { + position: relative; + border: 1px solid @gray-10; + border-radius: 6px; + box-shadow: 3px 3px 6px rgba(0, 0, 0, .07); + padding-left: 40px; + padding-right: 40px; + padding-top: 15px; + padding-bottom: 20px; + margin-bottom: 40px; + max-width: 480px; + + umb-button { + margin-left: auto; + margin-right: 0; + display: block; + width: fit-content; } - - .__get-sample-box { - position:relative; - border: 1px solid @gray-10; - border-radius: 6px; - box-shadow: 3px 3px 6px rgba(0, 0, 0, .07); - - padding-left: 40px; - padding-right: 40px; - padding-top: 15px; - padding-bottom: 20px; - margin-bottom: 40px; - max-width: 480px; - - umb-button { - margin-left: auto; - margin-right: 0; - display: block; - width: fit-content; - } - } - + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html index 062e2dddee..1f4a696380 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html @@ -18,8 +18,9 @@
- + + Add Block + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less index afb4316ce8..9ceadc7a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less @@ -1,5 +1,11 @@ .umb-block-list-block-configuration { + uui-button { + --uui-button-border-radius: 6px; + font-weight: 700; + min-height: 80px; + } + .__add-button { position: relative; display: inline-flex; From 8c23075ec5808d5989936ad162fcd7a037e05ec2 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 30 Nov 2023 19:38:56 +0100 Subject: [PATCH 076/146] Adjust content type picker to support different content types (#15080) --- .../src/common/services/editor.service.js | 37 ++++++++++++++++++- ...blockgrid.blockconfiguration.controller.js | 7 +--- ...blocklist.blockconfiguration.controller.js | 7 +--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 5418aa399c..0dafdc6ff4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -391,12 +391,44 @@ When building a custom infinite editor view you can use the same components as a * Opens a content type picker in infinite editing, the submit callback returns an array of selected items * * @param {object} editor rendering options. + * @param {string} editor.entityType Entity type to open - default is document type. * @param {boolean} editor.multiPicker Pick one or multiple items. * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. * @param {function} editor.close Callback function when the close button is clicked. * @returns {object} editor object */ function contentTypePicker(editor) { + + if (!editor.entityType) editor.entityType = "documentType"; + + switch (editor.entityType) { + case "documentType": + documentTypePicker(editor); + break; + case "mediaType": + mediaTypePicker(editor); + break; + case "memberType": + memberTypePicker(editor); + break; + } + } + + /** + * @ngdoc method + * @name umbraco.services.editorService#documentTypePicker + * @methodOf umbraco.services.editorService + * + * @description + * Opens a document type picker in infinite editing, the submit callback returns an array of selected items + * + * @param {object} editor rendering options. + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. + * @returns {object} editor object + */ + function documentTypePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; if (!editor.size) editor.size = "small"; editor.section = "settings"; @@ -1176,10 +1208,12 @@ When building a custom infinite editor view you can use the same components as a open: open, close: close, closeAll: closeAll, - mediaEditor: mediaEditor, contentEditor: contentEditor, + mediaEditor: mediaEditor, + memberEditor: memberEditor, contentPicker: contentPicker, contentTypePicker: contentTypePicker, + documentTypePicker: documentTypePicker, mediaTypePicker: mediaTypePicker, memberTypePicker: memberTypePicker, copy: copy, @@ -1212,7 +1246,6 @@ When building a custom infinite editor view you can use the same components as a macroPicker: macroPicker, memberGroupPicker: memberGroupPicker, memberPicker: memberPicker, - memberEditor: memberEditor, mediaCropDetails }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.controller.js index ade5e9829a..3f0584556c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.controller.js @@ -199,12 +199,9 @@ localizationService.localize("blockEditor_headlineCreateBlock").then(function(localizedTitle) { - const contentTypePicker = { + const dialog = { title: localizedTitle, - section: "settings", - treeAlias: "documentTypes", entityType: "documentType", - isDialog: true, filter: function (node) { if (node.metaData.isElement === true) { var key = udiService.getKey(node.udi); @@ -238,8 +235,8 @@ } ] }; - editorService.treePicker(contentTypePicker); + editorService.contentTypePicker(dialog); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.controller.js index 63ab76b553..89e62e390d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.controller.js @@ -103,12 +103,9 @@ localizationService.localize("blockEditor_headlineCreateBlock").then(localizedTitle => { - const contentTypePicker = { + const dialog = { title: localizedTitle, - section: "settings", - treeAlias: "documentTypes", entityType: "documentType", - isDialog: true, filter: function (node) { if (node.metaData.isElement === true) { var key = udiService.getKey(node.udi); @@ -142,7 +139,7 @@ ] }; - editorService.treePicker(contentTypePicker); + editorService.contentTypePicker(dialog); }); }; From 0a718b058535b155b96e307f78e7d70ac94367be Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 30 Nov 2023 19:59:52 +0100 Subject: [PATCH 077/146] Fix log message in Verify2FACode --- .../Controllers/UmbTwoFactorLoginController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs index f498189ff0..3a4715b537 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs @@ -73,7 +73,7 @@ public class UmbTwoFactorLoginController : SurfaceController MemberIdentityUser? user = await _memberSignInManager.GetTwoFactorAuthenticationUserAsync(); if (user == null!) { - _logger.LogWarning("PostVerify2FACode :: No verified member found, returning 404"); + _logger.LogWarning("Verify2FACode :: No verified member found, returning 404"); return NotFound(); } From d5d4edbe92bb93985c70593a1ce466546a3820e6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 30 Nov 2023 21:51:54 +0100 Subject: [PATCH 078/146] Bump Version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index d733b40e8f..dcb4240d7c 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.8.0", + "version": "10.8.1", "assemblyVersion": { "precision": "build" }, From da6aaa07db10889661d133a5bdcdea9493d4f1d1 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 16 Nov 2023 10:38:53 +0100 Subject: [PATCH 079/146] Don't change format in convertToLocalMomentTime (#15223) (cherry picked from commit 963e57723a73449f8cf2b3840e1a1535fc2d3c54) --- src/Umbraco.Web.UI.Client/src/common/services/util.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index e69d49d82e..91318f5cf4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -108,7 +108,7 @@ function dateHelper() { const localOffset = new Date().getTimezoneOffset(); const serverTimeNeedsOffsetting = -serverOffset !== localOffset; if (serverTimeNeedsOffsetting) { - dateVal = this.convertToLocalMomentTime(date, serverOffset, format); + dateVal = this.convertToLocalMomentTime(date, serverOffset); } else { dateVal = moment(date, parsingFormat); } From 8f497c8f7ea7c9d907709772d78b40c40611dd2e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:32:54 +0100 Subject: [PATCH 080/146] V13 RC: Fix regression for external login providers (#15334) * add check for the presence of the ?logout=true parameter on the login screen to avoid autoredirect to external login * refactor controller to eliminate need for duplicate defaultResponse and externalLoginResponse (they are always the same) * refactor to reduce nesting --- .../Controllers/BackOfficeController.cs | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 134a89372f..65d7c442f2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -11,13 +11,13 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Grid; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -145,7 +145,6 @@ public class BackOfficeController : UmbracoController return await RenderDefaultOrProcessExternalLoginAsync( result, - () => defaultView, () => defaultView); } @@ -172,7 +171,6 @@ public class BackOfficeController : UmbracoController return await RenderDefaultOrProcessExternalLoginAsync( result, - () => View(viewPath), () => View(viewPath)); } @@ -460,11 +458,9 @@ public class BackOfficeController : UmbracoController /// private async Task RenderDefaultOrProcessExternalLoginAsync( AuthenticateResult authenticateResult, - Func defaultResponse, - Func externalSignInResponse) + Func defaultResponse) { ArgumentNullException.ThrowIfNull(defaultResponse); - ArgumentNullException.ThrowIfNull(externalSignInResponse); ViewData.SetUmbracoPath(_globalSettings.GetUmbracoMvcArea(_hostingEnvironment)); @@ -481,23 +477,35 @@ public class BackOfficeController : UmbracoController // First check if there's external login info, if there's not proceed as normal ExternalLoginInfo? loginInfo = await _signInManager.GetExternalLoginInfoAsync(); - if (loginInfo == null || loginInfo.Principal == null) + if (loginInfo != null) { - // if the user is not logged in, check if there's any auto login redirects specified - if (!authenticateResult.Succeeded) - { - var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider(); - if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace()) - { - return ExternalLogin(oauthRedirectAuthProvider!); - } - } + // we're just logging in with an external source, not linking accounts + return await ExternalSignInAsync(loginInfo, defaultResponse); + } + // If we are authenticated then we can just render the default view + if (authenticateResult.Succeeded) + { return defaultResponse(); } - // we're just logging in with an external source, not linking accounts - return await ExternalSignInAsync(loginInfo, externalSignInResponse); + // If the user is not logged in, check if there's any auto login redirects specified + var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider(); + + // If there's no auto login provider specified, then we'll render the default view + if (oauthRedirectAuthProvider.IsNullOrWhiteSpace()) + { + return defaultResponse(); + } + + // If the ?logout=true query string is not specified, then we'll redirect to the external login provider + // which will then redirect back to the ExternalLoginCallback action + if (Request.Query.TryGetValue("logout", out StringValues logout) == false || logout != "true") + { + return ExternalLogin(oauthRedirectAuthProvider); + } + + return defaultResponse(); } private async Task ExternalSignInAsync(ExternalLoginInfo loginInfo, Func response) From 50baed151d341f7ed5c7ae957b9d52263193840b Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 1 Dec 2023 11:12:18 +0100 Subject: [PATCH 081/146] Use version range on project references (#14719) --- Directory.Build.props | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index 4c315660e6..b9439a50ea 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -49,4 +49,13 @@ $(MSBuildThisFileDirectory) + + + + + <_ProjectReferencesWithVersions Condition="'%(ProjectVersion)' != ''"> + [%(ProjectVersion), $([MSBuild]::Add($([System.Text.RegularExpressions.Regex]::Match('%(ProjectVersion)', '^\d+').Value), 1))) + + + From d315a1082ef27e3ff8aa75df229945943dfe2974 Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Fri, 1 Dec 2023 13:05:50 +0100 Subject: [PATCH 082/146] Fixed potential NuCache file lock causing unusable website (#14940) * Fixed potential NuCache file lock causing unusable website * Remove unnecessary volatile read --- .../PublishedSnapshotService.cs | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 68b50cfd91..38165ee12a 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -58,11 +58,13 @@ internal class PublishedSnapshotService : IPublishedSnapshotService private long _domainGen; private SnapDictionary _domainStore = null!; private IAppCache? _elementsCache; - private bool _isReadSet; + private bool _isReadSet; private bool _isReady; private object? _isReadyLock; + private bool _mainDomRegistered; + private BPlusTree? _localContentDb; private bool _localContentDbExists; private BPlusTree? _localMediaDb; @@ -490,9 +492,20 @@ internal class PublishedSnapshotService : IPublishedSnapshotService // it will not be able to close the stores until we are done populating (if the store is empty) lock (_storesLock) { + SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState(); + if (!_options.IgnoreLocalDb) { - _mainDom.Register(MainDomRegister, MainDomRelease); + if (!_mainDomRegistered) + { + _mainDom.Register(MainDomRegister, MainDomRelease); + } + else + { + // MainDom is already registered, so we must be retrying to load cache data + // We can't trust the localdb state, so always perform a cold boot + bootState = SyncBootState.ColdBoot; + } // stores are created with a db so they can write to it, but they do not read from it, // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to @@ -541,8 +554,6 @@ internal class PublishedSnapshotService : IPublishedSnapshotService var okContent = false; var okMedia = false; - SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState(); - try { if (bootState != SyncBootState.ColdBoot && _localContentDbExists) @@ -614,6 +625,8 @@ internal class PublishedSnapshotService : IPublishedSnapshotService _localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists, _config, _contentDataSerializer); _localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists, _config, _contentDataSerializer); + _mainDomRegistered = true; + _logger.LogInformation( "Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}", _localContentDbExists, @@ -699,21 +712,10 @@ internal class PublishedSnapshotService : IPublishedSnapshotService _localContentDb?.Clear(); // IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder - try - { - IEnumerable kits = _publishedContentService.GetAllContentSources(); - return onStartup - ? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) - : _contentStore.SetAllLocked(kits, _config.KitBatchSize, true); - } - catch (ThreadAbortException tae) - { - // Caught a ThreadAbortException, most likely from a database timeout. - // If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment). - _logger.LogWarning(tae, tae.Message); - } - - return false; + IEnumerable kits = _publishedContentService.GetAllContentSources(); + return onStartup + ? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) + : _contentStore.SetAllLocked(kits, _config.KitBatchSize, true); } } @@ -762,21 +764,10 @@ internal class PublishedSnapshotService : IPublishedSnapshotService } // IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder - try - { - IEnumerable kits = _publishedContentService.GetAllMediaSources(); - return onStartup - ? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) - : _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true); - } - catch (ThreadAbortException tae) - { - // Caught a ThreadAbortException, most likely from a database timeout. - // If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment). - _logger.LogWarning(tae, tae.Message); - } - - return false; + IEnumerable kits = _publishedContentService.GetAllMediaSources(); + return onStartup + ? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) + : _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true); } } From 7f4378066d436facf048307ddc69b0668e30a986 Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Fri, 1 Dec 2023 13:43:39 +0100 Subject: [PATCH 083/146] Expose the Delivery API CLR type (#15150) * Expose the Delivery API CLR type * Updated field naming and warnings Addresses PR feedback * Added default implementation to prevent breaking change --- .../IPublishedPropertyType.cs | 8 +++++++ .../PublishedContent/PublishedPropertyType.cs | 22 ++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs index 45d36abb6a..fd22b7cc9a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -65,6 +65,14 @@ public interface IPublishedPropertyType /// Type ModelClrType { get; } + /// + /// Gets the property model Delivery Api CLR type. + /// + /// + /// The model CLR type may be a type, or may contain types. + /// + Type DeliveryApiModelClrType => ModelClrType; + /// /// Gets the property CLR type. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 848e961d0b..dae0287ee7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -23,6 +23,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent private PropertyCacheLevel _deliveryApiCacheLevel; private Type? _modelClrType; + private Type? _deliveryApiModelClrType; private Type? _clrType; #region Constructors @@ -191,11 +192,12 @@ namespace Umbraco.Cms.Core.Models.PublishedContent } } + var deliveryApiPropertyValueConverter = _converter as IDeliveryApiPropertyValueConverter; + _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; - _deliveryApiCacheLevel = _converter is IDeliveryApiPropertyValueConverter deliveryApiPropertyValueConverter - ? deliveryApiPropertyValueConverter.GetDeliveryApiPropertyCacheLevel(this) - : _cacheLevel; + _deliveryApiCacheLevel = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyCacheLevel(this) ?? _cacheLevel; _modelClrType = _converter?.GetPropertyValueType(this) ?? typeof(object); + _deliveryApiModelClrType = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyValueType(this) ?? _modelClrType; } /// @@ -331,6 +333,20 @@ namespace Umbraco.Cms.Core.Models.PublishedContent } } + /// + public Type DeliveryApiModelClrType + { + get + { + if (!_initialized) + { + Initialize(); + } + + return _deliveryApiModelClrType!; + } + } + /// public Type? ClrType { From a4b410719034a59ab6456662253e115ca9aaa14a Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Fri, 1 Dec 2023 13:43:39 +0100 Subject: [PATCH 084/146] Expose the Delivery API CLR type (#15150) * Expose the Delivery API CLR type * Updated field naming and warnings Addresses PR feedback * Added default implementation to prevent breaking change (cherry picked from commit 7f4378066d436facf048307ddc69b0668e30a986) --- .../IPublishedPropertyType.cs | 8 +++++ .../PublishedContent/PublishedPropertyType.cs | 29 +++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs index 4cf5bdd6af..9bced05ff5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -71,6 +71,14 @@ public interface IPublishedPropertyType /// Type ModelClrType { get; } + /// + /// Gets the property model Delivery Api CLR type. + /// + /// + /// The model CLR type may be a type, or may contain types. + /// + Type DeliveryApiModelClrType => ModelClrType; + /// /// Gets the property CLR type. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 52e3371767..ed2d19cd6f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -24,6 +24,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent private PropertyCacheLevel _deliveryApiCacheLevelForExpansion; private Type? _modelClrType; + private Type? _deliveryApiModelClrType; private Type? _clrType; #region Constructors @@ -192,17 +193,13 @@ namespace Umbraco.Cms.Core.Models.PublishedContent } } + var deliveryApiPropertyValueConverter = _converter as IDeliveryApiPropertyValueConverter; + _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; - if (_converter is IDeliveryApiPropertyValueConverter deliveryApiPropertyValueConverter) - { - _deliveryApiCacheLevel = deliveryApiPropertyValueConverter.GetDeliveryApiPropertyCacheLevel(this); - _deliveryApiCacheLevelForExpansion = deliveryApiPropertyValueConverter.GetDeliveryApiPropertyCacheLevelForExpansion(this); - } - else - { - _deliveryApiCacheLevel = _deliveryApiCacheLevelForExpansion = _cacheLevel; - } + _deliveryApiCacheLevel = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyCacheLevel(this) ?? _cacheLevel; + _deliveryApiCacheLevelForExpansion = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyCacheLevelForExpansion(this) ?? _cacheLevel; _modelClrType = _converter?.GetPropertyValueType(this) ?? typeof(object); + _deliveryApiModelClrType = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyValueType(this) ?? _modelClrType; } /// @@ -352,6 +349,20 @@ namespace Umbraco.Cms.Core.Models.PublishedContent } } + /// + public Type DeliveryApiModelClrType + { + get + { + if (!_initialized) + { + Initialize(); + } + + return _deliveryApiModelClrType!; + } + } + /// public Type? ClrType { From c6d01178c06a519423b3c37fea7bbdd0e536319f Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 1 Dec 2023 14:51:42 +0100 Subject: [PATCH 085/146] Update z-index of block actions (#15329) --- src/Umbraco.Web.UI.Client/src/less/variables.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index af1c136cc0..505036cf06 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -353,7 +353,7 @@ // Try to avoid customizing these :) @zIndexEditor: 100; @zIndexTree: 100; -@zIndexBlockActions: 500; +@zIndexBlockActions: 90; // Should be less than "zIndexEditor" to stay behind editor overlay in infinite mode. @zindexDropdown: 1000; @zindexPopover: 1010; @zindexTooltip: 1030; From 2a56d4e376fe8e592391e48e95064acf5921f9dc Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:00:26 +0100 Subject: [PATCH 086/146] V12 Integration test for InternalIndex and ExternalIndex Search (#15167) * Added the possibility for us to search through the external index * Added ForceInstantExecution * Added InternalIndex integration tests * Added External Index integration tests * Removed comments * Remove unused internal field * Add mechanism to wait for index rebuilding * Remove some of the usages of sleep * Remove sleeps * Added comments from review * Added a method for getting the IndexPath --------- Co-authored-by: Nikolaj --- .../BackOfficeExamineSearcherTests.cs | 611 ++++++++++++++++++ .../UmbracoExamine/ExamineBaseTest.cs | 46 ++ .../ExamineExternalIndexSearcherTest.cs | 417 ++++++++++++ .../ExamineExternalIndexTests.cs | 137 ++++ .../IExamineExternalIndexSearcherTest.cs | 18 + 5 files changed, 1229 insertions(+) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs new file mode 100644 index 0000000000..3447dff634 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs @@ -0,0 +1,611 @@ +using Examine; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Examine.DependencyInjection; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Infrastructure.Search; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; +using Umbraco.Cms.Web.BackOffice.Security; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class BackOfficeExamineSearcherTests : ExamineBaseTest +{ + [SetUp] + public void Setup() + { + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.InternalIndexName)); + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.ExternalIndexName)); + var httpContext = new DefaultHttpContext(); + httpContext.RequestServices = Services; + Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext); + } + + [TearDown] + public void TearDown() + { + // When disposing examine, it does a final write, which ends up locking the file if the indexing is not done yet. So we have this wait to circumvent that. + Thread.Sleep(1500); + // Sometimes we do not dispose all services in time and the test fails because the log file is locked. Resulting in all other tests failing as well + Services.DisposeIfDisposable(); + } + + private IBackOfficeExamineSearcher BackOfficeExamineSearcher => GetRequiredService(); + + private IContentTypeService ContentTypeService => GetRequiredService(); + + private ILocalizationService LocalizationService => GetRequiredService(); + + private ContentService ContentService => (ContentService)GetRequiredService(); + + private IUserStore BackOfficeUserStore => + GetRequiredService>(); + + private IBackOfficeSignInManager BackOfficeSignInManager => GetRequiredService(); + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder + .AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddExamineIndexes(); + builder.AddBackOfficeIdentity(); + builder.Services.AddHostedService(); + } + + private IEnumerable BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0) => + BackOfficeExamineSearcher.Search(query, UmbracoEntityTypes.Document, + pageSize, pageIndex, out _, ignoreUserStartNodes: true); + + private async Task SetupUserIdentity(string userId) + { + var identity = + await BackOfficeUserStore.FindByIdAsync(userId, CancellationToken.None); + await BackOfficeSignInManager.SignInAsync(identity, false); + } + + private async Task CreateDefaultPublishedContent(string contentName) + { + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .Build(); + + var createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + return createdContent; + } + + private async Task CreateDefaultPublishedContentWithTwoLanguages(string englishNodeName, string danishNodeName) + { + const string usIso = "en-US"; + const string dkIso = "da"; + + var langDa = new LanguageBuilder() + .WithCultureInfo(dkIso) + .Build(); + LocalizationService.Save(langDa); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .WithContentVariation(ContentVariation.Culture) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithId(0) + .WithCultureName(usIso, englishNodeName) + .WithCultureName(dkIso, danishNodeName) + .WithContentType(contentType) + .Build(); + var createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + return createdContent; + } + + [Test] + public async Task Search_Published_Content_With_Empty_Query() + { + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + // Arrange + const string contentName = "TestContent"; + await CreateDefaultPublishedContent(contentName); + + string query = string.Empty; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + Assert.AreEqual(0, actual.Count()); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + await CreateDefaultPublishedContent(contentName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentName); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Non_Existing_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + await CreateDefaultPublishedContent(contentName); + + string query = "ContentTest"; + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + Assert.AreEqual(0, actual.Count()); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Content_Id() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "RandomContentName"; + PublishResult createdContent = await CreateDefaultPublishedContent(contentName); + + string contentId = createdContent.Content.Id.ToString(); + + string query = contentId; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentName); + Assert.AreEqual(searchResults.First().Id, contentId); + } + + [Test] + public async Task Search_Two_Published_Content_With_Similar_Names_By_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestName Original"; + const string secondContentName = "TestName Copy"; + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var firstContent = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(firstContent), Constants.UmbracoIndexes.InternalIndexName); + + var secondContent = new ContentBuilder() + .WithId(0) + .WithName(secondContentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(secondContent), Constants.UmbracoIndexes.InternalIndexName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(2, searchResults.Count()); + // Checks if the first content in the search is the original content + Assert.AreEqual(searchResults.First().Id, firstContent.Id.ToString()); + // Checks if the score for the original name is higher than the score for the copy + Assert.Greater(searchResults.First().Score, searchResults.Last().Score); + } + + [Test] + public async Task Search_For_Child_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "ParentTestContent"; + const string childContentName = "ChildTestContent"; + + var contentType = new ContentTypeBuilder() + .WithName("Document") + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + var childContent = new ContentBuilder() + .WithName(childContentName) + .WithContentType(contentType) + .WithParentId(content.Id) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childContent), Constants.UmbracoIndexes.InternalIndexName); + + string parentQuery = content.Id.ToString(); + + string childQuery = childContent.Id.ToString(); + + // Act + IEnumerable parentContentActual = BackOfficeExamineSearch(parentQuery); + IEnumerable childContentActual = BackOfficeExamineSearch(childQuery); + + // Assert + IEnumerable contentActual = parentContentActual.ToArray(); + IEnumerable searchResults = childContentActual.ToArray(); + Assert.AreEqual(1, contentActual.Count()); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(contentActual.First().Values["nodeName"], contentName); + Assert.AreEqual(searchResults.First().Values["nodeName"], childContentName); + } + + [Test] + public async Task Search_For_Child_In_Child_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "ParentTestContent"; + const string childContentName = "ChildTestContent"; + const string childChildContentName = "ChildChildTestContent"; + + var contentType = new ContentTypeBuilder() + .WithName("Document") + .Build(); + ContentTypeService.Save(contentType); + + var content = new ContentBuilder() + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + var childContent = new ContentBuilder() + .WithName(childContentName) + .WithContentType(contentType) + .WithParentId(content.Id) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childContent), Constants.UmbracoIndexes.InternalIndexName); + + var childChildContent = new ContentBuilder() + .WithName(childChildContentName) + .WithContentType(contentType) + .WithParentId(childContent.Id) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childChildContent), Constants.UmbracoIndexes.InternalIndexName); + + string parentQuery = content.Id.ToString(); + string childQuery = childContent.Id.ToString(); + string childChildQuery = childChildContent.Id.ToString(); + + // Act + IEnumerable parentContentActual = BackOfficeExamineSearch(parentQuery); + IEnumerable childContentActual = BackOfficeExamineSearch(childQuery); + IEnumerable childChildContentActual = BackOfficeExamineSearch(childChildQuery); + + IEnumerable parentSearchResults = parentContentActual.ToArray(); + IEnumerable childSearchResults = childContentActual.ToArray(); + IEnumerable childChildSearchResults = childChildContentActual.ToArray(); + + // Assert + Assert.AreEqual(1, parentSearchResults.Count()); + Assert.AreEqual(1, childSearchResults.Count()); + Assert.AreEqual(1, childChildSearchResults.Count()); + Assert.AreEqual(parentSearchResults.First().Values["nodeName"], contentName); + Assert.AreEqual(childSearchResults.First().Values["nodeName"], childContentName); + Assert.AreEqual(childChildSearchResults.First().Values["nodeName"], childChildContentName); + } + + [Test] + public async Task Search_Published_Content_With_Query_With_Content_Name_No_User_Logged_In() + { + // Arrange + const string contentName = "TestContent"; + + PublishResult createdContent = await CreateDefaultPublishedContent(contentName); + + string query = createdContent.Content.Id.ToString(); + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + Assert.AreEqual(0, actual.Count()); + } + + // Multiple Languages + [Test] + public async Task Search_Published_Content_By_Content_Name_With_Two_Languages() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string usIso = "en-US"; + const string dkIso = "da"; + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + var langDa = new LanguageBuilder() + .WithCultureInfo(dkIso) + .Build(); + LocalizationService.Save(langDa); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .WithContentVariation(ContentVariation.Culture) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithId(0) + .WithCultureName(usIso, englishNodeName) + .WithCultureName(dkIso, danishNodeName) + .WithContentType(contentType) + .Build(); + PublishResult createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + string query = createdContent.Content.Id.ToString(); + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + [Test] + public async Task Search_For_Published_Content_Name_With_Two_Languages_By_Default_Language_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName); + + string query = englishNodeName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + [Test] + public async Task Search_For_Published_Content_Name_With_Two_Languages_By_Non_Default_Language_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName); + + string query = danishNodeName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + [Test] + public async Task Search_Published_Content_With_Two_Languages_By_Id() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + var contentNode = await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName); + + string query = contentNode.Content.Id.ToString(); + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + // Check All Indexed Values + [Test] + public async Task Check_All_Indexed_Values_For_Published_Content_With_No_Properties() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + ContentTypeService.Save(contentType); + + var contentNode = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(contentNode), Constants.UmbracoIndexes.InternalIndexName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + + string contentNodePublish = string.Empty; + if (contentNode.Published) + { + contentNodePublish = "y"; + } + + string contentTypeCultureVariations = string.Empty; + + if (contentType.Variations == ContentVariation.Nothing) + { + contentTypeCultureVariations = "n"; + } + + Assert.Multiple(() => + { + Assert.AreEqual(searchResults.First().Values["__NodeId"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__IndexType"], "content"); + Assert.AreEqual(searchResults.First().Values["__NodeTypeAlias"], contentNode.ContentType.Alias); + Assert.AreEqual(searchResults.First().Values["__Published"], contentNodePublish); + Assert.AreEqual(searchResults.First().Values["id"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__Key"], contentNode.Key.ToString()); + Assert.AreEqual(searchResults.First().Values["parentID"], contentNode.ParentId.ToString()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentNode.Name); + Assert.AreEqual(searchResults.First().Values["__VariesByCulture"], contentTypeCultureVariations); + Assert.AreEqual(searchResults.First().Values["__Icon"], contentNode.ContentType.Icon); + }); + } + + [Test] + public async Task Check_All_Indexed_Values_For_Published_Content_With_Properties() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + const string propertyEditorName = "TestBox"; + + var contentType = new ContentTypeBuilder() + .WithId(0) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithName(propertyEditorName) + .WithAlias("testBox") + .Done() + .Build(); + ContentTypeService.Save(contentType); + + var contentNode = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .WithPropertyValues(new { testBox = "TestValue" }) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(contentNode), Constants.UmbracoIndexes.InternalIndexName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + + string contentNodePublish = string.Empty; + string contentTypeCultureVariations = string.Empty; + + if (contentNode.Published) + { + contentNodePublish = "y"; + } + + if (contentType.Variations == ContentVariation.Nothing) + { + contentTypeCultureVariations = "n"; + } + + Assert.Multiple(() => + { + Assert.AreEqual(searchResults.First().Values["__NodeId"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__IndexType"], "content"); + Assert.AreEqual(searchResults.First().Values["__NodeTypeAlias"], contentNode.ContentType.Alias); + Assert.AreEqual(searchResults.First().Values["__Published"], contentNodePublish); + Assert.AreEqual(searchResults.First().Values["id"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__Key"], contentNode.Key.ToString()); + Assert.AreEqual(searchResults.First().Values["parentID"], contentNode.ParentId.ToString()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentNode.Name); + Assert.AreEqual(searchResults.First().Values["__VariesByCulture"], contentTypeCultureVariations); + Assert.AreEqual(searchResults.First().Values["__Icon"], contentNode.ContentType.Icon); + }); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index 381464faf3..3d97f4ede0 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -1,4 +1,5 @@ using System.Data; +using Examine; using Examine.Lucene.Providers; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -25,6 +26,8 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest protected IRuntimeState RunningRuntimeState { get; } = Mock.Of(x => x.Level == RuntimeLevel.Run); + protected IExamineManager ExamineManager => GetRequiredService(); + protected override void ConfigureTestServices(IServiceCollection services) => services.AddSingleton(); @@ -113,6 +116,43 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest return new DisposableWrapper(syncMode, index, luceneDir); } + private AutoResetEvent indexingHandle = new(false); + + protected async Task ExecuteAndWaitForIndexing(Action indexUpdatingAction, string indexName) => + await ExecuteAndWaitForIndexing( + () => + { + indexUpdatingAction(); + return null; + }, indexName); + + /// + /// Performs and action and waits for the specified index to be done indexing. + /// + /// The action that causes the index to be updated. + /// The name of the index to wait for rebuild. + /// The type returned from the action. + /// The result of the action. + protected async Task ExecuteAndWaitForIndexing (Func indexUpdatingAction, string indexName) + { + // Set up an action to release the handle when the index is populated. + if (ExamineManager.TryGetIndex(indexName, out IIndex index) is false) + { + throw new InvalidOperationException($"Could not find index: {indexName}"); + } + + index.IndexOperationComplete += (_, _) => + { + indexingHandle.Set(); + }; + + // Perform the action, and wait for the handle to be freed, meaning the index is done populating. + var result = indexUpdatingAction(); + await indexingHandle.WaitOneAsync(); + + return result; + } + private class DisposableWrapper : IDisposable { private readonly IDisposable[] _disposables; @@ -127,4 +167,10 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest } } } + + protected string GetIndexPath(string indexName) + { + var root = TestContext.CurrentContext.TestDirectory.Split("Umbraco.Tests.Integration")[0]; + return Path.Combine(root, "Umbraco.Tests.Integration", "umbraco", "Data", "TEMP", "ExamineIndexes", indexName); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs new file mode 100644 index 0000000000..38eb270b3c --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs @@ -0,0 +1,417 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using Examine; +using Examine.Search; +using Lucene.Net.QueryParsers.Classic; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Examine; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +public class ExamineExternalIndexSearcherTest : IExamineExternalIndexSearcherTest +{ + private readonly AppCaches _appCaches; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IEntityService _entityService; + private readonly IExamineManager _examineManager; + private readonly ILocalizationService _languageService; + private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IUmbracoTreeSearcherFields _treeSearcherFields; + private readonly IUmbracoMapper _umbracoMapper; + + public ExamineExternalIndexSearcherTest( + IExamineManager examineManager, + ILocalizationService languageService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IEntityService entityService, + IUmbracoTreeSearcherFields treeSearcherFields, + AppCaches appCaches, + IUmbracoMapper umbracoMapper, + IPublishedUrlProvider publishedUrlProvider) + { + _examineManager = examineManager; + _languageService = languageService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _entityService = entityService; + _treeSearcherFields = treeSearcherFields; + _appCaches = appCaches; + _umbracoMapper = umbracoMapper; + _publishedUrlProvider = publishedUrlProvider; + } + + public IEnumerable Search( + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, + out long totalFound, + string? searchFrom = null, + bool ignoreUserStartNodes = false) + { + var sb = new StringBuilder(); + + string type; + var indexName = Constants.UmbracoIndexes.ExternalIndexName; + var fields = _treeSearcherFields.GetBackOfficeFields().ToList(); + + ISet fieldsToLoad = new HashSet(_treeSearcherFields.GetBackOfficeFieldsToLoad()); + + // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string + // manipulation for things like start paths, member types, etc... + //if (Examine.ExamineExtensions.TryParseLuceneQuery(query)) + //{ + + //} + + //special GUID check since if a user searches on one specifically we need to escape it + if (Guid.TryParse(query, out Guid g)) + { + query = "\"" + g + "\""; + } + + IUser? currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; + + switch (entityType) + { + case UmbracoEntityTypes.Member: + indexName = Constants.UmbracoIndexes.MembersIndexName; + type = "member"; + fields.AddRange(_treeSearcherFields.GetBackOfficeMembersFields()); + foreach (var field in _treeSearcherFields.GetBackOfficeMembersFieldsToLoad()) + { + fieldsToLoad.Add(field); + } + + if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && + searchFrom.Trim() != "-1") + { + sb.Append("+__NodeTypeAlias:"); + sb.Append(searchFrom); + sb.Append(" "); + } + + break; + case UmbracoEntityTypes.Media: + type = "media"; + fields.AddRange(_treeSearcherFields.GetBackOfficeMediaFields()); + foreach (var field in _treeSearcherFields.GetBackOfficeMediaFieldsToLoad()) + { + fieldsToLoad.Add(field); + } + + var allMediaStartNodes = currentUser != null + ? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches) + : Array.Empty(); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + break; + case UmbracoEntityTypes.Document: + type = "content"; + fields.AddRange(_treeSearcherFields.GetBackOfficeDocumentFields()); + foreach (var field in _treeSearcherFields.GetBackOfficeDocumentFieldsToLoad()) + { + fieldsToLoad.Add(field); + } + + var allContentStartNodes = currentUser != null + ? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches) + : Array.Empty(); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + break; + default: + throw new NotSupportedException("The " + typeof(BackOfficeExamineSearcher) + + " currently does not support searching against object type " + + entityType); + } + + if (!_examineManager.TryGetIndex(indexName, out IIndex? index)) + { + throw new InvalidOperationException("No index found by name " + indexName); + } + + if (!BuildQuery(sb, query, searchFrom, fields, type)) + { + totalFound = 0; + return Enumerable.Empty(); + } + + ISearchResults? result = index.Searcher + .CreateQuery() + .NativeQuery(sb.ToString()) + .SelectFields(fieldsToLoad) + //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested + .Execute(QueryOptions.SkipTake(Convert.ToInt32(pageSize * pageIndex), pageSize)); + + totalFound = result.TotalItemCount; + + return result; + } + + private bool BuildQuery(StringBuilder sb, string query, string? searchFrom, List fields, string type) + { + //build a lucene query: + // the nodeName will be boosted 10x without wildcards + // then nodeName will be matched normally with wildcards + // the rest will be normal without wildcards + + var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList(); + + // the chars [*-_] in the query will mess everything up so let's remove those + // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. + query = Regex.Replace(query, "[\\*]", string.Empty); + query = Regex.Replace(query, "[\\-_]", " "); + + + //check if text is surrounded by single or double quotes, if so, then exact match + var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") + || Regex.IsMatch(query, "^\'.*?\'$"); + + if (surroundedByQuotes) + { + //strip quotes, escape string, the replace again + query = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote); + + query = QueryParserBase.Escape(query); + + //nothing to search + if (searchFrom.IsNullOrWhiteSpace() && query.IsNullOrWhiteSpace()) + { + return false; + } + + //update the query with the query term + if (query.IsNullOrWhiteSpace() == false) + { + //add back the surrounding quotes + query = string.Format("{0}{1}{0}", "\"", query); + + sb.Append("+("); + + AppendNodeNamePhraseWithBoost(sb, query, allLangs); + + foreach (var f in fields) + { + //additional fields normally + sb.Append(f); + sb.Append(": ("); + sb.Append(query); + sb.Append(") "); + } + + sb.Append(") "); + } + } + else + { + var trimmed = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote); + + //nothing to search + if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace()) + { + return false; + } + + //update the query with the query term + if (trimmed.IsNullOrWhiteSpace() == false) + { + query = QueryParserBase.Escape(query); + + var querywords = query.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries); + + sb.Append("+("); + + AppendNodeNameExactWithBoost(sb, query, allLangs); + + AppendNodeNameWithWildcards(sb, querywords, allLangs); + + foreach (var f in fields) + { + var queryWordsReplaced = new string[querywords.Length]; + + // when searching file names containing hyphens we need to replace the hyphens with spaces + if (f.Equals(UmbracoExamineFieldNames.UmbracoFileFieldName)) + { + for (var index = 0; index < querywords.Length; index++) + { + queryWordsReplaced[index] = + querywords[index].Replace("\\-", " ").Replace("_", " ").Trim(" "); + } + } + else + { + queryWordsReplaced = querywords; + } + + //additional fields normally + sb.Append(f); + sb.Append(":"); + sb.Append("("); + foreach (var w in queryWordsReplaced) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + + sb.Append(")"); + sb.Append(" "); + } + + sb.Append(") "); + } + } + + //must match index type + sb.Append("+__IndexType:"); + sb.Append(type); + + return true; + } + + private void AppendNodeNamePhraseWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + } + } + + private void AppendNodeNameExactWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + } + } + + private void AppendNodeNameWithWildcards(StringBuilder sb, string[] querywords, IEnumerable allLangs) + { + //node name normally with wildcards + sb.Append("nodeName:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + + sb.Append(") "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name normally with wildcards + sb.Append($"nodeName_{lang}:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + + sb.Append(") "); + } + } + + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[]? startNodeIds, string? searchFrom, bool ignoreUserStartNodes, IEntityService entityService) + { + if (sb == null) + { + throw new ArgumentNullException(nameof(sb)); + } + + if (entityService == null) + { + throw new ArgumentNullException(nameof(entityService)); + } + + UdiParser.TryParse(searchFrom, true, out Udi? udi); + searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString(); + + TreeEntityPath? entityPath = + int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var searchFromId) && + searchFromId > 0 + ? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault() + : null; + if (entityPath != null) + { + // find... only what's underneath + sb.Append("+__Path:"); + AppendPath(sb, entityPath.Path, false); + sb.Append(" "); + } + else if (startNodeIds?.Length == 0) + { + // make sure we don't find anything + sb.Append("+__Path:none "); + } + else if (startNodeIds?.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction + { + IEnumerable entityPaths = entityService.GetAllPaths(objectType, startNodeIds); + + // for each start node, find the start node, and what's underneath + // +__Path:(-1*,1234 -1*,1234,* -1*,5678 -1*,5678,* ...) + sb.Append("+__Path:("); + var first = true; + foreach (TreeEntityPath ep in entityPaths) + { + if (first) + { + first = false; + } + else + { + sb.Append(" "); + } + + AppendPath(sb, ep.Path, true); + } + + sb.Append(") "); + } + } + + private void AppendPath(StringBuilder sb, string path, bool includeThisNode) + { + path = path.Replace("-", "\\-").Replace(",", "\\,"); + if (includeThisNode) + { + sb.Append(path); + sb.Append(" "); + } + + sb.Append(path); + sb.Append("\\,*"); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs new file mode 100644 index 0000000000..41b66a193c --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs @@ -0,0 +1,137 @@ +using Examine; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.Examine.DependencyInjection; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Infrastructure.Search; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; +using Umbraco.Cms.Web.BackOffice.Security; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class ExamineExternalIndexTests : ExamineBaseTest +{ + private const string ContentName = "TestContent"; + + [SetUp] + public void Setup() + { + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.InternalIndexName)); + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.ExternalIndexName)); + var httpContext = new DefaultHttpContext(); + httpContext.RequestServices = Services; + Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext); + } + + [TearDown] + public void TearDown() + { + // When disposing examine, it does a final write, which ends up locking the file if the indexing is not done yet. So we have this wait to circumvent that. + Thread.Sleep(1500); + // Sometimes we do not dispose all services in time and the test fails because the log file is locked. Resulting in all other tests failing as well + Services.DisposeIfDisposable(); + } + + private IExamineExternalIndexSearcherTest ExamineExternalIndexSearcher => + GetRequiredService(); + + private IContentTypeService ContentTypeService => GetRequiredService(); + + private ContentService ContentService => (ContentService)GetRequiredService(); + + private IUserStore BackOfficeUserStore => + GetRequiredService>(); + + private IBackOfficeSignInManager BackOfficeSignInManager => GetRequiredService(); + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder + .AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddExamineIndexes(); + builder.AddBackOfficeIdentity(); + builder.Services.AddHostedService(); + } + + private IEnumerable ExamineExternalIndexSearch(string query, int pageSize = 20, int pageIndex = 0) => + ExamineExternalIndexSearcher.Search(query, UmbracoEntityTypes.Document, + pageSize, pageIndex, out _, ignoreUserStartNodes: true); + + private async Task SetupUserIdentity(string userId) + { + var identity = + await BackOfficeUserStore.FindByIdAsync(userId, CancellationToken.None); + await BackOfficeSignInManager.SignInAsync(identity, false); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + ContentTypeService.Save(contentType); + + var content = new ContentBuilder() + .WithId(0) + .WithName(ContentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.ExternalIndexName); + + // Act + IEnumerable actual = ExamineExternalIndexSearch(ContentName); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(searchResults.First().Values["nodeName"], ContentName); + } + + [Test] + public async Task Search_Unpublished_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + ContentTypeService.Save(contentType); + + var content = new ContentBuilder() + .WithId(0) + .WithName(ContentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.Save(content), Constants.UmbracoIndexes.ExternalIndexName); + + // Act + IEnumerable actual = ExamineExternalIndexSearch(ContentName); + + // Assert + Assert.AreEqual(0, actual.Count()); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs new file mode 100644 index 0000000000..eba1ebf7bb --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs @@ -0,0 +1,18 @@ +using Examine; +using Umbraco.Cms.Core.Models.ContentEditing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +public interface IExamineExternalIndexSearcherTest +{ + + IEnumerable Search( + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, + out long totalFound, + string? searchFrom = null, + bool ignoreUserStartNodes = false); + +} From da56f16abfa76d9cbb4a0a7f98717222acfd0b4c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 4 Dec 2023 10:58:11 +0100 Subject: [PATCH 087/146] Move helper methods for adding webhooks to extension methods (#15344) * Move adding webhooks to extension methods * Clean up WebhookEventCollectionBuilder * Rename AddAllAvailableWebhooks to AddCmsWebhooks and internalize defaults --- .../UmbracoBuilder.Collections.cs | 8 +- src/Umbraco.Core/Extensions/TypeExtensions.cs | 18 +- .../Webhooks/WebhookEventCollectionBuilder.cs | 237 +-------------- ...WebhookEventCollectionBuilderExtensions.cs | 279 ++++++++++++++++++ 4 files changed, 298 insertions(+), 244 deletions(-) create mode 100644 src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index cd009600ed..b4c0461356 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -1,9 +1,11 @@ using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.ContentApps; using Umbraco.Cms.Core.Dashboards; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DynamicRoot.Origin; +using Umbraco.Cms.Core.DynamicRoot.QuerySteps; using Umbraco.Cms.Core.Editors; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; @@ -15,8 +17,6 @@ using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Sections; using Umbraco.Cms.Core.Snippets; -using Umbraco.Cms.Core.DynamicRoot.QuerySteps; -using Umbraco.Cms.Core.DynamicRoot.Origin; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Tour; using Umbraco.Cms.Core.Trees; @@ -145,7 +145,7 @@ public static partial class UmbracoBuilderExtensions builder.FilterHandlers().Add(() => builder.TypeLoader.GetTypes()); builder.SortHandlers().Add(() => builder.TypeLoader.GetTypes()); builder.ContentIndexHandlers().Add(() => builder.TypeLoader.GetTypes()); - builder.WebhookEvents().AddCoreWebhooks(); + builder.WebhookEvents().AddDefaultWebhooks(); } /// diff --git a/src/Umbraco.Core/Extensions/TypeExtensions.cs b/src/Umbraco.Core/Extensions/TypeExtensions.cs index 1416888e73..6be68ae0a4 100644 --- a/src/Umbraco.Core/Extensions/TypeExtensions.cs +++ b/src/Umbraco.Core/Extensions/TypeExtensions.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using Umbraco.Cms.Core; @@ -132,9 +133,7 @@ public static class TypeExtensions /// true if [is of generic type] [the specified type]; otherwise, false. /// public static bool IsOfGenericType(this Type type, Type genericType) - { - return type.TryGetGenericArguments(genericType, out Type[]? args); - } + => type.TryGetGenericArguments(genericType, out _); /// /// Will find the generic type of the 'type' parameter passed in that is equal to the 'genericType' parameter passed in @@ -143,17 +142,10 @@ public static class TypeExtensions /// /// /// - public static bool TryGetGenericArguments(this Type type, Type genericType, out Type[]? genericArgType) + public static bool TryGetGenericArguments(this Type type, Type genericType, [NotNullWhen(true)] out Type[]? genericArgType) { - if (type == null) - { - throw new ArgumentNullException("type"); - } - - if (genericType == null) - { - throw new ArgumentNullException("genericType"); - } + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(genericType); if (genericType.IsGenericType == false) { diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs index 9ebc7ee13f..c6b5ecc0ea 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs @@ -1,38 +1,20 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Webhooks.Events.Content; -using Umbraco.Cms.Core.Webhooks.Events.DataType; -using Umbraco.Cms.Core.Webhooks.Events.Dictionary; -using Umbraco.Cms.Core.Webhooks.Events.Domain; -using Umbraco.Cms.Core.Webhooks.Events.Language; -using Umbraco.Cms.Core.Webhooks.Events.Media; -using Umbraco.Cms.Core.Webhooks.Events.MediaType; -using Umbraco.Cms.Core.Webhooks.Events.Member; -using Umbraco.Cms.Core.Webhooks.Events.MemberType; -using Umbraco.Cms.Core.Webhooks.Events.Package; -using Umbraco.Cms.Core.Webhooks.Events.PublicAccess; -using Umbraco.Cms.Core.Webhooks.Events.Relation; -using Umbraco.Cms.Core.Webhooks.Events.RelationType; -using Umbraco.Cms.Core.Webhooks.Events.Script; -using Umbraco.Cms.Core.Webhooks.Events.Stylesheet; -using Umbraco.Cms.Core.Webhooks.Events.Template; -using Umbraco.Cms.Core.Webhooks.Events.User; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Webhooks; -public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase +public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase { protected override WebhookEventCollectionBuilder This => this; public override void RegisterWith(IServiceCollection services) { // register the collection - services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection, - ServiceLifetime.Singleton)); + services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection, ServiceLifetime.Singleton)); // register the types RegisterTypes(services); @@ -43,16 +25,16 @@ public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase))) - { - Type[] genericArguments = handlerType.BaseType!.GetGenericArguments(); - - Type? notificationType = - genericArguments.FirstOrDefault(arg => typeof(INotification).IsAssignableFrom(arg)); - - if (notificationType is not null) - { - return notificationType; - } - } - - return null; - } - - public WebhookEventCollectionBuilder AddAllAvailableWebhooks() => - this.AddContentWebhooks() - .AddDataTypeWebhooks() - .AddDictionaryWebhooks() - .AddDomainWebhooks() - .AddLanguageWebhooks() - .AddMediaWebhooks() - .AddMemberWebhooks() - .AddMemberTypeWebhooks() - .AddPackageWebhooks() - .AddPublicAccessWebhooks() - .AddRelationWebhooks() - .AddScriptWebhooks() - .AddStylesheetWebhooks() - .AddTemplateWebhooks() - .AddUserWebhooks(); - - public WebhookEventCollectionBuilder AddContentWebhooks() - { - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddCoreWebhooks() - { - Append(); - Append(); - Append(); - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddDataTypeWebhooks() - { - Append(); - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddDictionaryWebhooks() - { - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddDomainWebhooks() - { - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddLanguageWebhooks() - { - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddMediaWebhooks() - { - // Even though these two are in the AddCoreWebhooks() - // The job of the CollectionBuilder should be removing duplicates - // Would allow someone to use .AddCoreWebhooks().AddMediaWebhooks() - // Or if they explicitly they could skip over CoreWebHooks and just add this perhaps - Append(); - Append(); - - Append(); - Append(); - Append(); - - Append(); - Append(); - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddMemberWebhooks() - { - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddMemberTypeWebhooks() - { - Append(); - Append(); - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddPackageWebhooks() - { - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddPublicAccessWebhooks() - { - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddRelationWebhooks() - { - Append(); - Append(); - - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddScriptWebhooks() - { - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddStylesheetWebhooks() - { - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddTemplateWebhooks() - { - Append(); - Append(); - - Append(); - Append(); - return this; - } - - public WebhookEventCollectionBuilder AddUserWebhooks() - { - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - Append(); - return this; - } + => handlerType.TryGetGenericArguments(typeof(INotificationAsyncHandler<>), out Type[]? genericArguments) + ? genericArguments.FirstOrDefault(arg => typeof(INotification).IsAssignableFrom(arg)) + : null; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs new file mode 100644 index 0000000000..0592ceee40 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs @@ -0,0 +1,279 @@ +using Umbraco.Cms.Core.Webhooks; +using Umbraco.Cms.Core.Webhooks.Events.Content; +using Umbraco.Cms.Core.Webhooks.Events.DataType; +using Umbraco.Cms.Core.Webhooks.Events.Dictionary; +using Umbraco.Cms.Core.Webhooks.Events.Domain; +using Umbraco.Cms.Core.Webhooks.Events.Language; +using Umbraco.Cms.Core.Webhooks.Events.Media; +using Umbraco.Cms.Core.Webhooks.Events.MediaType; +using Umbraco.Cms.Core.Webhooks.Events.Member; +using Umbraco.Cms.Core.Webhooks.Events.MemberType; +using Umbraco.Cms.Core.Webhooks.Events.Package; +using Umbraco.Cms.Core.Webhooks.Events.PublicAccess; +using Umbraco.Cms.Core.Webhooks.Events.Relation; +using Umbraco.Cms.Core.Webhooks.Events.RelationType; +using Umbraco.Cms.Core.Webhooks.Events.Script; +using Umbraco.Cms.Core.Webhooks.Events.Stylesheet; +using Umbraco.Cms.Core.Webhooks.Events.Template; +using Umbraco.Cms.Core.Webhooks.Events.User; + +namespace Umbraco.Cms.Core.DependencyInjection; + +public static class WebhookEventCollectionBuilderExtensions +{ + internal static WebhookEventCollectionBuilder AddDefaultWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append() + .Append(); + + /// + /// Adds all available CMS webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddCmsWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .AddContentWebhooks() + .AddDataTypeWebhooks() + .AddDictionaryWebhooks() + .AddDomainWebhooks() + .AddLanguageWebhooks() + .AddMediaWebhooks() + .AddMemberWebhooks() + .AddMemberTypeWebhooks() + .AddPackageWebhooks() + .AddPublicAccessWebhooks() + .AddRelationWebhooks() + .AddScriptWebhooks() + .AddStylesheetWebhooks() + .AddTemplateWebhooks() + .AddUserWebhooks(); + + /// + /// Adds the content webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddContentWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + /// + /// Adds the data type webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddDataTypeWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append(); + + /// + /// Adds the dictionary webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddDictionaryWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append(); + + /// + /// Adds the domain webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddDomainWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append(); + + /// + /// Adds the language webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddLanguageWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append(); + + /// + /// Adds the media webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddMediaWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + /// + /// Adds the member webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddMemberWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + /// + /// Adds the member type webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddMemberTypeWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append(); + + /// + /// Adds the package webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddPackageWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append(); + + /// + /// Adds the public access webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddPublicAccessWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append(); + + /// + /// Adds the relation webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddRelationWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append(); + + /// + /// Adds the script webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddScriptWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append(); + + /// + /// Adds the stylesheet webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddStylesheetWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append(); + + /// + /// Adds the template webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddTemplateWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append(); + + /// + /// Adds the user webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddUserWebhooks(this WebhookEventCollectionBuilder builder) + => builder + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); +} From c20d96c705bdc45e0f3983debd0bf7c03dacac97 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 4 Dec 2023 13:25:51 +0100 Subject: [PATCH 088/146] Use fluent API for adding webhook events and add document type events (#15345) * Move adding webhooks to extension methods * Clean up WebhookEventCollectionBuilder * Rename AddAllAvailableWebhooks to AddCmsWebhooks and internalize defaults * Use fluent CmsWebhookEventCollectionBuilder for adding CMS specific webhook events * Split media and media type webhook events * Use fluent CmsUserWebhookEventCollectionBuilder for adding CMS user specific webhook events * Rename extension methods and add onlyDefault parameter * Remove duplicate UserForgotPasswordRequestedWebhookEvent * Add document type webhook events and use fluent builders for content and content types * Use fluent WebhookEventCollectionBuilderCmsMember for adding member/member group webhook events * Use fluent WebhookEventCollectionBuilderCmsTemplate for template/partial view webhook events * Use fluent WebhookEventCollectionBuilderCmsFile for file based webhook events (instead of only template/partial view) * Move all webhook events to Umbraco.Cms.Core.Webhooks.Events namespace * Reorder AddFile method and update documentation --- .../UmbracoBuilder.Collections.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 6 +- .../Content/ContentCopiedWebhookEvent.cs | 4 +- .../ContentDeletedBlueprintWebhookEvent.cs | 4 +- .../ContentDeletedVersionsWebhookEvent.cs | 4 +- .../Content/ContentDeletedWebhookEvent.cs | 4 +- .../ContentEmptiedRecycleBinWebhookEvent.cs | 4 +- .../ContentMovedToRecycleBinWebhookEvent.cs | 4 +- .../Content/ContentMovedWebhookEvent.cs | 4 +- .../Content/ContentPublishedWebhookEvent.cs | 4 +- .../Events/Content/ContentRolledBack.cs | 4 +- .../ContentSavedBlueprintWebhookEvent.cs | 4 +- .../Content/ContentSavedWebhookEvent.cs | 4 +- .../Content/ContentSortedWebhookEvent.cs | 4 +- .../Content/ContentUnpublishedWebhookEvent.cs | 4 +- .../DocumentTypeChangedWebhookEvent.cs | 25 ++ .../DocumentTypeDeletedWebhookEvent.cs | 25 ++ .../DocumentTypeMovedWebhookEvent.cs | 25 ++ .../DocumentTypeSavedWebhookEvent.cs | 25 ++ .../MediaTypeChangedWebhookEvent.cs | 4 +- .../MediaTypeDeletedWebhookEvent.cs | 2 +- .../MediaTypeMovedWebhookEvent.cs | 2 +- .../MediaTypeSavedWebhookEvent.cs | 2 +- .../MemberTypeChangedWebhookEvent.cs | 4 +- .../MemberTypeDeletedWebhookEvent.cs | 2 +- .../MemberTypeMovedWebhookEvent.cs | 2 +- .../MemberTypeSavedWebhookEvent.cs | 2 +- .../DataType/DataTypeDeletedWebhookEvent.cs | 4 +- .../DataType/DataTypeMovedWebhookEvent.cs | 2 +- .../DataType/DataTypeSavedWebhookEvent.cs | 2 +- .../DictionaryItemDeletedWebhookEvent.cs | 4 +- .../DictionaryItemSavedWebhookEvent.cs | 4 +- .../Domain/DomainDeletedWebhookEvent.cs | 4 +- .../Events/Domain/DomainSavedWebhookEvent.cs | 4 +- .../PartialViewDeletedWebhookEvent.cs | 4 +- .../PartialViewSavedWebhookEvent.cs | 4 +- .../ScriptDeletedWebhookEvent.cs | 4 +- .../ScriptSavedWebhookEvent.cs | 4 +- .../StylesheetDeletedWebhookEvent.cs | 4 +- .../StylesheetSavedWebhookEvent.cs | 4 +- .../TemplateDeletedWebhookEvent.cs | 4 +- .../TemplateSavedWebhookEvent.cs | 18 +- .../Language/LanguageDeletedWebhookEvent.cs | 4 +- .../Language/LanguageSavedWebhookEvent.cs | 4 +- .../Events/Media/MediaDeletedWebhookEvent.cs | 4 +- .../MediaEmptiedRecycleBinWebhookEvent.cs | 4 +- .../MediaMovedToRecycleBinWebhookEvent.cs | 4 +- .../Events/Media/MediaMovedWebhookEvent.cs | 4 +- .../Events/Media/MediaSavedWebhookEvent.cs | 4 +- .../Member/AssignedMemberRolesWebhookEvent.cs | 4 +- .../Member/ExportedMemberWebhookEvent.cs | 2 +- .../Member/MemberDeletedWebhookEvent.cs | 2 +- .../Member/MemberGroupDeletedWebhookEvent.cs | 4 +- .../Member/MemberGroupSavedWebhookEvent.cs | 2 +- .../Events/Member/MemberSavedWebhookEvent.cs | 2 +- .../Member/RemovedMemberRolesWebhookEvent.cs | 2 +- .../Package/ImportedPackageWebhookEvent.cs | 4 +- .../PublicAccessEntryDeletedWebhookEvent.cs | 4 +- .../PublicAccessEntrySavedWebhookEvent.cs | 4 +- .../Relation/RelationDeletedWebhookEvent.cs | 4 +- .../Relation/RelationSavedWebhookEvent.cs | 4 +- .../RelationTypeDeletedWebhookEvent.cs | 4 +- .../RelationTypeSavedWebhookEvent.cs | 4 +- ...ssignedUserGroupPermissionsWebhookEvent.cs | 4 +- .../Events/User/UserDeletedWebhookEvent.cs | 2 +- ...UserForgotPasswordRequestedWebhookEvent.cs | 2 +- ...rForgottenPasswordRequestedWebhookEvent.cs | 22 - .../User/UserGroupDeletedWebhookEvent.cs | 2 +- .../Events/User/UserGroupSavedWebhookEvent.cs | 2 +- .../Events/User/UserLockedWebhookEvent.cs | 2 +- .../User/UserLoginFailedWebhookEvent.cs | 2 +- ...erLoginRequiresVerificationWebhookEvent.cs | 2 +- .../User/UserLoginSuccessWebhookEvent.cs | 2 +- .../User/UserLogoutSuccessWebhookEvent.cs | 2 +- .../User/UserPasswordChangedWebhookEvent.cs | 2 +- .../User/UserPasswordResetWebhookEvent.cs | 2 +- .../Events/User/UserSavedWebhookEvent.cs | 2 +- .../UserTwoFactorRequestedWebhookEvent.cs | 2 +- .../Events/User/UserUnlockedWebhookEvent.cs | 2 +- ...ntCollectionBuilderCmsContentExtensions.cs | 65 +++ ...llectionBuilderCmsContentTypeExtensions.cs | 65 +++ ...hookEventCollectionBuilderCmsExtensions.cs | 407 ++++++++++++++++++ ...EventCollectionBuilderCmsFileExtensions.cs | 74 ++++ ...entCollectionBuilderCmsMemberExtensions.cs | 59 +++ ...EventCollectionBuilderCmsUserExtensions.cs | 81 ++++ ...WebhookEventCollectionBuilderExtensions.cs | 299 ++----------- 86 files changed, 1024 insertions(+), 404 deletions(-) create mode 100644 src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeChangedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeDeletedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeMovedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeSavedWebhookEvent.cs rename src/Umbraco.Core/Webhooks/Events/{MediaType => ContentType}/MediaTypeChangedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{MediaType => ContentType}/MediaTypeDeletedWebhookEvent.cs (94%) rename src/Umbraco.Core/Webhooks/Events/{MediaType => ContentType}/MediaTypeMovedWebhookEvent.cs (94%) rename src/Umbraco.Core/Webhooks/Events/{MediaType => ContentType}/MediaTypeSavedWebhookEvent.cs (94%) rename src/Umbraco.Core/Webhooks/Events/{MemberType => ContentType}/MemberTypeChangedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{MemberType => ContentType}/MemberTypeDeletedWebhookEvent.cs (94%) rename src/Umbraco.Core/Webhooks/Events/{MemberType => ContentType}/MemberTypeMovedWebhookEvent.cs (94%) rename src/Umbraco.Core/Webhooks/Events/{MemberType => ContentType}/MemberTypeSavedWebhookEvent.cs (93%) rename src/Umbraco.Core/Webhooks/Events/{Template => File}/PartialViewDeletedWebhookEvent.cs (90%) rename src/Umbraco.Core/Webhooks/Events/{Template => File}/PartialViewSavedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{Script => File}/ScriptDeletedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{Script => File}/ScriptSavedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{Stylesheet => File}/StylesheetDeletedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{Stylesheet => File}/StylesheetSavedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{Template => File}/TemplateDeletedWebhookEvent.cs (89%) rename src/Umbraco.Core/Webhooks/Events/{Template => File}/TemplateSavedWebhookEvent.cs (70%) delete mode 100644 src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs create mode 100644 src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs create mode 100644 src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs create mode 100644 src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs create mode 100644 src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs create mode 100644 src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs create mode 100644 src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index b4c0461356..e008365709 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -145,7 +145,7 @@ public static partial class UmbracoBuilderExtensions builder.FilterHandlers().Add(() => builder.TypeLoader.GetTypes()); builder.SortHandlers().Add(() => builder.TypeLoader.GetTypes()); builder.ContentIndexHandlers().Add(() => builder.TypeLoader.GetTypes()); - builder.WebhookEvents().AddDefaultWebhooks(); + builder.WebhookEvents().AddCms(true); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8aebb53f60..24a0663d83 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -11,11 +11,11 @@ - + - - + + diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs index 3d6f0163d7..5651a65c8c 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Copied", Constants.WebhookEvents.Types.Content)] public class ContentCopiedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs index 996b8abd15..a17f2eb020 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Template [Blueprint] Deleted", Constants.WebhookEvents.Types.Content)] public class ContentDeletedBlueprintWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs index c543bfe3ca..e691d13792 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Versions Deleted", Constants.WebhookEvents.Types.Content)] public class ContentDeletedVersionsWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs index 1282822995..00e9f3cf72 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Deleted", Constants.WebhookEvents.Types.Content)] public class ContentDeletedWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs index 8670d23c49..e37455da5e 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Recycle Bin Emptied", Constants.WebhookEvents.Types.Content)] public class ContentEmptiedRecycleBinWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs index 6ed74d8c66..e227ccfa08 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Moved to Recycle Bin", Constants.WebhookEvents.Types.Content)] public class ContentMovedToRecycleBinWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs index 46d487c499..7cbc5ff244 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Moved", Constants.WebhookEvents.Types.Content)] public class ContentMovedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs index 5847fb450e..952ae419d0 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Published", Constants.WebhookEvents.Types.Content)] public class ContentPublishedWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs index f38ff571f9..577909d96b 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Rolled Back", Constants.WebhookEvents.Types.Content)] public class ContentRolledBackWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs index 40630b453f..d830963d6e 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Template [Blueprint] Saved", Constants.WebhookEvents.Types.Content)] public class ContentSavedBlueprintWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs index fac16de822..c8174371cc 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Saved", Constants.WebhookEvents.Types.Content)] public class ContentSavedWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs index d2a95028e2..e98b43580c 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Sorted", Constants.WebhookEvents.Types.Content)] public class ContentSortedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs index 81245f9b75..d9d38feff9 100644 --- a/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Content; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Content Unpublished", Constants.WebhookEvents.Types.Content)] public class ContentUnpublishedWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeChangedWebhookEvent.cs new file mode 100644 index 0000000000..e325030cc6 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeChangedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events; + +[WebhookEvent("Document Type Changed")] +public class DocumentTypeChangedWebhookEvent : WebhookEventBase +{ + public DocumentTypeChangedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "documentTypeChanged"; + + public override object? ConvertNotificationToRequestPayload(ContentTypeChangedNotification notification) + => notification.Changes; +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeDeletedWebhookEvent.cs new file mode 100644 index 0000000000..4919e5a255 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events; + +[WebhookEvent("Document Type Deleted")] +public class DocumentTypeDeletedWebhookEvent : WebhookEventBase +{ + public DocumentTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "documentTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(ContentTypeDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeMovedWebhookEvent.cs new file mode 100644 index 0000000000..d1688f133c --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeMovedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events; + +[WebhookEvent("Document Type Moved")] +public class DocumentTypeMovedWebhookEvent : WebhookEventBase +{ + public DocumentTypeMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "documentTypeMoved"; + + public override object? ConvertNotificationToRequestPayload(ContentTypeMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeSavedWebhookEvent.cs new file mode 100644 index 0000000000..90a7beafd9 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/DocumentTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events; + +[WebhookEvent("Document Type Saved")] +public class DocumentTypeSavedWebhookEvent : WebhookEventBase +{ + public DocumentTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "documentTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(ContentTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeChangedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeChangedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeChangedWebhookEvent.cs index 23decd0032..cbe5aeba90 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeChangedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeChangedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Type Changed")] public class MediaTypeChangedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeDeletedWebhookEvent.cs similarity index 94% rename from src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeDeletedWebhookEvent.cs index 7b46967d06..e025bfa80d 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeDeletedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Type Deleted")] public class MediaTypeDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeMovedWebhookEvent.cs similarity index 94% rename from src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeMovedWebhookEvent.cs index ae2a35995e..9d59d0eb44 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeMovedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Type Moved")] public class MediaTypeMovedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeSavedWebhookEvent.cs similarity index 94% rename from src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeSavedWebhookEvent.cs index 818b38f71b..4ef48fde91 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MediaTypeSavedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Type Saved")] public class MediaTypeSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeChangedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeChangedWebhookEvent.cs index 68c4424e48..380bdd59bb 100644 --- a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeChangedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Type Changed")] public class MemberTypeChangedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeDeletedWebhookEvent.cs similarity index 94% rename from src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeDeletedWebhookEvent.cs index 005143826c..35289588af 100644 --- a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeDeletedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Type Deleted")] public class MemberTypeDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeMovedWebhookEvent.cs similarity index 94% rename from src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeMovedWebhookEvent.cs index ddef29a0c2..42c38d644b 100644 --- a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeMovedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Type Moved")] public class MemberTypeMovedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeSavedWebhookEvent.cs similarity index 93% rename from src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeSavedWebhookEvent.cs index db4bcf8ad4..81f024c1df 100644 --- a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/ContentType/MemberTypeSavedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Type Saved")] public class MemberTypeSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs index b121c6f394..fe419ba744 100644 --- a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.DataType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Data Type Deleted")] public class DataTypeDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs index 03c0954305..280be11751 100644 --- a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.DataType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Data Type Moved")] public class DataTypeMovedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs index a6e5afe189..70cb70f573 100644 --- a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.DataType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Data Type Saved")] public class DataTypeSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs index fa3c95cea5..0a415563c5 100644 --- a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Dictionary; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Dictionary Item Deleted")] public class DictionaryItemDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs index 7edd4e7849..f08569b39e 100644 --- a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Dictionary; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Dictionary Item Saved")] public class DictionaryItemSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs index 30605a28de..884dff5705 100644 --- a/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Domain; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Domain Deleted")] public class DomainDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs index 5cd80f743b..841bc9a55d 100644 --- a/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Domain; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Domain Saved")] public class DomainSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/PartialViewDeletedWebhookEvent.cs similarity index 90% rename from src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/PartialViewDeletedWebhookEvent.cs index 1f3fc756fb..c6058a63a8 100644 --- a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/PartialViewDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Template; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Partial View Deleted")] public class PartialViewDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/PartialViewSavedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/PartialViewSavedWebhookEvent.cs index 224d71c837..342e95bc20 100644 --- a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/PartialViewSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Template; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Partial View Saved")] public class PartialViewSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/ScriptDeletedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/ScriptDeletedWebhookEvent.cs index 1dad592cb1..b8fb9f4ced 100644 --- a/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/ScriptDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Script; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Script Deleted")] public class ScriptDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/ScriptSavedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/ScriptSavedWebhookEvent.cs index 4d026b9f6c..3e070f9790 100644 --- a/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/ScriptSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Script; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Script Saved")] public class ScriptSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/StylesheetDeletedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/StylesheetDeletedWebhookEvent.cs index 5214dc39cb..0996d92b00 100644 --- a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/StylesheetDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Stylesheet; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Stylesheet Deleted")] public class StylesheetDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/StylesheetSavedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/StylesheetSavedWebhookEvent.cs index a135f70131..f2f05bc557 100644 --- a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/StylesheetSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Stylesheet; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Stylesheet Saved")] public class StylesheetSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/TemplateDeletedWebhookEvent.cs similarity index 89% rename from src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/TemplateDeletedWebhookEvent.cs index 2e18dd9932..67274b7e6c 100644 --- a/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/TemplateDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Template; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Template Deleted")] public class TemplateDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/File/TemplateSavedWebhookEvent.cs similarity index 70% rename from src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/File/TemplateSavedWebhookEvent.cs index 3950e7e370..d45ddbf9ca 100644 --- a/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/File/TemplateSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Template; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Template Saved")] public class TemplateSavedWebhookEvent : WebhookEventBase @@ -22,12 +22,12 @@ public class TemplateSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs index ccc52f4245..43ce2a183d 100644 --- a/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Language; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Language Saved")] public class LanguageSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs index 659917652d..c51d63d094 100644 --- a/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Media; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Deleted", Constants.WebhookEvents.Types.Media)] public class MediaDeletedWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs index 29d0e44e46..91b51dea34 100644 --- a/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Media; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Recycle Bin Emptied", Constants.WebhookEvents.Types.Media)] public class MediaEmptiedRecycleBinWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs index 1bd05c6d7b..e9411a56f4 100644 --- a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Media; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Moved to Recycle Bin", Constants.WebhookEvents.Types.Media)] public class MediaMovedToRecycleBinWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs index 60b235f4cc..7c405baf84 100644 --- a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Media; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Moved", Constants.WebhookEvents.Types.Media)] public class MediaMovedWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs index 6c68e8edb1..c744f0c427 100644 --- a/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Media; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Media Saved", Constants.WebhookEvents.Types.Media)] public class MediaSavedWebhookEvent : WebhookEventContentBase diff --git a/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs index 4812bdcc73..002aebbc1b 100644 --- a/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Member; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Roles Assigned")] public class AssignedMemberRolesWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs index 3f72587c8f..0ab92a015e 100644 --- a/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Member; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Exported")] public class ExportedMemberWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs index c29e22003f..06d05514c0 100644 --- a/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Member; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Deleted")] public class MemberDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs index ab41dfed07..bcbefd0b2c 100644 --- a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Member; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Group Deleted")] public class MemberGroupDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs index 19d3e7d4b2..2759764f10 100644 --- a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Member; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Group Saved")] public class MemberGroupSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs index edb82cb518..044386eb1a 100644 --- a/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Member; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Saved")] public class MemberSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs index 8925b24059..873918ec8a 100644 --- a/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Member; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Member Roles Removed")] public class RemovedMemberRolesWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs index 8495dde4c2..b099469910 100644 --- a/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Package; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Package Imported")] public class ImportedPackageWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs index 00f5a2ca86..f71fddb63b 100644 --- a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.PublicAccess; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Public Access Entry Deleted")] public class PublicAccessEntryDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs index 3977db2832..814f33059b 100644 --- a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.PublicAccess; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Public Access Entry Saved")] public class PublicAccessEntrySavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs index 2ade4bd08e..08b9106c5d 100644 --- a/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Relation; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Relation Deleted")] public class RelationDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs index 4cc775aa21..7a28a16615 100644 --- a/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.Relation; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Relation Saved")] public class RelationSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs index 41f9619a41..b50e146552 100644 --- a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.RelationType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Relation Type Deleted")] public class RelationTypeDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs index 915e95c6cb..b665b2479a 100644 --- a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.RelationType; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Relation Type Saved")] public class RelationTypeSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs index 1b8b1002bc..1d07e54337 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Group Permissions Assigned")] public class AssignedUserGroupPermissionsWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs index 3d7a6b2afa..944d5d1cde 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Deleted")] public class UserDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs index 0426d7a1e6..cfe388c97c 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Forgot Password Requested")] public class UserForgotPasswordRequestedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs deleted file mode 100644 index 7bd15f5a51..0000000000 --- a/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Sync; - -namespace Umbraco.Cms.Core.Webhooks.Events.User; - -[WebhookEvent("User Forgotten Password Requested")] -public class UserForgottenPasswordRequestedWebhookEvent : WebhookEventBase -{ - public UserForgottenPasswordRequestedWebhookEvent( - IWebhookFiringService webhookFiringService, - IWebhookService webHookService, - IOptionsMonitor webhookSettings, - IServerRoleAccessor serverRoleAccessor) - : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) - { - } - - public override string Alias => "userForgottenPasswordRequested"; -} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs index c05bacb377..cb34e859ff 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Group Deleted")] public class UserGroupDeletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs index 0bfe438a7b..c4d48c3565 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Group Saved")] public class UserGroupSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs index eb3106687f..f7afabb55f 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Locked")] public class UserLockedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs index e6957d39dc..85a7c41a93 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Login Failed")] public class UserLoginFailedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs index a642827150..7cc2773dbc 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Login Requires Verification")] public class UserLoginRequiresVerificationWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs index 58b98d8e07..651362cd42 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Login Success")] public class UserLoginSuccessWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs index dc0f8d7bb2..93b10e3810 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Logout Success")] public class UserLogoutSuccessWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs index 99c654aba3..77d5dd0375 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Password Changed")] public class UserPasswordChangedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs index 368116df0b..ee642bd434 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Password Reset")] public class UserPasswordResetWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs index ee65e9b2eb..a13ef13884 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Saved")] public class UserSavedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs index afcac294ca..706fa0fbf4 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Two Factor Requested")] public class UserTwoFactorRequestedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs index a6aea62196..1d54188521 100644 --- a/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.User; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("User Unlocked")] public class UserUnlockedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs new file mode 100644 index 0000000000..f8495514d8 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs @@ -0,0 +1,65 @@ +using Umbraco.Cms.Core.Webhooks.Events; +using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderCmsExtensions; + +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for . +/// +public static class WebhookEventCollectionBuilderCmsContentExtensions +{ + /// + /// Adds the content events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsContent AddDefault(this WebhookEventCollectionBuilderCmsContent builder) + { + builder.Builder + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the content blueprint events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsContent AddBlueprint(this WebhookEventCollectionBuilderCmsContent builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the content version events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsContent AddVersion(this WebhookEventCollectionBuilderCmsContent builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs new file mode 100644 index 0000000000..c8b84ef4e1 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs @@ -0,0 +1,65 @@ +using Umbraco.Cms.Core.Webhooks.Events; +using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderCmsExtensions; + +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for . +/// +public static class WebhookEventCollectionBuilderCmsContentTypeExtensions +{ + /// + /// Adds the document type webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsContentType AddDocumentType(this WebhookEventCollectionBuilderCmsContentType builder) + { + builder.Builder + .Append() + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the media type webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsContentType AddMediaType(this WebhookEventCollectionBuilderCmsContentType builder) + { + builder.Builder + .Append() + .Append() + .Append() + .Append(); + + return builder; + } + + + /// + /// Adds the member type webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsContentType AddMemberType(this WebhookEventCollectionBuilderCmsContentType builder) + { + builder.Builder + .Append() + .Append() + .Append() + .Append(); + + return builder; + } +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs new file mode 100644 index 0000000000..ba30984ee3 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs @@ -0,0 +1,407 @@ +using Umbraco.Cms.Core.Webhooks; +using Umbraco.Cms.Core.Webhooks.Events; +using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderExtensions; + +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for . +/// +public static class WebhookEventCollectionBuilderCmsExtensions +{ + /// + /// Adds the default webhook events. + /// + /// The builder. + /// + /// The builder. + /// + /// + /// This is a special subset of webhook events that is added by default. + /// + public static WebhookEventCollectionBuilderCms AddDefault(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append() + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds all available content (including blueprint and version) webhook events. + /// + /// The builder. + /// If set to true only adds the default webhook events instead of all available. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddContent(this WebhookEventCollectionBuilderCms builder, bool onlyDefault = false) + => builder.AddContent(builder => + { + builder.AddDefault(); + + if (onlyDefault is false) + { + builder + .AddBlueprint() + .AddVersion(); + } + }); + + /// + /// Adds content webhook events specified in the action. + /// + /// The builder. + /// The content builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddContent(this WebhookEventCollectionBuilderCms builder, Action contentBuilder) + { + contentBuilder(new WebhookEventCollectionBuilderCmsContent(builder.Builder)); + + return builder; + } + + /// + /// Adds all available content type (document, media and member type) webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddContentType(this WebhookEventCollectionBuilderCms builder) + => builder.AddContentType(builder => + { + builder + .AddDocumentType() + .AddMediaType() + .AddMemberType(); + }); + + /// + /// Adds content type webhook events specified in the action. + /// + /// The builder. + /// The content type builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddContentType(this WebhookEventCollectionBuilderCms builder, Action contentTypeBuilder) + { + contentTypeBuilder(new WebhookEventCollectionBuilderCmsContentType(builder.Builder)); + + return builder; + } + + /// + /// Adds the data type webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddDataType(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the dictionary webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddDictionary(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the domain webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddDomain(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds all available file (partial view, script, stylesheet and template) webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddFile(this WebhookEventCollectionBuilderCms builder) + => builder.AddFile(builder => + { + builder + .AddPartialView() + .AddScript() + .AddStylesheet() + .AddTemplate(); + }); + + /// + /// Adds file webhook events specified in the action. + /// + /// The builder. + /// The file builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddFile(this WebhookEventCollectionBuilderCms builder, Action fileBuilder) + { + fileBuilder(new WebhookEventCollectionBuilderCmsFile(builder.Builder)); + + return builder; + } + + /// + /// Adds the language webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddLanguage(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the media webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddMedia(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append() + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds all available member (including member role and member group) webhook events. + /// + /// The builder. + /// If set to true only adds the default webhook events instead of all available. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddMember(this WebhookEventCollectionBuilderCms builder, bool onlyDefault = false) + => builder.AddMember(builder => + { + builder.AddDefault(); + + if (onlyDefault is false) + { + builder + .AddRoles() + .AddGroup(); + } + }); + + /// + /// Adds member webhook events specified in the action. + /// + /// The builder. + /// The member builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddMember(this WebhookEventCollectionBuilderCms builder, Action memberBuilder) + { + memberBuilder(new WebhookEventCollectionBuilderCmsMember(builder.Builder)); + + return builder; + } + + /// + /// Adds the package webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddPackage(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append(); + + return builder; + } + + /// + /// Adds the public access webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddPublicAccess(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the relation webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddRelation(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the relation type webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddRelationType(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds all available user (including password, login and user group) webhook events. + /// + /// The builder. + /// If set to true only adds the default webhook events instead of all available. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddUser(this WebhookEventCollectionBuilderCms builder, bool onlyDefault = false) + => builder.AddUser(builder => + { + builder.AddDefault(); + + if (onlyDefault is false) + { + builder + .AddPassword() + .AddLogin() + .AddGroup(); + } + }); + + /// + /// Adds user webhook events specified in the action. + /// + /// The builder. + /// The user builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddUser(this WebhookEventCollectionBuilderCms builder, Action userBuilder) + { + userBuilder(new WebhookEventCollectionBuilderCmsUser(builder.Builder)); + + return builder; + } + + /// + /// Fluent for adding CMS content specific webhook events. + /// + public sealed class WebhookEventCollectionBuilderCmsContent + { + internal WebhookEventCollectionBuilderCmsContent(WebhookEventCollectionBuilder builder) + => Builder = builder; + + internal WebhookEventCollectionBuilder Builder { get; } + } + + /// + /// Fluent for adding CMS content type specific webhook events. + /// + public sealed class WebhookEventCollectionBuilderCmsContentType + { + internal WebhookEventCollectionBuilderCmsContentType(WebhookEventCollectionBuilder builder) + => Builder = builder; + + internal WebhookEventCollectionBuilder Builder { get; } + } + + /// + /// Fluent for adding CMS member specific webhook events. + /// + public sealed class WebhookEventCollectionBuilderCmsMember + { + internal WebhookEventCollectionBuilderCmsMember(WebhookEventCollectionBuilder builder) + => Builder = builder; + + internal WebhookEventCollectionBuilder Builder { get; } + } + + /// + /// Fluent for adding CMS file specific webhook events. + /// + public sealed class WebhookEventCollectionBuilderCmsFile + { + internal WebhookEventCollectionBuilderCmsFile(WebhookEventCollectionBuilder builder) + => Builder = builder; + + internal WebhookEventCollectionBuilder Builder { get; } + } + + /// + /// Fluent for adding CMS user specific webhook events. + /// + public sealed class WebhookEventCollectionBuilderCmsUser + { + internal WebhookEventCollectionBuilderCmsUser(WebhookEventCollectionBuilder builder) + => Builder = builder; + + internal WebhookEventCollectionBuilder Builder { get; } + } +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs new file mode 100644 index 0000000000..b229e4d725 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs @@ -0,0 +1,74 @@ +using Umbraco.Cms.Core.Webhooks.Events; +using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderCmsExtensions; + +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for . +/// +public static class WebhookEventCollectionBuilderCmsFileExtensions +{ + /// + /// Adds the partial view webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsFile AddPartialView(this WebhookEventCollectionBuilderCmsFile builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the script webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsFile AddScript(this WebhookEventCollectionBuilderCmsFile builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the stylesheet webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsFile AddStylesheet(this WebhookEventCollectionBuilderCmsFile builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the template webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsFile AddTemplate(this WebhookEventCollectionBuilderCmsFile builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs new file mode 100644 index 0000000000..12c1ab53f1 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs @@ -0,0 +1,59 @@ +using Umbraco.Cms.Core.Webhooks.Events; +using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderCmsExtensions; + +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for . +/// +public static class WebhookEventCollectionBuilderCmsMemberExtensions +{ + /// + /// Adds the member webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsMember AddDefault(this WebhookEventCollectionBuilderCmsMember builder) + { + builder.Builder + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the member role webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsMember AddRoles(this WebhookEventCollectionBuilderCmsMember builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the member group webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsMember AddGroup(this WebhookEventCollectionBuilderCmsMember builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs new file mode 100644 index 0000000000..4e6017c21f --- /dev/null +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs @@ -0,0 +1,81 @@ +using Umbraco.Cms.Core.Webhooks.Events; +using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderCmsExtensions; + +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for . +/// +public static class WebhookEventCollectionBuilderCmsUserExtensions +{ + /// + /// Adds the user events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsUser AddDefault(this WebhookEventCollectionBuilderCmsUser builder) + { + builder.Builder + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the user login events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsUser AddLogin(this WebhookEventCollectionBuilderCmsUser builder) + { + builder.Builder + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the user password events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsUser AddPassword(this WebhookEventCollectionBuilderCmsUser builder) + { + builder.Builder + .Append() + .Append() + .Append(); + + return builder; + } + + /// + /// Adds the user group events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCmsUser AddGroup(this WebhookEventCollectionBuilderCmsUser builder) + { + builder.Builder + .Append() + .Append() + .Append(); + + return builder; + } +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs index 0592ceee40..4205ce8e3b 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs @@ -1,279 +1,70 @@ using Umbraco.Cms.Core.Webhooks; -using Umbraco.Cms.Core.Webhooks.Events.Content; -using Umbraco.Cms.Core.Webhooks.Events.DataType; -using Umbraco.Cms.Core.Webhooks.Events.Dictionary; -using Umbraco.Cms.Core.Webhooks.Events.Domain; -using Umbraco.Cms.Core.Webhooks.Events.Language; -using Umbraco.Cms.Core.Webhooks.Events.Media; -using Umbraco.Cms.Core.Webhooks.Events.MediaType; -using Umbraco.Cms.Core.Webhooks.Events.Member; -using Umbraco.Cms.Core.Webhooks.Events.MemberType; -using Umbraco.Cms.Core.Webhooks.Events.Package; -using Umbraco.Cms.Core.Webhooks.Events.PublicAccess; -using Umbraco.Cms.Core.Webhooks.Events.Relation; -using Umbraco.Cms.Core.Webhooks.Events.RelationType; -using Umbraco.Cms.Core.Webhooks.Events.Script; -using Umbraco.Cms.Core.Webhooks.Events.Stylesheet; -using Umbraco.Cms.Core.Webhooks.Events.Template; -using Umbraco.Cms.Core.Webhooks.Events.User; namespace Umbraco.Cms.Core.DependencyInjection; +/// +/// Extension methods for . +/// public static class WebhookEventCollectionBuilderExtensions { - internal static WebhookEventCollectionBuilder AddDefaultWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append() - .Append(); - /// /// Adds all available CMS webhook events. /// /// The builder. + /// If set to true only adds the default webhook events instead of all available. /// /// The builder. /// - public static WebhookEventCollectionBuilder AddCmsWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .AddContentWebhooks() - .AddDataTypeWebhooks() - .AddDictionaryWebhooks() - .AddDomainWebhooks() - .AddLanguageWebhooks() - .AddMediaWebhooks() - .AddMemberWebhooks() - .AddMemberTypeWebhooks() - .AddPackageWebhooks() - .AddPublicAccessWebhooks() - .AddRelationWebhooks() - .AddScriptWebhooks() - .AddStylesheetWebhooks() - .AddTemplateWebhooks() - .AddUserWebhooks(); + public static WebhookEventCollectionBuilder AddCms(this WebhookEventCollectionBuilder builder, bool onlyDefault = false) + => builder.AddCms(builder => + { + if (onlyDefault) + { + builder.AddDefault(); + } + else + { + builder + .AddContent() + .AddContentType() + .AddDataType() + .AddDictionary() + .AddDomain() + .AddFile() + .AddLanguage() + .AddMedia() + .AddMember() + .AddPackage() + .AddPublicAccess() + .AddRelation() + .AddRelationType() + .AddUser(); + } + }); /// - /// Adds the content webhook events. + /// Adds CMS webhook events specified in the action. /// /// The builder. + /// The CMS builder. /// /// The builder. /// - public static WebhookEventCollectionBuilder AddContentWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); + public static WebhookEventCollectionBuilder AddCms(this WebhookEventCollectionBuilder builder, Action cmsBuilder) + { + cmsBuilder(new WebhookEventCollectionBuilderCms(builder)); + + return builder; + } /// - /// Adds the data type webhook events. + /// Fluent for adding CMS specific webhook events. /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddDataTypeWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append(); + public sealed class WebhookEventCollectionBuilderCms + { + internal WebhookEventCollectionBuilderCms(WebhookEventCollectionBuilder builder) + => Builder = builder; - /// - /// Adds the dictionary webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddDictionaryWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append(); - - /// - /// Adds the domain webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddDomainWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append(); - - /// - /// Adds the language webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddLanguageWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append(); - - /// - /// Adds the media webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddMediaWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - - /// - /// Adds the member webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddMemberWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - - /// - /// Adds the member type webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddMemberTypeWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append(); - - /// - /// Adds the package webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddPackageWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append(); - - /// - /// Adds the public access webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddPublicAccessWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append(); - - /// - /// Adds the relation webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddRelationWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append(); - - /// - /// Adds the script webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddScriptWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append(); - - /// - /// Adds the stylesheet webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddStylesheetWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append(); - - /// - /// Adds the template webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddTemplateWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append(); - - /// - /// Adds the user webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddUserWebhooks(this WebhookEventCollectionBuilder builder) - => builder - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); + internal WebhookEventCollectionBuilder Builder { get; } + } } From 80dcc04df004c61caad7202d683607dc0329d2e8 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Mon, 4 Dec 2023 14:04:57 +0100 Subject: [PATCH 089/146] Add HealthCheckCompletedWebhookEvent (#15337) --- .../HealthCheckCompletedWebhookEvent.cs | 19 +++++++++++++++++++ ...hookEventCollectionBuilderCmsExtensions.cs | 12 ++++++++++++ ...WebhookEventCollectionBuilderExtensions.cs | 3 ++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs diff --git a/src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs new file mode 100644 index 0000000000..0e5fddbf39 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.HealthCheck; + +[WebhookEvent("Health Check Completed")] +public class HealthCheckCompletedWebhookEvent : WebhookEventBase +{ + public HealthCheckCompletedWebhookEvent(IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, IServerRoleAccessor serverRoleAccessor) : base(webhookFiringService, webhookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "healthCheckCompleted"; + + public override object? ConvertNotificationToRequestPayload(HealthCheckCompletedNotification notification) => notification.HealthCheckResults; +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs index ba30984ee3..69ffddd252 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.Webhooks; using Umbraco.Cms.Core.Webhooks.Events; +using Umbraco.Cms.Core.Webhooks.Events.HealthCheck; using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderExtensions; namespace Umbraco.Cms.Core.DependencyInjection; @@ -313,6 +314,17 @@ public static class WebhookEventCollectionBuilderCmsExtensions return builder; } + /// + /// Adds the healthcheck webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilder AddHealthCheck(this WebhookEventCollectionBuilder builder) + => builder + .Append(); + /// /// Adds all available user (including password, login and user group) webhook events. /// diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs index 4205ce8e3b..f5fa205832 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs @@ -38,7 +38,8 @@ public static class WebhookEventCollectionBuilderExtensions .AddPublicAccess() .AddRelation() .AddRelationType() - .AddUser(); + .AddUser() + .AddHealthCheck(); } }); From 8a65c002793eefc942f5e289b247fd2d3df0d01a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 4 Dec 2023 14:11:42 +0100 Subject: [PATCH 090/146] Fix health check webhook event registration --- .../HealthCheckCompletedWebhookEvent.cs | 2 +- ...hookEventCollectionBuilderCmsExtensions.cs | 27 ++++++++++--------- ...WebhookEventCollectionBuilderExtensions.cs | 4 +-- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs index 0e5fddbf39..5fd2ab413b 100644 --- a/src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/HealthCheck/HealthCheckCompletedWebhookEvent.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events.HealthCheck; +namespace Umbraco.Cms.Core.Webhooks.Events; [WebhookEvent("Health Check Completed")] public class HealthCheckCompletedWebhookEvent : WebhookEventBase diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs index 69ffddd252..e98495c866 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs @@ -1,6 +1,5 @@ using Umbraco.Cms.Core.Webhooks; using Umbraco.Cms.Core.Webhooks.Events; -using Umbraco.Cms.Core.Webhooks.Events.HealthCheck; using static Umbraco.Cms.Core.DependencyInjection.WebhookEventCollectionBuilderExtensions; namespace Umbraco.Cms.Core.DependencyInjection; @@ -180,6 +179,21 @@ public static class WebhookEventCollectionBuilderCmsExtensions return builder; } + /// + /// Adds the health check webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms AddHealthCheck(this WebhookEventCollectionBuilderCms builder) + { + builder.Builder + .Append(); + + return builder; + } + /// /// Adds the language webhook events. /// @@ -314,17 +328,6 @@ public static class WebhookEventCollectionBuilderCmsExtensions return builder; } - /// - /// Adds the healthcheck webhook events. - /// - /// The builder. - /// - /// The builder. - /// - public static WebhookEventCollectionBuilder AddHealthCheck(this WebhookEventCollectionBuilder builder) - => builder - .Append(); - /// /// Adds all available user (including password, login and user group) webhook events. /// diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs index f5fa205832..bb7e35120b 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderExtensions.cs @@ -31,6 +31,7 @@ public static class WebhookEventCollectionBuilderExtensions .AddDictionary() .AddDomain() .AddFile() + .AddHealthCheck() .AddLanguage() .AddMedia() .AddMember() @@ -38,8 +39,7 @@ public static class WebhookEventCollectionBuilderExtensions .AddPublicAccess() .AddRelation() .AddRelationType() - .AddUser() - .AddHealthCheck(); + .AddUser(); } }); From 941705b03de1d00676c2889c4438465e2709d55a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 4 Dec 2023 14:34:47 +0100 Subject: [PATCH 091/146] Revert "Grant all users access to the content recycle bin (#14977)" This reverts commit ed006931a49521b1749d322483e67b3396d8a8a8. --- .../Trees/ContentTreeControllerBase.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs index 84e7692d0c..930c51c713 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs @@ -384,8 +384,8 @@ public abstract class ContentTreeControllerBase : TreeController, ITreeNodeContr /// protected sealed override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { - // check if we're rendering the root - if (id == Constants.System.RootString) + //check if we're rendering the root + if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root)) { var altStartId = string.Empty; @@ -394,7 +394,7 @@ public abstract class ContentTreeControllerBase : TreeController, ITreeNodeContr altStartId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId); } - // check if a request has been made to render from a specific start node + //check if a request has been made to render from a specific start node if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.RootString) { @@ -402,17 +402,16 @@ public abstract class ContentTreeControllerBase : TreeController, ITreeNodeContr } ActionResult nodesResult = GetTreeNodesInternal(id, queryStrings); - - if (nodesResult.Result is not null) + if (!(nodesResult.Result is null)) { return nodesResult.Result; } TreeNodeCollection? nodes = nodesResult.Value; - // only render the recycle bin if we are not in dialog and the start id is still the root - // we need to check for the "application" key in the queryString because its value is required here, - // and for some reason when there are no dashboards, this parameter is missing + //only render the recycle bin if we are not in dialog and the start id is still the root + //we need to check for the "application" key in the queryString because its value is required here, + //and for some reason when there are no dashboards, this parameter is missing if (IsDialog(queryStrings) == false && id == Constants.System.RootString && queryStrings.HasKey("application")) { From d5c137583decfa0f92114df9bb229afde0cd4bff Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Wed, 15 Nov 2023 16:13:59 +0100 Subject: [PATCH 092/146] AddDynamicRootStep --- .../UmbracoBuilder.CollectionBuilders.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs index 9d0a245f9f..eda4626490 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Dashboards; +using Umbraco.Cms.Core.DynamicRoot.QuerySteps; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Media; @@ -146,4 +147,16 @@ public static partial class UmbracoBuilderExtensions builder.WebhookEvents().Append(); return builder; } + + /// + /// Add an IDynamicRootQueryStep to the DynamicRootQueryStepCollectionBuilder. + /// + /// + /// + /// + public static IUmbracoBuilder AddDynamicRootStep(this IUmbracoBuilder builder) where T : IDynamicRootQueryStep + { + builder.DynamicRootSteps().Append(); + return builder; + } } From 12a43c24069bd2b8663b50e9e18baec2ede4574b Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 4 Dec 2023 16:00:54 +0100 Subject: [PATCH 093/146] v13: Update deploy types (add setter to `ArtifactDependency.Checksum` and `TryGetValue()`/`GetEntityTypes()` to `IFileTypeCollection`) (#15318) --- src/Umbraco.Core/Deploy/ArtifactBase.cs | 60 +++++++++----- src/Umbraco.Core/Deploy/ArtifactDependency.cs | 24 ++---- .../Deploy/ArtifactDependencyCollection.cs | 43 ++++++---- .../Deploy/ArtifactDependencyMode.cs | 7 +- .../Deploy/ArtifactDeployState.cs | 47 +++++++---- src/Umbraco.Core/Deploy/ArtifactSignature.cs | 12 ++- src/Umbraco.Core/Deploy/Difference.cs | 33 ++++++++ src/Umbraco.Core/Deploy/Direction.cs | 9 +++ src/Umbraco.Core/Deploy/IArtifact.cs | 14 +++- src/Umbraco.Core/Deploy/IArtifactSignature.cs | 57 +++++++------ src/Umbraco.Core/Deploy/IDeployContext.cs | 37 ++++++--- src/Umbraco.Core/Deploy/IFileSource.cs | 72 +++++++++++------ src/Umbraco.Core/Deploy/IFileType.cs | 80 ++++++++++++++++++- .../Deploy/IFileTypeCollection.cs | 40 ++++++++++ src/Umbraco.Core/Deploy/IImageSourceParser.cs | 48 +++++++---- src/Umbraco.Core/Deploy/ILocalLinkParser.cs | 48 +++++++---- src/Umbraco.Core/Deploy/IMacroParser.cs | 45 +++++++---- src/Umbraco.Core/Deploy/IServiceConnector.cs | 1 - .../IUniqueIdentifyingServiceConnector.cs | 24 +++--- 19 files changed, 508 insertions(+), 193 deletions(-) diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index 0d354b65de..994297990d 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -1,49 +1,69 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides a base class to all artifacts. +/// Provides a base class for all artifacts. /// +/// The UDI type. public abstract class ArtifactBase : IArtifact where TUdi : Udi { + private IEnumerable _dependencies; + private readonly Lazy _checksum; + + /// + /// Initializes a new instance of the class. + /// + /// The UDI. + /// The dependencies. protected ArtifactBase(TUdi udi, IEnumerable? dependencies = null) { - Udi = udi ?? throw new ArgumentNullException("udi"); + Udi = udi ?? throw new ArgumentNullException(nameof(udi)); Name = Udi.ToString(); _dependencies = dependencies ?? Enumerable.Empty(); _checksum = new Lazy(GetChecksum); } - private readonly Lazy _checksum; - - private IEnumerable _dependencies; - - protected abstract string GetChecksum(); - + /// Udi IArtifactSignature.Udi => Udi; + /// public TUdi Udi { get; set; } - public string Checksum => _checksum.Value; - - /// - /// Prevents the property from being serialized. - /// - /// - /// Note that we can't use here as that works only on fields, not properties. And we want to avoid using [JsonIgnore] - /// as that would require an external dependency in Umbraco.Cms.Core. - /// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm - /// - public bool ShouldSerializeChecksum() => false; - + /// public IEnumerable Dependencies { get => _dependencies; set => _dependencies = value.OrderBy(x => x.Udi); } + /// + public string Checksum => _checksum.Value; + + /// public string Name { get; set; } + /// public string Alias { get; set; } = string.Empty; + + /// + /// Gets the checksum. + /// + /// + /// The checksum. + /// + protected abstract string GetChecksum(); + + /// + /// Prevents the property from being serialized. + /// + /// + /// Returns false to prevent the property from being serialized. + /// + /// + /// Note that we can't use here as that works only on fields, not properties. And we want to avoid using [JsonIgnore] + /// as that would require an external dependency in Umbraco.Cms.Core. + /// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm + /// + public bool ShouldSerializeChecksum() => false; } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs index 31c8025ddb..80a77740d0 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependency.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs @@ -1,27 +1,17 @@ -using System.Text.Json.Serialization; - namespace Umbraco.Cms.Core.Deploy; /// /// Represents an artifact dependency. /// -/// -/// -/// Dependencies have an order property which indicates whether it must be respected when ordering artifacts. -/// -/// -/// Dependencies have a mode which can be or depending on whether the checksum should match. -/// -/// public class ArtifactDependency { /// /// Initializes a new instance of the class. /// /// The entity identifier of the artifact dependency. - /// A value indicating whether the dependency is ordering. - /// The dependency mode. - /// The checksum. + /// A value indicating whether the dependency must be included when building a dependency tree and ensure the artifact gets deployed in the correct order. + /// A value indicating whether the checksum must match or the artifact just needs to exist. + /// The checksum of the dependency. public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode, string? checksum = null) { Udi = udi; @@ -39,10 +29,10 @@ public class ArtifactDependency public Udi Udi { get; } /// - /// Gets a value indicating whether the dependency is ordering. + /// Gets a value indicating whether the dependency is included when building a dependency tree and gets deployed in the correct order. /// /// - /// true if the dependency is ordering; otherwise, false. + /// true if the dependency is included when building a dependency tree and gets deployed in the correct order; otherwise, false. /// public bool Ordering { get; } @@ -55,10 +45,10 @@ public class ArtifactDependency public ArtifactDependencyMode Mode { get; } /// - /// Gets the checksum. + /// Gets or sets the checksum. /// /// /// The checksum. /// - public string? Checksum { get; } + public string? Checksum { get; set; } } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs index 1be524c86f..7f2b05eaad 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs @@ -3,42 +3,53 @@ using System.Collections; namespace Umbraco.Cms.Core.Deploy; /// -/// Represents a collection of distinct . +/// Represents a collection of distinct . /// -/// The collection cannot contain duplicates and modes are properly managed. +/// +/// The collection cannot contain duplicates and modes are properly managed. +/// public class ArtifactDependencyCollection : ICollection { private readonly Dictionary _dependencies = new(); + /// public int Count => _dependencies.Count; - public IEnumerator GetEnumerator() => _dependencies.Values.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public bool IsReadOnly => false; + /// public void Add(ArtifactDependency item) { - if (_dependencies.ContainsKey(item.Udi)) + if (item.Mode == ArtifactDependencyMode.Exist && + _dependencies.TryGetValue(item.Udi, out ArtifactDependency? existingItem) && + existingItem.Mode == ArtifactDependencyMode.Match) { - ArtifactDependency exist = _dependencies[item.Udi]; - if (item.Mode == ArtifactDependencyMode.Exist || item.Mode == exist.Mode) - { - return; - } + // Don't downgrade dependency mode from Match to Exist + return; } _dependencies[item.Udi] = item; } + /// public void Clear() => _dependencies.Clear(); - public bool Contains(ArtifactDependency item) => - _dependencies.ContainsKey(item.Udi) && - (_dependencies[item.Udi].Mode == item.Mode || _dependencies[item.Udi].Mode == ArtifactDependencyMode.Match); + /// + public bool Contains(ArtifactDependency item) + => _dependencies.TryGetValue(item.Udi, out ArtifactDependency? existingItem) && + // Check whether it has the same or higher dependency mode + (existingItem.Mode == item.Mode || existingItem.Mode == ArtifactDependencyMode.Match); + /// public void CopyTo(ArtifactDependency[] array, int arrayIndex) => _dependencies.Values.CopyTo(array, arrayIndex); - public bool Remove(ArtifactDependency item) => throw new NotSupportedException(); + /// + public bool Remove(ArtifactDependency item) => _dependencies.Remove(item.Udi); - public bool IsReadOnly => false; + /// + public IEnumerator GetEnumerator() => _dependencies.Values.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs index b997b9c759..6ea6a5e2c2 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs @@ -1,17 +1,16 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Indicates the mode of the dependency. +/// Indicates the mode of the dependency. /// public enum ArtifactDependencyMode { /// - /// The dependency must match exactly. + /// The dependency must match exactly. /// Match, - /// - /// The dependency must exist. + /// The dependency must exist. /// Exist, } diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs index e2dd343af1..b6a31f2da2 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -1,27 +1,36 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represent the state of an artifact being deployed. +/// Represent the state of an artifact being deployed. /// public abstract class ArtifactDeployState { /// - /// Gets the artifact. + /// Gets the artifact. /// + /// + /// The artifact. + /// public IArtifact Artifact => GetArtifactAsIArtifact(); /// - /// Gets or sets the service connector in charge of deploying the artifact. + /// Gets or sets the service connector in charge of deploying the artifact. /// + /// + /// The connector. + /// public IServiceConnector? Connector { get; set; } /// - /// Gets or sets the next pass number. + /// Gets or sets the next pass number. /// + /// + /// The next pass. + /// public int NextPass { get; set; } /// - /// Creates a new instance of the class from an artifact and an entity. + /// Creates a new instance of the class from an artifact and an entity. /// /// The type of the artifact. /// The type of the entity. @@ -29,24 +38,28 @@ public abstract class ArtifactDeployState /// The entity. /// The service connector deploying the artifact. /// The next pass number. - /// A deploying artifact. + /// + /// A deploying artifact. + /// public static ArtifactDeployState Create(TArtifact art, TEntity? entity, IServiceConnector connector, int nextPass) where TArtifact : IArtifact => new ArtifactDeployState(art, entity, connector, nextPass); /// - /// Gets the artifact as an . + /// Gets the artifact as an . /// - /// The artifact, as an . + /// + /// The artifact, as an . + /// /// - /// This is because classes that inherit from this class cannot override the Artifact property - /// with a property that specializes the return type, and so they need to 'new' the property. + /// This is because classes that inherit from this class cannot override the Artifact property + /// with a property that specializes the return type, and so they need to 'new' the property. /// protected abstract IArtifact GetArtifactAsIArtifact(); } /// -/// Represent the state of an artifact being deployed. +/// Represent the state of an artifact being deployed. /// /// The type of the artifact. /// The type of the entity. @@ -54,7 +67,7 @@ public class ArtifactDeployState : ArtifactDeployState where TArtifact : IArtifact { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The artifact. /// The entity. @@ -69,13 +82,19 @@ public class ArtifactDeployState : ArtifactDeployState } /// - /// Gets or sets the artifact. + /// Gets or sets the artifact. /// + /// + /// The artifact. + /// public new TArtifact Artifact { get; set; } /// - /// Gets or sets the entity. + /// Gets or sets the entity. /// + /// + /// The entity. + /// public TEntity? Entity { get; set; } /// diff --git a/src/Umbraco.Core/Deploy/ArtifactSignature.cs b/src/Umbraco.Core/Deploy/ArtifactSignature.cs index 3dccddba29..c91897355e 100644 --- a/src/Umbraco.Core/Deploy/ArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/ArtifactSignature.cs @@ -1,17 +1,27 @@ namespace Umbraco.Cms.Core.Deploy; +/// public sealed class ArtifactSignature : IArtifactSignature { + /// + /// Initializes a new instance of the class. + /// + /// The UDI. + /// The checksum. + /// The artifact dependencies. public ArtifactSignature(Udi udi, string checksum, IEnumerable? dependencies = null) { Udi = udi; Checksum = checksum; - Dependencies = dependencies ?? Enumerable.Empty(); + Dependencies = dependencies ?? Array.Empty(); } + /// public Udi Udi { get; } + /// public string Checksum { get; } + /// public IEnumerable Dependencies { get; } } diff --git a/src/Umbraco.Core/Deploy/Difference.cs b/src/Umbraco.Core/Deploy/Difference.cs index d704642a9f..7bc3ff3055 100644 --- a/src/Umbraco.Core/Deploy/Difference.cs +++ b/src/Umbraco.Core/Deploy/Difference.cs @@ -1,7 +1,16 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents a difference between two artifacts. +/// public class Difference { + /// + /// Initializes a new instance of the class. + /// + /// The title. + /// The text. + /// The category. public Difference(string title, string? text = null, string? category = null) { Title = title; @@ -9,12 +18,36 @@ public class Difference Category = category; } + /// + /// Gets or sets the title. + /// + /// + /// The title. + /// public string Title { get; set; } + /// + /// Gets or sets the text. + /// + /// + /// The text. + /// public string? Text { get; set; } + /// + /// Gets or sets the category. + /// + /// + /// The category. + /// public string? Category { get; set; } + /// + /// Converts the difference to a . + /// + /// + /// A that represents the difference. + /// public override string ToString() { var s = Title; diff --git a/src/Umbraco.Core/Deploy/Direction.cs b/src/Umbraco.Core/Deploy/Direction.cs index 30439380f2..a1129e2e4c 100644 --- a/src/Umbraco.Core/Deploy/Direction.cs +++ b/src/Umbraco.Core/Deploy/Direction.cs @@ -1,7 +1,16 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents the direction when replacing an attribute value while parsing macros. +/// public enum Direction { + /// + /// Replacing an attribute value while converting to an artifact. + /// ToArtifact, + /// + /// Replacing an attribute value while converting from an artifact. + /// FromArtifact, } diff --git a/src/Umbraco.Core/Deploy/IArtifact.cs b/src/Umbraco.Core/Deploy/IArtifact.cs index faea983dee..6b041b7f82 100644 --- a/src/Umbraco.Core/Deploy/IArtifact.cs +++ b/src/Umbraco.Core/Deploy/IArtifact.cs @@ -1,11 +1,23 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents an artifact ie an object that can be transfered between environments. +/// Represents an artifact ie an object that can be transfered between environments. /// public interface IArtifact : IArtifactSignature { + /// + /// Gets the name. + /// + /// + /// The name. + /// string Name { get; } + /// + /// Gets the alias. + /// + /// + /// The alias. + /// string? Alias { get; } } diff --git a/src/Umbraco.Core/Deploy/IArtifactSignature.cs b/src/Umbraco.Core/Deploy/IArtifactSignature.cs index f1dd35295f..ca34fdb894 100644 --- a/src/Umbraco.Core/Deploy/IArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/IArtifactSignature.cs @@ -1,46 +1,55 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents the signature of an artifact. +/// Represents the signature of an artifact. /// public interface IArtifactSignature { /// - /// Gets the entity unique identifier of this artifact. + /// Gets the entity unique identifier of this artifact. /// + /// + /// The udi. + /// /// - /// - /// The project identifier is independent from the state of the artifact, its data - /// values, dependencies, anything. It never changes and fully identifies the artifact. - /// - /// - /// What an entity uses as a unique identifier will influence what we can transfer - /// between environments. Eg content type "Foo" on one environment is not necessarily the - /// same as "Foo" on another environment, if guids are used as unique identifiers. What is - /// used should be documented for each entity, along with the consequences of the choice. - /// + /// + /// The project identifier is independent from the state of the artifact, its data + /// values, dependencies, anything. It never changes and fully identifies the artifact. + /// + /// + /// What an entity uses as a unique identifier will influence what we can transfer + /// between environments. Eg content type "Foo" on one environment is not necessarily the + /// same as "Foo" on another environment, if guids are used as unique identifiers. What is + /// used should be documented for each entity, along with the consequences of the choice. + /// /// Udi Udi { get; } /// - /// Gets the checksum of this artifact. + /// Gets the checksum of this artifact. /// + /// + /// The checksum. + /// /// - /// - /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies, - /// but not on their checksums. So the checksum changes when any of the artifact's properties changes, - /// or when the list of dependencies changes. But not if one of these dependencies change. - /// - /// - /// It is assumed that checksum collisions cannot happen ie that no two different artifact's - /// states will ever produce the same checksum, so that if two artifacts have the same checksum then - /// they are identical. - /// + /// + /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies, + /// but not on their checksums. So the checksum changes when any of the artifact's properties changes, + /// or when the list of dependencies changes. But not if one of these dependencies change. + /// + /// + /// It is assumed that checksum collisions cannot happen ie that no two different artifact's + /// states will ever produce the same checksum, so that if two artifacts have the same checksum then + /// they are identical. + /// /// string Checksum { get; } /// - /// Gets the dependencies of this artifact. + /// Gets the dependencies of this artifact. /// + /// + /// The dependencies. + /// IEnumerable Dependencies { get; } } diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs index bdc8fd8d61..f706cbd3ae 100644 --- a/src/Umbraco.Core/Deploy/IDeployContext.cs +++ b/src/Umbraco.Core/Deploy/IDeployContext.cs @@ -1,39 +1,56 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents a deployment context. +/// Represents a deployment context. /// public interface IDeployContext { /// - /// Gets the unique identifier of the deployment. + /// Gets the unique identifier of the deployment. /// + /// + /// The session identifier. + /// Guid SessionId { get; } /// - /// Gets the file source. + /// Gets the file source. /// - /// The file source is used to obtain files from the source environment. + /// + /// The file source. + /// + /// + /// The file source is used to obtain files from the source environment. + /// IFileSource FileSource { get; } /// - /// Gets items. + /// Gets items. /// + /// + /// The items. + /// IDictionary Items { get; } /// - /// Gets the next number in a numerical sequence. + /// Gets the next number in a numerical sequence. /// - /// The next sequence number. - /// Can be used to uniquely number things during a deployment. + /// + /// The next sequence number. + /// + /// + /// Can be used to uniquely number things during a deployment. + /// int NextSeq(); /// - /// Gets item. + /// Gets item. /// /// The type of the item. /// The key of the item. - /// The item with the specified key and type, if any, else null. + /// + /// The item with the specified key and type, if any, else null. + /// T? Item(string key) where T : class; diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs index d3f6ebe770..92eb125d9e 100644 --- a/src/Umbraco.Core/Deploy/IFileSource.cs +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -1,70 +1,86 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents a file source, ie a mean for a target environment involved in a -/// deployment to obtain the content of files being deployed. +/// Represents a file source, ie a mean for a target environment involved in a +/// deployment to obtain the content of files being deployed. /// public interface IFileSource { /// - /// Gets the content of a file as a stream. + /// Gets the content of a file as a stream. /// /// A file entity identifier. - /// A stream with read access to the file content. + /// + /// A stream with read access to the file content. + /// /// - /// Returns null if no content could be read. - /// The caller should ensure that the stream is properly closed/disposed. + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. /// Stream GetFileStream(StringUdi udi); /// - /// Gets the content of a file as a stream. + /// Gets the content of a file as a stream. /// /// A file entity identifier. /// A cancellation token. - /// A stream with read access to the file content. + /// + /// A stream with read access to the file content. + /// /// - /// Returns null if no content could be read. - /// The caller should ensure that the stream is properly closed/disposed. + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. /// Task GetFileStreamAsync(StringUdi udi, CancellationToken token); /// - /// Gets the content of a file as a string. + /// Gets the content of a file as a string. /// /// A file entity identifier. - /// A string containing the file content. - /// Returns null if no content could be read. + /// + /// A string containing the file content. + /// + /// + /// Returns null if no content could be read. + /// string GetFileContent(StringUdi udi); /// - /// Gets the content of a file as a string. + /// Gets the content of a file as a string. /// /// A file entity identifier. /// A cancellation token. - /// A string containing the file content. - /// Returns null if no content could be read. + /// + /// A string containing the file content. + /// + /// + /// Returns null if no content could be read. + /// Task GetFileContentAsync(StringUdi udi, CancellationToken token); /// - /// Gets the length of a file. + /// Gets the length of a file. /// /// A file entity identifier. - /// The length of the file, or -1 if the file does not exist. + /// + /// The length of the file, or -1 if the file does not exist. + /// long GetFileLength(StringUdi udi); /// - /// Gets the length of a file. + /// Gets the length of a file. /// /// A file entity identifier. /// A cancellation token. - /// The length of the file, or -1 if the file does not exist. + /// + /// The length of the file, or -1 if the file does not exist. + /// Task GetFileLengthAsync(StringUdi udi, CancellationToken token); // TODO (V14): Remove obsolete methods and default implementations for GetFiles and GetFilesAsync overloads. /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. /// A collection of file types which can store the files. @@ -72,32 +88,38 @@ public interface IFileSource void GetFiles(IEnumerable udis, IFileTypeCollection fileTypes); /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. - /// A collection of file types which can store the files. /// A flag indicating whether to continue if a file isn't found or to stop and throw a FileNotFoundException. + /// A collection of file types which can store the files. void GetFiles(IEnumerable udis, bool continueOnFileNotFound, IFileTypeCollection fileTypes) #pragma warning disable CS0618 // Type or member is obsolete => GetFiles(udis, fileTypes); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. /// A collection of file types which can store the files. /// A cancellation token. + /// + /// The task object representing the asynchronous operation. + /// [Obsolete("Please use the method overload taking all parameters. This method overload will be removed in Umbraco 14.")] Task GetFilesAsync(IEnumerable udis, IFileTypeCollection fileTypes, CancellationToken token); /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. /// A collection of file types which can store the files. /// A flag indicating whether to continue if a file isn't found or to stop and throw a FileNotFoundException. /// A cancellation token. + /// + /// The task object representing the asynchronous operation. + /// Task GetFilesAsync(IEnumerable udis, IFileTypeCollection fileTypes, bool continueOnFileNotFound, CancellationToken token) #pragma warning disable CS0618 // Type or member is obsolete => GetFilesAsync(udis, fileTypes, token); diff --git a/src/Umbraco.Core/Deploy/IFileType.cs b/src/Umbraco.Core/Deploy/IFileType.cs index 466c87a3ed..dfd80e2c1e 100644 --- a/src/Umbraco.Core/Deploy/IFileType.cs +++ b/src/Umbraco.Core/Deploy/IFileType.cs @@ -1,27 +1,101 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents a deployable file type. +/// public interface IFileType { + /// + /// Gets a value indicating whether the file can be set using a physical path. + /// + /// + /// true if the file can be set using a physical path; otherwise, false. + /// bool CanSetPhysical { get; } + /// + /// Gets the stream. + /// + /// The UDI. + /// + /// The stream. + /// Stream GetStream(StringUdi udi); + /// + /// Gets the stream as an asynchronous operation. + /// + /// The UDI. + /// The cancellation token. + /// + /// The task object representing the asynchronous operation. + /// Task GetStreamAsync(StringUdi udi, CancellationToken token); + /// + /// Gets the checksum stream. + /// + /// The UDI. + /// + /// The checksum stream. + /// Stream GetChecksumStream(StringUdi udi); + /// + /// Gets the file length in bytes or -1 if not found. + /// + /// The UDI. + /// + /// The file length in bytes or -1 if not found. + /// long GetLength(StringUdi udi); + /// + /// Sets the stream. + /// + /// The UDI. + /// The stream. void SetStream(StringUdi udi, Stream stream); + /// + /// Sets the stream as an asynchronous operation. + /// + /// The UDI. + /// The stream. + /// The cancellation token. + /// + /// The task object representing the asynchronous operation. + /// Task SetStreamAsync(StringUdi udi, Stream stream, CancellationToken token); + /// + /// Sets the physical path of the file. + /// + /// The UDI. + /// The physical path. + /// If set to true copies the file instead of moving. void Set(StringUdi udi, string physicalPath, bool copy = false); - // this is not pretty as *everywhere* in Deploy we take care of ignoring - // the physical path and always rely on Core's virtual IFileSystem but - // Cloud wants to add some of these files to Git and needs the path... + /// + /// Gets the physical path or if not found. + /// + /// The UDI. + /// + /// The physical path or if not found. + /// + /// + /// This is not pretty as *everywhere* in Deploy we take care of ignoring + /// the physical path and always rely on the virtual IFileSystem, + /// but Cloud wants to add some of these files to Git and needs the path... + /// string GetPhysicalPath(StringUdi udi); + /// + /// Gets the virtual path or if not found. + /// + /// The UDI. + /// + /// The virtual path or if not found. + /// string GetVirtualPath(StringUdi udi); } diff --git a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs index 2ae2bb4bb9..3fc22192b6 100644 --- a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs +++ b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs @@ -1,8 +1,48 @@ +using System.Diagnostics.CodeAnalysis; + namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents a collection of deployable file types used for each specific entity type. +/// public interface IFileTypeCollection { + /// + /// Gets the for the specified entity type. + /// + /// + /// The . + /// + /// The entity type. + /// + /// The for the specified entity type. + /// IFileType this[string entityType] { get; } + /// + /// Gets the for the specified entity type. + /// + /// The entity type. + /// When this method returns, contains the file type associated with the specified entity type, if the item is found; otherwise, null. + /// + /// true if the file type associated with the specified entity type was found; otherwise, false. + /// + bool TryGetValue(string entityType, [NotNullWhen(true)] out IFileType? fileType); + + /// + /// Determines whether this collection contains a file type for the specified entity type. + /// + /// The entity type. + /// + /// true if this collection contains a file type for the specified entity type; otherwise, false. + /// bool Contains(string entityType); + + /// + /// Gets the entity types. + /// + /// + /// The entity types. + /// + ICollection GetEntityTypes(); } diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs index cbbaa6bc9a..861e15dc1b 100644 --- a/src/Umbraco.Core/Deploy/IImageSourceParser.cs +++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs @@ -1,49 +1,67 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides methods to parse image tag sources in property values. +/// Provides methods to parse image tag sources in property values. /// public interface IImageSourceParser { /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. - /// The parsed value. - /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// + /// The parsed value. + /// + /// + /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ToArtifact(string value, ICollection dependencies); /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. /// The context cache. - /// The parsed value. - /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// + /// The parsed value. + /// + /// + /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// + string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) => ToArtifact(value, dependencies); + => ToArtifact(value, dependencies); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. - /// The parsed value. - /// Turns umb://media/... into /media/.... + /// + /// The parsed value. + /// + /// + /// Turns umb://media/... into /media/.... + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string FromArtifact(string value); /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. /// The context cache. - /// The parsed value. - /// Turns umb://media/... into /media/.... + /// + /// The parsed value. + /// + /// + /// Turns umb://media/... into /media/.... + /// + string FromArtifact(string value, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string FromArtifact(string value, IContextCache contextCache) => FromArtifact(value); + => FromArtifact(value); #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs index d84bf35af1..9273faa054 100644 --- a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs +++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs @@ -1,55 +1,69 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides methods to parse local link tags in property values. +/// Provides methods to parse local link tags in property values. /// public interface ILocalLinkParser { /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. - /// The parsed value. + /// + /// The parsed value. + /// /// - /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the - /// dependencies. + /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the + /// dependencies. /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ToArtifact(string value, ICollection dependencies); /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. /// The context cache. - /// The parsed value. + /// + /// The parsed value. + /// /// - /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the - /// dependencies. + /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the + /// dependencies. /// + string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) => ToArtifact(value, dependencies); + => ToArtifact(value, dependencies); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. - /// The parsed value. - /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// + /// The parsed value. + /// + /// + /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string FromArtifact(string value); /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. /// The context cache. - /// The parsed value. - /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// + /// The parsed value. + /// + /// + /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// + string FromArtifact(string value, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string FromArtifact(string value, IContextCache contextCache) => FromArtifact(value); + => FromArtifact(value); #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Core/Deploy/IMacroParser.cs b/src/Umbraco.Core/Deploy/IMacroParser.cs index 17f06992d5..de14bb528f 100644 --- a/src/Umbraco.Core/Deploy/IMacroParser.cs +++ b/src/Umbraco.Core/Deploy/IMacroParser.cs @@ -1,65 +1,82 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Provides methods to parse macro tags in property values. +/// public interface IMacroParser { /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// Property value. /// A list of dependencies. - /// Parsed value. + /// + /// Parsed value. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ToArtifact(string value, ICollection dependencies); /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// Property value. /// A list of dependencies. /// The context cache. - /// Parsed value. + /// + /// Parsed value. + /// + string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) => ToArtifact(value, dependencies); + => ToArtifact(value, dependencies); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// Artifact property value. - /// Parsed value. + /// + /// Parsed value. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string FromArtifact(string value); /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// Artifact property value. /// The context cache. - /// Parsed value. + /// + /// Parsed value. + /// + string FromArtifact(string value, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string FromArtifact(string value, IContextCache contextCache) => FromArtifact(value); + => FromArtifact(value); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. + /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. /// /// Value to attempt to convert /// Alias of the editor used for the parameter /// Collection to add dependencies to when performing ToArtifact /// Indicates which action is being performed (to or from artifact) - /// Value with converted identifiers + /// + /// Value with converted identifiers + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction); /// - /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. + /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. /// /// Value to attempt to convert /// Alias of the editor used for the parameter /// Collection to add dependencies to when performing ToArtifact /// Indicates which action is being performed (to or from artifact) /// The context cache. - /// Value with converted identifiers + /// + /// Value with converted identifiers + /// string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete => ReplaceAttributeValue(value, editorAlias, dependencies, direction); diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index 84617943c6..e831ed9e08 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -5,7 +5,6 @@ namespace Umbraco.Cms.Core.Deploy; /// /// Connects to an Umbraco service. /// -/// public interface IServiceConnector : IDiscoverable { /// diff --git a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs index c68906bbbf..3178328e03 100644 --- a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs @@ -1,24 +1,26 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides a method to retrieve an artifact's unique identifier. +/// Provides a method to retrieve an artifact's unique identifier. /// /// -/// Artifacts are uniquely identified by their , however they represent -/// elements in Umbraco that may be uniquely identified by another value. For example, -/// a content type is uniquely identified by its alias. If someone creates a new content -/// type, and tries to deploy it to a remote environment where a content type with the -/// same alias already exists, both content types end up having different -/// but the same alias. By default, Deploy would fail and throw when trying to save the -/// new content type (duplicate alias). However, if the connector also implements this -/// interface, the situation can be detected beforehand and reported in a nicer way. +/// Artifacts are uniquely identified by their , however they represent +/// elements in Umbraco that may be uniquely identified by another value. For example, +/// a content type is uniquely identified by its alias. If someone creates a new content +/// type, and tries to deploy it to a remote environment where a content type with the +/// same alias already exists, both content types end up having different +/// but the same alias. By default, Deploy would fail and throw when trying to save the +/// new content type (duplicate alias). However, if the connector also implements this +/// interface, the situation can be detected beforehand and reported in a nicer way. /// public interface IUniqueIdentifyingServiceConnector { /// - /// Gets the unique identifier of the specified artifact. + /// Gets the unique identifier of the specified artifact. /// /// The artifact. - /// The unique identifier. + /// + /// The unique identifier. + /// string GetUniqueIdentifier(IArtifact artifact); } From c598171d855d5c9197001b2d046bfa25eb39a8c5 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 5 Dec 2023 10:33:47 +0100 Subject: [PATCH 094/146] Change `WebhookEventCollectionBuilder` to set collection (#15351) * Change WebhookEventCollectionBuilder to set collection * Fix AddHealthCheck extension method --- .../UmbracoBuilder.CollectionBuilders.cs | 108 ++++++++++++------ .../Webhooks/WebhookEventCollectionBuilder.cs | 2 +- ...ntCollectionBuilderCmsContentExtensions.cs | 26 ++--- ...llectionBuilderCmsContentTypeExtensions.cs | 24 ++-- ...hookEventCollectionBuilderCmsExtensions.cs | 54 ++++----- ...EventCollectionBuilderCmsFileExtensions.cs | 16 +-- ...entCollectionBuilderCmsMemberExtensions.cs | 14 +-- ...EventCollectionBuilderCmsUserExtensions.cs | 30 ++--- 8 files changed, 159 insertions(+), 115 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs index eda4626490..b90a703786 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs @@ -12,91 +12,119 @@ using Umbraco.Cms.Core.Webhooks; namespace Umbraco.Cms.Core.DependencyInjection; /// -/// Contains extensions methods for used for registering content apps. +/// Contains extensions methods for . /// public static partial class UmbracoBuilderExtensions { /// - /// Register a component. + /// Register a component. /// /// The type of the component. /// The builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddComponent(this IUmbracoBuilder builder) - where T : class, IComponent + where T : IComponent { builder.Components().Append(); + return builder; } /// - /// Register a content app. + /// Register a content app. /// /// The type of the content app. /// The builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddContentApp(this IUmbracoBuilder builder) - where T : class, IContentAppFactory + where T : IContentAppFactory { builder.ContentApps().Append(); + return builder; } /// - /// Register a content finder. + /// Register a content finder. /// /// The type of the content finder. /// The builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddContentFinder(this IUmbracoBuilder builder) - where T : class, IContentFinder + where T : IContentFinder { builder.ContentFinders().Append(); + return builder; } /// - /// Register a dashboard. + /// Register a dashboard. /// /// The type of the dashboard. /// The builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddDashboard(this IUmbracoBuilder builder) - where T : class, IDashboard + where T : IDashboard { builder.Dashboards().Add(); + return builder; } /// - /// Register a manifest filter + /// Register a manifest filter. /// /// The type of the manifest filter. /// The Builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddManifestFilter(this IUmbracoBuilder builder) - where T : class, IManifestFilter + where T : IManifestFilter { builder.ManifestFilters().Append(); + return builder; } /// - /// Register a media url provider. + /// Register a media URL provider. /// - /// The type of the media url provider. + /// The type of the media URL provider. /// The builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddMediaUrlProvider(this IUmbracoBuilder builder) - where T : class, IMediaUrlProvider + where T : IMediaUrlProvider { builder.MediaUrlProviders().Append(); + return builder; } /// - /// Register a embed provider. + /// Register a embed provider. /// /// The type of the embed provider. /// The builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddEmbedProvider(this IUmbracoBuilder builder) - where T : class, IEmbedProvider + where T : IEmbedProvider { builder.EmbedProviders().Append(); + return builder; } @@ -105,46 +133,62 @@ public static partial class UmbracoBuilderExtensions /// /// The type of the section. /// The builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddSection(this IUmbracoBuilder builder) - where T : class, ISection + where T : ISection { builder.Sections().Append(); + return builder; } /// - /// Register a url provider. + /// Register a URL provider. /// - /// The type of the url provider. + /// The type of the URL provider. /// The Builder. + /// + /// The builder. + /// public static IUmbracoBuilder AddUrlProvider(this IUmbracoBuilder builder) - where T : class, IUrlProvider + where T : IUrlProvider { builder.UrlProviders().Append(); + return builder; } /// - /// Add an IMapDefinition to the MapDefinitionCollectionBuilder + /// Add a map definition. /// - /// - /// - /// - public static IUmbracoBuilder AddMapDefinition(this IUmbracoBuilder builder) where T : IMapDefinition + /// The type of map definition. + /// The builder. + /// + /// The builder. + /// + public static IUmbracoBuilder AddMapDefinition(this IUmbracoBuilder builder) + where T : IMapDefinition { - builder.WithCollectionBuilder().Add(); + builder.MapDefinitions().Add(); + return builder; } /// - /// Add an IWebhookEvent to the WebhookEventCollectionBuilder. + /// Add a webhook event. /// - /// - /// - /// - public static IUmbracoBuilder AddWebhookEvent(this IUmbracoBuilder builder) where T : IWebhookEvent + /// The type of webhook event. + /// The builder. + /// + /// The builder. + /// + public static IUmbracoBuilder AddWebhookEvent(this IUmbracoBuilder builder) + where T : IWebhookEvent { - builder.WebhookEvents().Append(); + builder.WebhookEvents().Add(); + return builder; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs index c6b5ecc0ea..1cd6f3dae8 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs @@ -7,7 +7,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Webhooks; -public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase +public class WebhookEventCollectionBuilder : SetCollectionBuilderBase { protected override WebhookEventCollectionBuilder This => this; diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs index f8495514d8..97e85704c7 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentExtensions.cs @@ -18,15 +18,15 @@ public static class WebhookEventCollectionBuilderCmsContentExtensions public static WebhookEventCollectionBuilderCmsContent AddDefault(this WebhookEventCollectionBuilderCmsContent builder) { builder.Builder - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(); return builder; } @@ -41,8 +41,8 @@ public static class WebhookEventCollectionBuilderCmsContentExtensions public static WebhookEventCollectionBuilderCmsContent AddBlueprint(this WebhookEventCollectionBuilderCmsContent builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -57,8 +57,8 @@ public static class WebhookEventCollectionBuilderCmsContentExtensions public static WebhookEventCollectionBuilderCmsContent AddVersion(this WebhookEventCollectionBuilderCmsContent builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs index c8b84ef4e1..db87697e2a 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsContentTypeExtensions.cs @@ -18,10 +18,10 @@ public static class WebhookEventCollectionBuilderCmsContentTypeExtensions public static WebhookEventCollectionBuilderCmsContentType AddDocumentType(this WebhookEventCollectionBuilderCmsContentType builder) { builder.Builder - .Append() - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add() + .Add(); return builder; } @@ -36,10 +36,10 @@ public static class WebhookEventCollectionBuilderCmsContentTypeExtensions public static WebhookEventCollectionBuilderCmsContentType AddMediaType(this WebhookEventCollectionBuilderCmsContentType builder) { builder.Builder - .Append() - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add() + .Add(); return builder; } @@ -55,10 +55,10 @@ public static class WebhookEventCollectionBuilderCmsContentTypeExtensions public static WebhookEventCollectionBuilderCmsContentType AddMemberType(this WebhookEventCollectionBuilderCmsContentType builder) { builder.Builder - .Append() - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add() + .Add(); return builder; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs index e98495c866..361891de6a 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs @@ -22,11 +22,11 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddDefault(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append() - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add() + .Add() + .Add(); return builder; } @@ -108,9 +108,9 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddDataType(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add(); return builder; } @@ -125,8 +125,8 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddDictionary(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -141,8 +141,8 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddDomain(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -189,7 +189,7 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddHealthCheck(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append(); + .Add(); return builder; } @@ -204,8 +204,8 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddLanguage(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -220,11 +220,11 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddMedia(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append() - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add() + .Add() + .Add(); return builder; } @@ -275,7 +275,7 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddPackage(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append(); + .Add(); return builder; } @@ -290,8 +290,8 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddPublicAccess(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -306,8 +306,8 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddRelation(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -322,8 +322,8 @@ public static class WebhookEventCollectionBuilderCmsExtensions public static WebhookEventCollectionBuilderCms AddRelationType(this WebhookEventCollectionBuilderCms builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs index b229e4d725..21ff19fd3a 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsFileExtensions.cs @@ -18,8 +18,8 @@ public static class WebhookEventCollectionBuilderCmsFileExtensions public static WebhookEventCollectionBuilderCmsFile AddPartialView(this WebhookEventCollectionBuilderCmsFile builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -34,8 +34,8 @@ public static class WebhookEventCollectionBuilderCmsFileExtensions public static WebhookEventCollectionBuilderCmsFile AddScript(this WebhookEventCollectionBuilderCmsFile builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -50,8 +50,8 @@ public static class WebhookEventCollectionBuilderCmsFileExtensions public static WebhookEventCollectionBuilderCmsFile AddStylesheet(this WebhookEventCollectionBuilderCmsFile builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -66,8 +66,8 @@ public static class WebhookEventCollectionBuilderCmsFileExtensions public static WebhookEventCollectionBuilderCmsFile AddTemplate(this WebhookEventCollectionBuilderCmsFile builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs index 12c1ab53f1..15fd7fdd19 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsMemberExtensions.cs @@ -18,9 +18,9 @@ public static class WebhookEventCollectionBuilderCmsMemberExtensions public static WebhookEventCollectionBuilderCmsMember AddDefault(this WebhookEventCollectionBuilderCmsMember builder) { builder.Builder - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add(); return builder; } @@ -35,8 +35,8 @@ public static class WebhookEventCollectionBuilderCmsMemberExtensions public static WebhookEventCollectionBuilderCmsMember AddRoles(this WebhookEventCollectionBuilderCmsMember builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -51,8 +51,8 @@ public static class WebhookEventCollectionBuilderCmsMemberExtensions public static WebhookEventCollectionBuilderCmsMember AddGroup(this WebhookEventCollectionBuilderCmsMember builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs index 4e6017c21f..e1d9e0307d 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsUserExtensions.cs @@ -18,8 +18,8 @@ public static class WebhookEventCollectionBuilderCmsUserExtensions public static WebhookEventCollectionBuilderCmsUser AddDefault(this WebhookEventCollectionBuilderCmsUser builder) { builder.Builder - .Append() - .Append(); + .Add() + .Add(); return builder; } @@ -34,13 +34,13 @@ public static class WebhookEventCollectionBuilderCmsUserExtensions public static WebhookEventCollectionBuilderCmsUser AddLogin(this WebhookEventCollectionBuilderCmsUser builder) { builder.Builder - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(); return builder; } @@ -55,9 +55,9 @@ public static class WebhookEventCollectionBuilderCmsUserExtensions public static WebhookEventCollectionBuilderCmsUser AddPassword(this WebhookEventCollectionBuilderCmsUser builder) { builder.Builder - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add(); return builder; } @@ -72,9 +72,9 @@ public static class WebhookEventCollectionBuilderCmsUserExtensions public static WebhookEventCollectionBuilderCmsUser AddGroup(this WebhookEventCollectionBuilderCmsUser builder) { builder.Builder - .Append() - .Append() - .Append(); + .Add() + .Add() + .Add(); return builder; } From e7279d2ff0ecaf931be925109c7f9f87a37bdb08 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 5 Dec 2023 11:01:25 +0100 Subject: [PATCH 095/146] Add range filter options to the Delivery API (#15353) * Add range filter options to the Delivery API * Add range filter examples to Swagger docs --- .../SwaggerContentDocumentationFilter.cs | 12 ++- .../Querying/Filters/ContainsFilterBase.cs | 78 +++++++++++++++++++ .../Querying/Filters/CreateDateFilter.cs | 14 ++++ .../Querying/Filters/UpdateDateFilter.cs | 14 ++++ .../ApiContentQueryFilterBuilder.cs | 67 ++++++++++++++++ .../DeliveryApi/FilterOperation.cs | 6 +- 6 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Delivery/Querying/Filters/ContainsFilterBase.cs create mode 100644 src/Umbraco.Cms.Api.Delivery/Querying/Filters/CreateDateFilter.cs create mode 100644 src/Umbraco.Cms.Api.Delivery/Querying/Filters/UpdateDateFilter.cs diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs index 9d938cef41..0d2d35fb23 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs @@ -113,12 +113,20 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi { { "Default filter", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, { - "Filter by content type", + "Filter by content type (equals)", new OpenApiExample { Value = new OpenApiArray { new OpenApiString("contentType:alias1") } } }, { - "Filter by name", + "Filter by name (contains)", new OpenApiExample { Value = new OpenApiArray { new OpenApiString("name:nodeName") } } + }, + { + "Filter by creation date (less than)", + new OpenApiExample { Value = new OpenApiArray { new OpenApiString("createDate<2024-01-01") } } + }, + { + "Filter by update date (greater than or equal)", + new OpenApiExample { Value = new OpenApiArray { new OpenApiString("updateDate>:2023-01-01") } } } }; diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/Filters/ContainsFilterBase.cs b/src/Umbraco.Cms.Api.Delivery/Querying/Filters/ContainsFilterBase.cs new file mode 100644 index 0000000000..1770bfd4b9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Querying/Filters/ContainsFilterBase.cs @@ -0,0 +1,78 @@ +using System.Text.RegularExpressions; +using Umbraco.Cms.Core.DeliveryApi; + +namespace Umbraco.Cms.Api.Delivery.Querying.Filters; + +public abstract class ContainsFilterBase : IFilterHandler +{ + /// + /// The regex to parse the filter query. Must supply two match groups named "operator" and "value", where "operator" + /// contains the filter operator (i.e. ">") and "value" contains the value to filter on. + /// + /// + /// Supported operators: + /// + /// ":" = Is (the filter value equals the index field value) + /// ":!" = IsNot (the filter value does not equal the index field value) + /// ">" = GreaterThan (the filter value is greater than the index field value) + /// ">:" = GreaterThanOrEqual (the filter value is greater than or equal to the index field value) + /// "<" = LessThan (the filter value is less than the index field value) + /// "<:" = LessThanOrEqual (the filter value is less than or equal to the index field value) + /// + /// Range operators (greater than, less than) only work with numeric and date type filters. + /// + protected abstract Regex QueryParserRegex { get; } + + /// + /// The index field name to filter on. + /// + protected abstract string FieldName { get; } + + /// + public bool CanHandle(string query) + => QueryParserRegex.IsMatch(query); + + /// + public FilterOption BuildFilterOption(string filter) + { + GroupCollection groups = QueryParserRegex.Match(filter).Groups; + + if (groups.Count != 3 || groups.ContainsKey("operator") is false || groups.ContainsKey("value") is false) + { + return DefaultFilterOption(); + } + + FilterOperation? filterOperation = ParseFilterOperation(groups["operator"].Value); + if (filterOperation.HasValue is false) + { + return DefaultFilterOption(); + } + + return new FilterOption + { + FieldName = FieldName, + Values = new[] { groups["value"].Value }, + Operator = filterOperation.Value + }; + + FilterOption DefaultFilterOption() + => new FilterOption + { + FieldName = FieldName, + Values = new[] { Guid.NewGuid().ToString() }, + Operator = FilterOperation.Is + }; + } + + private FilterOperation? ParseFilterOperation(string filterOperation) + => filterOperation switch + { + ":" => FilterOperation.Is, + ":!" => FilterOperation.IsNot, + ">" => FilterOperation.GreaterThan, + ">:" => FilterOperation.GreaterThanOrEqual, + "<" => FilterOperation.LessThan, + "<:" => FilterOperation.LessThanOrEqual, + _ => null + }; +} diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/Filters/CreateDateFilter.cs b/src/Umbraco.Cms.Api.Delivery/Querying/Filters/CreateDateFilter.cs new file mode 100644 index 0000000000..725d7d8f89 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Querying/Filters/CreateDateFilter.cs @@ -0,0 +1,14 @@ +using System.Text.RegularExpressions; +using Umbraco.Cms.Api.Delivery.Indexing.Sorts; + +namespace Umbraco.Cms.Api.Delivery.Querying.Filters; + +public sealed partial class CreateDateFilter : ContainsFilterBase +{ + protected override string FieldName => CreateDateSortIndexer.FieldName; + + protected override Regex QueryParserRegex => CreateDateRegex(); + + [GeneratedRegex("createDate(?[><:]{1,2})(?.*)", RegexOptions.IgnoreCase)] + public static partial Regex CreateDateRegex(); +} diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/Filters/UpdateDateFilter.cs b/src/Umbraco.Cms.Api.Delivery/Querying/Filters/UpdateDateFilter.cs new file mode 100644 index 0000000000..9ceb0ca5a1 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Querying/Filters/UpdateDateFilter.cs @@ -0,0 +1,14 @@ +using System.Text.RegularExpressions; +using Umbraco.Cms.Api.Delivery.Indexing.Sorts; + +namespace Umbraco.Cms.Api.Delivery.Querying.Filters; + +public sealed partial class UpdateDateFilter : ContainsFilterBase +{ + protected override string FieldName => UpdateDateSortIndexer.FieldName; + + protected override Regex QueryParserRegex => UpdateDateRegex(); + + [GeneratedRegex("updateDate(?[><:]{1,2})(?.*)", RegexOptions.IgnoreCase)] + public static partial Regex UpdateDateRegex(); +} diff --git a/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQueryFilterBuilder.cs b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQueryFilterBuilder.cs index a54965f116..819ee3aa72 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQueryFilterBuilder.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQueryFilterBuilder.cs @@ -50,6 +50,12 @@ internal sealed class ApiContentQueryFilterBuilder case FilterOperation.DoesNotContain: ApplyContainsFilter(queryOperation.Not(), filterOption.FieldName, values); break; + case FilterOperation.LessThan: + case FilterOperation.LessThanOrEqual: + case FilterOperation.GreaterThan: + case FilterOperation.GreaterThanOrEqual: + ApplyRangeFilter(queryOperation.And(), filterOption.FieldName, values, fieldType, filterOption.Operator); + break; default: continue; } @@ -143,6 +149,67 @@ internal sealed class ApiContentQueryFilterBuilder } } + private void ApplyRangeFilter(IQuery query, string fieldName, string[] values, FieldType fieldType, FilterOperation filterOperation) + { + switch (fieldType) + { + case FieldType.Number: + ApplyRangeNumberFilter(query, fieldName, values, filterOperation); + break; + case FieldType.Date: + ApplyRangeDateFilter(query, fieldName, values, filterOperation); + break; + default: + _logger.LogWarning("Range filtering cannot be used with String fields. Only Number and Date fields support range filtering."); + break; + } + } + + private void ApplyRangeNumberFilter(IQuery query, string fieldName, string[] values, FilterOperation filterOperation) + { + if (TryParseIntFilterValue(values.First(), out int intValue) is false) + { + return; + } + + AddRangeFilter(query, fieldName, intValue, filterOperation); + } + + private void ApplyRangeDateFilter(IQuery query, string fieldName, string[] values, FilterOperation filterOperation) + { + if (TryParseDateTimeFilterValue(values.First(), out DateTime dateValue) is false) + { + return; + } + + AddRangeFilter(query, fieldName, dateValue, filterOperation); + } + + private void AddRangeFilter(IQuery query, string fieldName, T value, FilterOperation filterOperation) + where T : struct + { + T? min = null, max = null; + bool minInclusive = false, maxInclusive = false; + + switch (filterOperation) + { + case FilterOperation.GreaterThan: + case FilterOperation.GreaterThanOrEqual: + min = value; + minInclusive = filterOperation is FilterOperation.GreaterThanOrEqual; + break; + case FilterOperation.LessThan: + case FilterOperation.LessThanOrEqual: + max = value; + maxInclusive = filterOperation is FilterOperation.LessThanOrEqual; + break; + default: + throw new ArgumentOutOfRangeException(nameof(filterOperation)); + } + + query.RangeQuery(new[] { fieldName }, min, max, minInclusive, maxInclusive); + } + private void AddGroupedOrFilter(IQuery query, string fieldName, params T[] values) where T : struct { diff --git a/src/Umbraco.Core/DeliveryApi/FilterOperation.cs b/src/Umbraco.Core/DeliveryApi/FilterOperation.cs index 1e69f68c7f..6b897fd27d 100644 --- a/src/Umbraco.Core/DeliveryApi/FilterOperation.cs +++ b/src/Umbraco.Core/DeliveryApi/FilterOperation.cs @@ -5,5 +5,9 @@ public enum FilterOperation Is, IsNot, Contains, - DoesNotContain + DoesNotContain, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual } From 79af9e8e2bc34b61b4ccfcb811b7cfe0711a2c2a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:38:14 +0100 Subject: [PATCH 096/146] Update nightly build (#15359) * cherry-pick templateParameters from v14/dev * add isNightly parameter * remove v9 * add parameter check for myget isNightly --- build/azure-pipelines.yml | 9 ++++++++- build/nightly-build-trigger.yml | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 5ce19d4550..6c952d788d 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -41,6 +41,10 @@ parameters: 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: 18.16.x @@ -536,7 +540,10 @@ 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: diff --git a/build/nightly-build-trigger.yml b/build/nightly-build-trigger.yml index 1b70e6c9e5..7e128b2af7 100644 --- a/build/nightly-build-trigger.yml +++ b/build/nightly-build-trigger.yml @@ -8,13 +8,13 @@ schedules: displayName: Daily midnight build branches: include: - - v9/dev - v10/dev - v12/dev - v13/dev - v14/dev steps: +- checkout: none - task: TriggerBuild@4 inputs: definitionIsInCurrentTeamProject: true @@ -26,10 +26,10 @@ steps: useSameBranch: true waitForQueuedBuildsToFinish: false storeInEnvironmentVariable: false - templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true' + templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true' authenticationMethod: 'OAuth Token' enableBuildInQueueCondition: false dependentOnSuccessfulBuildCondition: false dependentOnFailedBuildCondition: false checkbuildsoncurrentbranch: false - failTaskIfConditionsAreNotFulfilled: false \ No newline at end of file + failTaskIfConditionsAreNotFulfilled: false From 37e4d80ce8ffdc07146c8374eb648400a8b81484 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:38:14 +0100 Subject: [PATCH 097/146] Update nightly build (#15359) * cherry-pick templateParameters from v14/dev * add isNightly parameter * remove v9 * add parameter check for myget isNightly --- build/azure-pipelines.yml | 84 +++++++++++++-------------------- build/nightly-build-trigger.yml | 6 +-- 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index ebfc4a5e19..1e955bfa36 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -41,6 +41,10 @@ parameters: 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: 20 @@ -101,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: @@ -151,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: @@ -212,7 +184,7 @@ stages: 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 @@ -281,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: @@ -314,7 +288,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQLite - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) ${{parameters.integrationNonReleaseTestFilter}}' @@ -328,7 +302,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQLite - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}' @@ -339,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: @@ -350,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: @@ -361,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')) @@ -374,7 +353,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQL Server - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}' @@ -389,7 +368,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQL Server - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}' @@ -420,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 @@ -510,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) @@ -548,7 +530,7 @@ 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: displayName: Push to pre-release feed @@ -565,14 +547,16 @@ 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: displayName: Push to NuGet @@ -595,12 +579,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 diff --git a/build/nightly-build-trigger.yml b/build/nightly-build-trigger.yml index 1b70e6c9e5..7e128b2af7 100644 --- a/build/nightly-build-trigger.yml +++ b/build/nightly-build-trigger.yml @@ -8,13 +8,13 @@ schedules: displayName: Daily midnight build branches: include: - - v9/dev - v10/dev - v12/dev - v13/dev - v14/dev steps: +- checkout: none - task: TriggerBuild@4 inputs: definitionIsInCurrentTeamProject: true @@ -26,10 +26,10 @@ steps: useSameBranch: true waitForQueuedBuildsToFinish: false storeInEnvironmentVariable: false - templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true' + templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true' authenticationMethod: 'OAuth Token' enableBuildInQueueCondition: false dependentOnSuccessfulBuildCondition: false dependentOnFailedBuildCondition: false checkbuildsoncurrentbranch: false - failTaskIfConditionsAreNotFulfilled: false \ No newline at end of file + failTaskIfConditionsAreNotFulfilled: false From 0a048d9231682df749b1b420909cb63183dfbda1 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:38:14 +0100 Subject: [PATCH 098/146] Update nightly build (#15359) * cherry-pick templateParameters from v14/dev * add isNightly parameter * remove v9 * add parameter check for myget isNightly --- build/azure-pipelines.yml | 19 +++++++++++++++---- build/nightly-build-trigger.yml | 6 +++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 7b543b201a..c3bac382da 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -41,11 +41,15 @@ parameters: 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: 20 - dotnetVersion: 8.x - dotnetIncludePreviewVersions: true + dotnetVersion: 6.x + dotnetIncludePreviewVersions: false solution: umbraco.sln buildConfiguration: Release UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042 @@ -107,6 +111,7 @@ stages: displayName: Use .NET $(dotnetVersion) inputs: version: $(dotnetVersion) + performMultiLevelLookup: true includePreviewVersions: $(dotnetIncludePreviewVersions) - task: DotNetCoreCLI@2 displayName: Run dotnet restore @@ -154,7 +159,7 @@ stages: inputs: targetType: inline script: | - dotnet tool install -g docfx --version 2.72.1 + choco install docfx --version=2.59.4 -y if ($lastexitcode -ne 0){ throw ("Error installing DocFX") } @@ -254,6 +259,7 @@ stages: displayName: Use .NET $(dotnetVersion) inputs: version: $(dotnetVersion) + performMultiLevelLookup: true includePreviewVersions: $(dotnetIncludePreviewVersions) - task: DotNetCoreCLI@2 displayName: Run dotnet test @@ -292,6 +298,7 @@ stages: displayName: Use .NET $(dotnetVersion) inputs: version: $(dotnetVersion) + performMultiLevelLookup: true includePreviewVersions: $(dotnetIncludePreviewVersions) - task: DotNetCoreCLI@2 displayName: Run dotnet test Windows @@ -467,6 +474,7 @@ stages: displayName: Use .NET $(dotnetVersion) inputs: version: $(dotnetVersion) + performMultiLevelLookup: true includePreviewVersions: $(dotnetIncludePreviewVersions) - pwsh: | $sha = 'g$(Build.SourceVersion)'.substring(0, 8) @@ -559,7 +567,10 @@ 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: diff --git a/build/nightly-build-trigger.yml b/build/nightly-build-trigger.yml index 1b70e6c9e5..7e128b2af7 100644 --- a/build/nightly-build-trigger.yml +++ b/build/nightly-build-trigger.yml @@ -8,13 +8,13 @@ schedules: displayName: Daily midnight build branches: include: - - v9/dev - v10/dev - v12/dev - v13/dev - v14/dev steps: +- checkout: none - task: TriggerBuild@4 inputs: definitionIsInCurrentTeamProject: true @@ -26,10 +26,10 @@ steps: useSameBranch: true waitForQueuedBuildsToFinish: false storeInEnvironmentVariable: false - templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true' + templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true' authenticationMethod: 'OAuth Token' enableBuildInQueueCondition: false dependentOnSuccessfulBuildCondition: false dependentOnFailedBuildCondition: false checkbuildsoncurrentbranch: false - failTaskIfConditionsAreNotFulfilled: false \ No newline at end of file + failTaskIfConditionsAreNotFulfilled: false From 25e36871d7ad295208051800d39b541138611817 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:15:03 +0100 Subject: [PATCH 099/146] use dotnet 8 --- build/azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index c3bac382da..0cacf10718 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -48,8 +48,8 @@ parameters: variables: nodeVersion: 20 - dotnetVersion: 6.x - dotnetIncludePreviewVersions: false + dotnetVersion: 8.x + dotnetIncludePreviewVersions: true solution: umbraco.sln buildConfiguration: Release UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042 From a6b53b0a932fe67304870ea10536c79ae4aa95e6 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 5 Dec 2023 16:49:55 +0100 Subject: [PATCH 100/146] Fixing PR #15318 which updated an interface, which is breaking (#15363) --- src/Umbraco.Core/Deploy/IFileTypeCollection.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs index 3fc22192b6..5dc03eb185 100644 --- a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs +++ b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs @@ -27,7 +27,18 @@ public interface IFileTypeCollection /// /// true if the file type associated with the specified entity type was found; otherwise, false. /// - bool TryGetValue(string entityType, [NotNullWhen(true)] out IFileType? fileType); + bool TryGetValue(string entityType, [NotNullWhen(true)] out IFileType? fileType) + { + // TODO (V14): Remove default implementation + if (Contains(entityType)) + { + fileType = this[entityType]; + return true; + } + + fileType = null; + return false; + } /// /// Determines whether this collection contains a file type for the specified entity type. @@ -44,5 +55,5 @@ public interface IFileTypeCollection /// /// The entity types. /// - ICollection GetEntityTypes(); + ICollection GetEntityTypes() => Array.Empty(); // TODO (V14): Remove default implementation } From 17eda54bfba9ef0c3a09e7db78b003f57aaf51f7 Mon Sep 17 00:00:00 2001 From: Richard Ockerby Date: Sun, 29 Oct 2023 00:51:34 +0100 Subject: [PATCH 101/146] Don't overwrite default values in the block list editor if they don't exist in the data model (for new creations) --- .../src/common/services/blockeditormodelobject.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js index 26c2ea014e..824868506a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -31,7 +31,8 @@ for (var p = 0; p < tab.properties.length; p++) { var prop = tab.properties[p]; - prop.value = dataModel[prop.alias]; + if (typeof (dataModel[prop.alias]) !== 'undefined') + prop.value = dataModel[prop.alias]; } } From 8fefca557fffe4afd97e5faeac5bd765c39678e8 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 6 Dec 2023 08:19:46 +0100 Subject: [PATCH 102/146] Improve Central Package Management, update dependencies and fix package validation/MSBuild warnings (#15362) * Use version overrides for ImageSharp 2 * Move Directory.Packages.props to root, use GlobalPackageReference and replace Microsoft.CSharp with Microsoft.CodeAnalysis.CSharp * Replace NPoco.SqlServer with NPoco and remove unused System dependencies * Fix package validation and MSBuild warnings * Add nuget.config and enable package source mapping * Remove explicitly set C# language version * Add empty Directory.Packages.props file for acceptance test * Downgrade SixLabors.ImageSharp back to 3.0.2 because of breaking changes * Update ImageSharp/ImageSharp.Web to 3.1.0 and use ComputeHMAC --- Directory.Build.props | 19 ++-- ...Packages.props => Directory.Packages.props | 96 ++++++++++++------- nuget.config | 13 +++ .../Media/ImageSharpImageUrlGenerator.cs | 6 +- .../Umbraco.Cms.Imaging.ImageSharp.csproj | 1 - .../Umbraco.Cms.Imaging.ImageSharp2.csproj | 5 +- .../Umbraco.Cms.Targets.csproj | 5 +- src/Umbraco.Cms/Umbraco.Cms.csproj | 1 - .../Umbraco.Infrastructure.csproj | 22 ++--- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 11 ++- templates/Directory.Build.props | 3 +- templates/Umbraco.Templates.csproj | 3 +- .../UmbracoPackage/UmbracoPackage.csproj | 12 ++- .../UmbracoProject/UmbracoProject.csproj | 1 - tests/Directory.Build.props | 3 +- tests/Directory.Packages.props | 29 +++--- .../misc/Directory.Packages.props | 7 ++ .../Umbraco.Tests.Benchmarks.csproj | 1 - .../Umbraco.Tests.Integration.csproj | 1 + .../Umbraco.JsonSchema.csproj | 5 +- umbraco.sln | 5 +- 21 files changed, 142 insertions(+), 107 deletions(-) rename src/Directory.Packages.props => Directory.Packages.props (70%) create mode 100644 nuget.config create mode 100644 tests/Umbraco.Tests.AcceptanceTest/misc/Directory.Packages.props diff --git a/Directory.Build.props b/Directory.Build.props index b9439a50ea..56ab2370c3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,6 @@ net8.0 - 11.0 Umbraco HQ Umbraco Copyright © Umbraco $([System.DateTime]::Today.ToString('yyyy')) @@ -17,6 +16,7 @@ nullable enable true + false @@ -35,21 +35,16 @@ true - - - - - - - - - - - + $(MSBuildThisFileDirectory) + + + + + diff --git a/src/Directory.Packages.props b/Directory.Packages.props similarity index 70% rename from src/Directory.Packages.props rename to Directory.Packages.props index bfa71d3932..0f28210de3 100644 --- a/src/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,8 +2,49 @@ true - NU1507 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -14,35 +55,16 @@ - - - - - - - - - - - - - - - - - - - - + + - - - - + + + @@ -55,16 +77,20 @@ - - + + - - - - - - - + - \ No newline at end of file + + + + + + + + + + + diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000000..e94f426fac --- /dev/null +++ b/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs index afcd0f35a2..25c964186f 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs @@ -130,11 +130,7 @@ public sealed class ImageSharpImageUrlGenerator : IImageUrlGenerator { var uri = QueryHelpers.AddQueryString(options.ImageUrl, queryString); - // It's important that we call the async version here. - // This is because if we call the synchronous version, we ImageSharp will start a new Task ever single time. - // This becomes a huge problem if the site is under load, and will result in massive spikes in response time. - // See https://github.com/SixLabors/ImageSharp.Web/blob/main/src/ImageSharp.Web/AsyncHelper.cs#L24 - var token = _requestAuthorizationUtilities.ComputeHMACAsync(uri, CommandHandling.Sanitize).GetAwaiter().GetResult(); + var token = _requestAuthorizationUtilities.ComputeHMAC(uri, CommandHandling.Sanitize); if (string.IsNullOrEmpty(token) is false) { queryString.Add(RequestAuthorizationUtilities.TokenCommand, token); diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj index bb9b44cf51..13126a24b5 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj @@ -2,7 +2,6 @@ Umbraco CMS - Imaging - ImageSharp Adds imaging support using ImageSharp/ImageSharp.Web to Umbraco CMS. - false diff --git a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj index d55479d8ec..1dcba40ab6 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj @@ -2,12 +2,11 @@ Umbraco CMS - Imaging - ImageSharp 2 Adds imaging support using ImageSharp/ImageSharp.Web version 2 to Umbraco CMS. - false - - + + diff --git a/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj b/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj index b930ff1bc0..a02370bb1e 100644 --- a/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj +++ b/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj @@ -4,9 +4,6 @@ Installs Umbraco CMS with minimal dependencies in your ASP.NET Core project. false false - - false - false @@ -24,7 +21,7 @@ NU5100;NU5128 - + diff --git a/src/Umbraco.Cms/Umbraco.Cms.csproj b/src/Umbraco.Cms/Umbraco.Cms.csproj index 8b0dcb8a87..39a3b03c56 100644 --- a/src/Umbraco.Cms/Umbraco.Cms.csproj +++ b/src/Umbraco.Cms/Umbraco.Cms.csproj @@ -4,7 +4,6 @@ Installs Umbraco CMS with all default dependencies in your ASP.NET Core project. false false - false diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index a9fef3d046..c5a60e8dcc 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -12,35 +12,31 @@ - - + + - + - + - + - - - + + + - + - - - - diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 0609c216eb..8e2241564e 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -3,20 +3,23 @@ Umbraco.Cms.Web.UI false false - false - - + + + + + + - + diff --git a/templates/Directory.Build.props b/templates/Directory.Build.props index c4e40e3e06..4d993c78c8 100644 --- a/templates/Directory.Build.props +++ b/templates/Directory.Build.props @@ -1,4 +1,5 @@ + - + diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index 6a4d0460d1..0ccbab5e19 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -9,7 +9,6 @@ true . NU5128 - false @@ -44,7 +43,7 @@ - + diff --git a/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj index 98f5bac3ad..309bf6b2d2 100644 --- a/templates/UmbracoPackage/UmbracoPackage.csproj +++ b/templates/UmbracoPackage/UmbracoPackage.csproj @@ -1,16 +1,20 @@ net8.0 + enable + enable . - UmbracoPackage + UmbracoPackage + + + UmbracoPackage + UmbracoPackage UmbracoPackage ... umbraco plugin package - UmbracoPackage - false - + diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj index 1c530223ad..ee8dd5e56e 100644 --- a/templates/UmbracoProject/UmbracoProject.csproj +++ b/templates/UmbracoProject/UmbracoProject.csproj @@ -4,7 +4,6 @@ enable enable Umbraco.Cms.Web.UI - false diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 1d532e6664..49f18af1bd 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -1,6 +1,7 @@ + - + diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index 41398eed6e..a8031cc500 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -1,24 +1,25 @@ - - true - NU1507 - + + - + + + + + + + + + + + + - - - - - - - - - \ No newline at end of file + diff --git a/tests/Umbraco.Tests.AcceptanceTest/misc/Directory.Packages.props b/tests/Umbraco.Tests.AcceptanceTest/misc/Directory.Packages.props new file mode 100644 index 0000000000..8db7958e54 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/misc/Directory.Packages.props @@ -0,0 +1,7 @@ + + + + diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 09a4600005..189d69ac31 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -3,7 +3,6 @@ Exe false false - false false diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index c4d3070472..56898cebf5 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -7,6 +7,7 @@ Umbraco.Cms.Tests.Integration true $(BaseEnablePackageValidation) + NU5100 diff --git a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj index 8b66986898..3e3b2792a8 100644 --- a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj +++ b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj @@ -3,12 +3,11 @@ Exe false false - false - - + + diff --git a/umbraco.sln b/umbraco.sln index 98a353a387..517b3959e9 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -129,12 +129,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore .globalconfig = .globalconfig Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + global.json = global.json icon.png = icon.png LICENSE.md = LICENSE.md umbraco.sln.DotSettings = umbraco.sln.DotSettings + nuget.config = nuget.config version.json = version.json - global.json = global.json - src\Directory.Packages.props = src\Directory.Packages.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{20CE9C97-9314-4A19-BCF1-D12CF49B7205}" From 370e1cc12563c728860320c0ff080b542b95dc51 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 6 Dec 2023 10:18:23 +0100 Subject: [PATCH 103/146] Media Picker 3 should not be used as a macro parameter (#15330) --- .../PropertyEditors/MediaPicker3PropertyEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs index 0014a96e1f..c2dedef852 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// [DataEditor( Constants.PropertyEditors.Aliases.MediaPicker3, - EditorType.PropertyValue | EditorType.MacroParameter, + EditorType.PropertyValue, "Media Picker", "mediapicker3", ValueType = ValueTypes.Json, From 312276bde70d9d19dfe0b9a3b556c806acc3995b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 6 Dec 2023 10:21:46 +0100 Subject: [PATCH 104/146] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 33e6ec9c6f..73318d49b0 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.0.0-rc4", + "version": "13.1.0-rc", "assemblyVersion": { "precision": "build" }, From 024a57e0982dc23469de44d5adb7283394078301 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 6 Dec 2023 11:02:50 +0100 Subject: [PATCH 105/146] Merge branch 'v12/dev' into v13/dev # Conflicts: # global.json # src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html # src/Umbraco.Core/EmbeddedResources/Lang/da.xml # src/Umbraco.Core/EmbeddedResources/Lang/en.xml # src/Umbraco.Core/EmbeddedResources/Lang/tr.xml # src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs # src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs # src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs # src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs # src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js # src/Umbraco.Web.UI.Client/src/common/services/editor.service.js # src/Umbraco.Web.UI/Startup.cs # tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts # tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs (cherry picked from commit ef3f2c48198d8baed9d3340f926cd07e80720428) --- .github/CONTRIBUTING.md | 258 +------- .github/contributing-before-you-start.md | 54 ++ .github/contributing-core-collabs-team.md | 28 + .github/contributing-creating-a-pr.md | 51 ++ .github/contributing-first-issue.md | 93 +++ .../contributing-other-ways-to-contribute.md | 10 + .github/contributing-unwanted-changes.md | 18 + legacy/Umbraco.Tests/App.config | 119 ---- .../Umbraco.Tests/Properties/AssemblyInfo.cs | 41 -- .../Umbraco.Tests/Published/ModelTypeTests.cs | 43 -- .../Routing/BaseUrlProviderTest.cs | 45 -- .../Routing/MediaUrlProviderTests.cs | 206 ------ legacy/Umbraco.Tests/Umbraco.Tests.csproj | 199 ------ .../AuthenticationControllerTests.cs | 122 ---- .../Umbraco.Tests/unit-test-log4net.CI.config | 6 - legacy/Umbraco.Tests/unit-test-logger.config | 20 - .../OpenApi/EnumSchemaFilter.cs | 2 + .../Querying/QueryOptionBase.cs | 3 +- .../SqliteMigrationProvider.cs | 1 + .../Constants-ProviderNames.cs | 11 + .../umbBlockGridDemoRichTextBlock.html | 2 +- src/Umbraco.Core/Composing/TypeFinder.cs | 2 +- .../EmbeddedResources/Lang/da.xml | 131 ++-- .../EmbeddedResources/Lang/en.xml | 141 +++- .../EmbeddedResources/Lang/en_us.xml | 1 + .../EmbeddedResources/Lang/fr.xml | 29 +- .../EmbeddedResources/Lang/tr.xml | 89 +-- .../Routing/ContentFinderByRedirectUrl.cs | 3 +- src/Umbraco.Core/UriUtilityCore.cs | 4 +- .../Examine/ContentValueSetBuilder.cs | 63 +- .../Security/MemberPasswordHasher.cs | 4 +- .../PublishedSnapshotService.cs | 59 +- .../Controllers/MemberController.cs | 12 +- .../Controllers/UsersController.cs | 4 +- .../Tours/getting-started.json | 2 +- .../UmbracoBuilderExtensions.cs | 1 - .../buttons/umbtogglegroup.directive.js | 1 + .../forms/umbfocuslock.directive.js | 3 +- .../components/umbgroupsbuilder.directive.js | 3 +- .../components/umbmediagrid.directive.js | 1 + .../common/resources/contenttype.resource.js | 2 +- .../common/resources/mediatype.resource.js | 2 +- .../blockeditormodelobject.service.js | 10 +- .../src/common/services/editor.service.js | 72 ++- .../src/common/services/navigation.service.js | 17 +- .../src/common/services/util.service.js | 2 +- src/Umbraco.Web.UI.Client/src/index.html | 4 +- .../src/installer/steps/user.controller.js | 7 + .../src/installer/steps/user.html | 12 +- .../src/less/application/grid.less | 6 + .../components/application/umb-dashboard.less | 1 + .../src/less/installer.less | 28 + .../src/less/variables.less | 2 +- .../linkpicker/linkpicker.controller.js | 21 +- .../linkpicker/linkpicker.html | 1 + .../application/umb-app-header.html | 4 +- .../application/umb-navigation.html | 6 +- .../components/application/umb-search.html | 4 +- .../components/application/umb-sections.html | 4 +- .../blockcard/umb-block-card-grid.less | 2 +- .../components/blockcard/umb-block-card.less | 1 + .../components/buttons/umb-toggle-group.html | 3 +- .../umbimagepreview/umb-image-preview.less | 1 + .../src/views/components/umb-table.html | 3 +- .../components/upload/umb-file-dropzone.html | 2 +- .../views/datatype.info.controller.js | 13 +- .../src/views/dictionary/list.html | 8 +- .../src/views/mediaTypes/create.controller.js | 5 +- .../src/views/mediaTypes/create.html | 20 +- .../views/memberTypes/create.controller.js | 5 +- .../src/views/memberTypes/create.html | 21 +- ...blockgrid.blockconfiguration.controller.js | 7 +- .../blockgrid.blockconfiguration.less | 51 +- .../blockgrid.blockconfiguration.overlay.less | 2 +- ...blocklist.blockconfiguration.controller.js | 7 +- .../blocklist.blockconfiguration.html | 7 +- .../blocklist.blockconfiguration.less | 6 + .../blocklist.blockconfiguration.overlay.less | 2 +- .../colorpicker/colorpicker.prevalues.html | 72 ++- .../multicolorpicker.controller.js | 393 ++++++----- .../imagecropper/imagecropper.controller.js | 5 +- .../UmbTwoFactorLoginController.cs | 2 +- tests/Umbraco.TestData/LoadTestController.cs | 9 +- .../package-lock.json | 60 +- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../DefaultConfig/HelpPanel/helpLinks.spec.ts | 13 + .../BackOfficeExamineSearcherTests.cs | 611 ++++++++++++++++++ .../UmbracoExamine/ExamineBaseTest.cs | 46 ++ .../ExamineExternalIndexSearcherTest.cs | 417 ++++++++++++ .../ExamineExternalIndexTests.cs | 137 ++++ .../IExamineExternalIndexSearcherTest.cs | 18 + .../UmbracoExamine/IndexInitializer.cs | 14 +- 92 files changed, 2487 insertions(+), 1568 deletions(-) create mode 100644 .github/contributing-before-you-start.md create mode 100644 .github/contributing-core-collabs-team.md create mode 100644 .github/contributing-creating-a-pr.md create mode 100644 .github/contributing-first-issue.md create mode 100644 .github/contributing-other-ways-to-contribute.md create mode 100644 .github/contributing-unwanted-changes.md delete mode 100644 legacy/Umbraco.Tests/App.config delete mode 100644 legacy/Umbraco.Tests/Properties/AssemblyInfo.cs delete mode 100644 legacy/Umbraco.Tests/Published/ModelTypeTests.cs delete mode 100644 legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs delete mode 100644 legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs delete mode 100644 legacy/Umbraco.Tests/Umbraco.Tests.csproj delete mode 100644 legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs delete mode 100644 legacy/Umbraco.Tests/unit-test-log4net.CI.config delete mode 100644 legacy/Umbraco.Tests/unit-test-logger.config create mode 100644 src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 06fd873638..431fa7c2fa 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,268 +4,56 @@ These contribution guidelines are mostly just that - guidelines, not rules. This is what we've found to work best over the years, but if you choose to ignore them, we still love you! 💖 Use your best judgement, and feel free to propose changes to this document in a pull request. -## Coding not your thing? Or want more ways to contribute? +## Getting Started +We have a guide on [what to consider before you start](contributing-before-you-start.md) and more detailed guides at the end of this article. -This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved] - -If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better. - -We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy. - -## Table of contents - -- [Before you start](#before-you-start) - * [Code of Conduct](#code-of-conduct) - * [What can I contribute?](#what-can-i-contribute) - + [Making larger changes](#making-larger-changes) - + [Pull request or package?](#pull-request-or-package) - + [Unwanted changes](#unwanted-changes) - + [Ownership and copyright](#ownership-and-copyright) -- [Finding your first issue: Up for grabs](#finding-your-first-issue-up-for-grabs) -- [Making your changes](#making-your-changes) - + [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository) - + [Style guide](#style-guide) - + [Questions?](#questions) -- [Creating a pull request](#creating-a-pull-request) -- [The review process](#the-review-process) - * [Dealing with requested changes](#dealing-with-requested-changes) - + [No longer available?](#no-longer-available) - * [The Core Collaborators team](#the-core-collaborators-team) - -## Before you start - - -### Code of Conduct - -This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct]. - -### What can I contribute? - -We categorise pull requests (PRs) into two categories: - -| PR type | Definition | -| --------- | ------------------------------------------------------------ | -| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. | -| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). | - -We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process]. - -We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations. - -Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge. - -#### Making larger changes - -[making larger changes]: #making-larger-changes - -Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented. - -If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress. - -#### Pull request or package? - -[pr or package]: #pull-request-or-package - -If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes]. - -If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS. - -#### Unwanted changes -While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused. -Of course this will depend heavily on the specific change, but please take the following examples in mind. - -- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you. -- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues. -- **Changes to obsolete code and/or property editors ✍️** -- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it. -- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files! -- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core. - -While these are only a few examples, it is important to ask yourself these questions before making a pull request: - -- How many will benefit from this change? -- Are there other ways to achieve this? And if so, how do they compare? -- How maintainable is the change? -- What would be the effort to test it properly? -- Do the benefits outweigh the risks? - -#### Ownership and copyright - -It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code. - -Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS. - -If you're not sure, leave a note on your contribution and we will be happy to guide you. - -When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards. - -## Finding your first issue: Up for grabs - -Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out. - -If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has. - -## Making your changes - -Great question! The short version goes like this: +The following steps are a quick-start guide: 1. **Fork** - Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo] + Create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS) ![Fork the repository](img/forkrepository.png) -1. **Clone** +2. **Clone** - When GitHub has created your fork, you can clone it in your favorite Git tool + When GitHub has created your fork, you can clone it in your favorite Git tool or on the command line with `git clone https://github.com/[YourUsername]/Umbraco-CMS`. ![Clone the fork](img/clonefork.png) -1. **Switch to the correct branch** +3. **Switch to the correct branch** Switch to the `contrib` branch -1. **Build** +4. **Build** - Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs]. + Build your fork of Umbraco locally [as described in the build documentation](BUILD.md), you can build with any IDE that supports dotnet or the command line. -1. **Branch** +5. **Branch** - Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first. + Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp/12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first. -1. **Change** +6. **Change** - Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions]. + Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](contributing-first-issue.md#questions). -1. **Commit and push** +7. **Commit and push** Done? Yay! 🎉 Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub. -#### Keeping your Umbraco fork in sync with the main repository -[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository +8. **Create pull request** -Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes. + On GitHub, in your forked repository (`https://github.com/[YourUsername]/Umbraco-CMS`) you will see a banner saying that you pushed a new branch and a button to make a pull request. Tap the button and follow the instuctions. -To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once: + Want to read further? [Creating a pull request and what happens next](contributing-creating-a-pr.md). -``` -git remote add upstream https://github.com/umbraco/Umbraco-CMS.git -``` +## Further contribution guides -Then when you want to get the changes from the main repository: +- [Before you start](contributing-before-you-start.md) +- [Finding your first issue: Up for grabs](contributing-before-you-start.md) +- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/) +- [Unwanted changes](contributing-unwanted-changes.md) +- [Other ways to contribute](contributing-other-ways-to-contribute.md) -``` -git fetch upstream -git rebase upstream/contrib -``` - -In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed. - -[More information on how this works can be found on the thoughtbot blog.][sync fork ext] - -#### Style guide - -To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. - -That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. - -#### Questions? -[questions]: #questions - -You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away: - -- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward. -- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs]. -- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction. - -## Creating a pull request - -Exciting! You're ready to show us your changes. - -We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier. - -GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. -![Create a pull request](img/createpullrequest.png) - -We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting. - -Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise. - -## The review process -[review process]: #the-review-process - -You've sent us your first contribution - congratulations! Now what? - -The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes. - -You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides. - -You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes: - -- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco -- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible -- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see -- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package] - -### Dealing with requested changes - -If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco! - -#### No longer available? - -We understand you have other things to do and can't just drop everything to help us out. - -So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time. - -If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it. - -There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository. - -### The Core Collaborators team -[Core collabs]: #the-core-collaborators-team - -The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time: - -- [Busra Sengul][Busra Sengul] -- [Emma Garland][Emma Garland] -- [George Bidder][George Bidder] -- [Jason Elkin][Jason Elkin] -- [Laura Neto][Laura Neto] -- [Michael Latouche][Michael Latouche] -- [Sebastiaan][Sebastiaan] - - -These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged. - - - - - -[MIT license]: ../LICENSE.md "Umbraco's license declaration" -[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio" -[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code" - - - -[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile" -[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile" -[George Bidder]: https://github.com/georgebid "George's GitHub profile" -[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile" -[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile" -[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile" -[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile" -[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile" -[ Umbrabot ]: https://github.com/umbrabot -[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow" -[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated" -[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests" -[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/ -[get involved]: https://community.umbraco.com/get-involved/ -[docs repo]: https://github.com/umbraco/UmbracoDocs -[code of conduct]: https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md -[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs -[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS -[issues]: https://github.com/umbraco/Umbraco-CMS/issues -[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions diff --git a/.github/contributing-before-you-start.md b/.github/contributing-before-you-start.md new file mode 100644 index 0000000000..1668cf9541 --- /dev/null +++ b/.github/contributing-before-you-start.md @@ -0,0 +1,54 @@ +## Before you start + + +### Code of Conduct + +This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct]. + +### What can I contribute? + +We categorise pull requests (PRs) into two categories: + +| PR type | Definition | +| --------- | ------------------------------------------------------------ | +| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. | +| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). | + +We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process]. + +We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations. + +Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge. + +#### Making larger changes + +[making larger changes]: #making-larger-changes + +Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented. + +If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress. + +#### Pull request or package? + +[pr or package]: #pull-request-or-package + +If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes]. + +If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS. + +#### Ownership and copyright + +It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code. + +Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS. + +If you're not sure, leave a note on your contribution and we will be happy to guide you. + +When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards. + + +[MIT license]: ../LICENSE.md "Umbraco's license declaration" + + +[issues]: https://github.com/umbraco/Umbraco-CMS/issues +[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions diff --git a/.github/contributing-core-collabs-team.md b/.github/contributing-core-collabs-team.md new file mode 100644 index 0000000000..1e00529ee9 --- /dev/null +++ b/.github/contributing-core-collabs-team.md @@ -0,0 +1,28 @@ +### The Core Collaborators team +[Core collabs]: #the-core-collaborators-team + +The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time: + +- [Busra Sengul][Busra Sengul] +- [Emma Garland][Emma Garland] +- [George Bidder][George Bidder] +- [Jason Elkin][Jason Elkin] +- [Laura Neto][Laura Neto] +- [Kyle Eck][Kyle Eck] +- [Michael Latouche][Michael Latouche] +- [Sebastiaan][Sebastiaan] + + +These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged. + + + + +[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile" +[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile" +[George Bidder]: https://github.com/georgebid "George's GitHub profile" +[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile" +[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile" +[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile" +[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile" +[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile" diff --git a/.github/contributing-creating-a-pr.md b/.github/contributing-creating-a-pr.md new file mode 100644 index 0000000000..dc1d67ea65 --- /dev/null +++ b/.github/contributing-creating-a-pr.md @@ -0,0 +1,51 @@ +## Creating a pull request + +Exciting! You're ready to show us your changes. + +We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier. + +GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. +![Create a pull request](img/createpullrequest.png) + +We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting. + +Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise. + +## The review process +[review process]: #the-review-process + +You've sent us your contribution - congratulations! Now what? + +The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes. + +You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides. + +You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes: + +- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco +- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible +- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see +- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package] + +### Dealing with requested changes + +If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco! + +#### No longer available? + +We understand you have other things to do and can't just drop everything to help us out. + +So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time. + +If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it. + +There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository. + + +[ Umbrabot ]: https://github.com/umbrabot +[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow" + + +[making larger changes]: contributing-before-you-start.md#making-large-changes +[pr or package]: contributing-before-you-start.md#pull-request-or-package +[Core collabs]: contributing-core-collabs-team.md \ No newline at end of file diff --git a/.github/contributing-first-issue.md b/.github/contributing-first-issue.md new file mode 100644 index 0000000000..8027d69519 --- /dev/null +++ b/.github/contributing-first-issue.md @@ -0,0 +1,93 @@ +## Finding your first issue: Up for grabs + +Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out. + +If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has. + +## Making your changes + +Great question! The short version goes like this: + +1. **Fork** + + Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo] + + ![Fork the repository](img/forkrepository.png) + +1. **Clone** + + When GitHub has created your fork, you can clone it in your favorite Git tool + + ![Clone the fork](img/clonefork.png) + +1. **Switch to the correct branch** + + Switch to the `contrib` branch + +1. **Build** + + Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs]. + +1. **Branch** + + Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first. + +1. **Change** + + Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions]. + +1. **Commit and push** + + Done? Yay! 🎉 + + Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub. + +#### Keeping your Umbraco fork in sync with the main repository +[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository + +Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes. + +To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once: + +``` +git remote add upstream https://github.com/umbraco/Umbraco-CMS.git +``` + +Then when you want to get the changes from the main repository: + +``` +git fetch upstream +git rebase upstream/contrib +``` + +In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed. + +[More information on how this works can be found on the thoughtbot blog.][sync fork ext] + +#### Style guide + +To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. + +That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. + +#### Questions? +[questions]: #questions + +You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away: + +- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward. +- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs]. +- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction. + + + + +[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio" +[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code" + + +[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated" +[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests" +[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/ +[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS +[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs \ No newline at end of file diff --git a/.github/contributing-other-ways-to-contribute.md b/.github/contributing-other-ways-to-contribute.md new file mode 100644 index 0000000000..773471b8f1 --- /dev/null +++ b/.github/contributing-other-ways-to-contribute.md @@ -0,0 +1,10 @@ +## Coding not your thing? Or want more ways to contribute? + +This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved] + +If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better. + +We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy. + +[get involved]: https://community.umbraco.com/get-involved/ +[docs repo]: https://github.com/umbraco/UmbracoDocs \ No newline at end of file diff --git a/.github/contributing-unwanted-changes.md b/.github/contributing-unwanted-changes.md new file mode 100644 index 0000000000..c078f4600e --- /dev/null +++ b/.github/contributing-unwanted-changes.md @@ -0,0 +1,18 @@ +## Unwanted changes +While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused. +Of course this will depend heavily on the specific change, but please take the following examples in mind. + +- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you. +- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues. +- **Changes to obsolete code and/or property editors ✍️** +- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it. +- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files! +- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core. + +While these are only a few examples, it is important to ask yourself these questions before making a pull request: + +- How many will benefit from this change? +- Are there other ways to achieve this? And if so, how do they compare? +- How maintainable is the change? +- What would be the effort to test it properly? +- Do the benefits outweigh the risks? \ No newline at end of file diff --git a/legacy/Umbraco.Tests/App.config b/legacy/Umbraco.Tests/App.config deleted file mode 100644 index 3bb668535d..0000000000 --- a/legacy/Umbraco.Tests/App.config +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/legacy/Umbraco.Tests/Properties/AssemblyInfo.cs b/legacy/Umbraco.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index ec1ddca2f8..0000000000 --- a/legacy/Umbraco.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Umbraco.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Umbraco.Tests")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("33ddf9b7-505c-4a12-8370-7fee9de5df6d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -// Internals must be visible to DynamicProxyGenAssembly2 -// in order to mock loggers loggers with types from the assembly -// I.E. Mock.Of>() -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/legacy/Umbraco.Tests/Published/ModelTypeTests.cs b/legacy/Umbraco.Tests/Published/ModelTypeTests.cs deleted file mode 100644 index 2702b82812..0000000000 --- a/legacy/Umbraco.Tests/Published/ModelTypeTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using NUnit.Framework; -using Umbraco.Cms.Core.Models.PublishedContent; - -namespace Umbraco.Tests.Published -{ - [TestFixture] - public class ModelTypeTests - { - - //TODO these is not easy to move to the Unittest project due to underlysing NotImplementedException of Type.IsSZArray - [Test] - public void ModelTypeToStringTests() - { - var modelType = ModelType.For("alias1"); - var modelTypeArray = modelType.MakeArrayType(); - - Assert.AreEqual("{alias1}", modelType.ToString()); - - // there's an "*" there because the arrays are not true SZArray - but that changes when we map - - Assert.AreEqual("{alias1}[*]", modelTypeArray.ToString()); - var enumArray = typeof(IEnumerable<>).MakeGenericType(modelTypeArray); - Assert.AreEqual("System.Collections.Generic.IEnumerable`1[{alias1}[*]]", enumArray.ToString()); - } - - [Test] - public void ModelTypeFullNameTests() - { - Assert.AreEqual("{alias1}", ModelType.For("alias1").FullName); - - Type type = ModelType.For("alias1"); - Assert.AreEqual("{alias1}", type.FullName); - - // there's an "*" there because the arrays are not true SZArray - but that changes when we map - Assert.AreEqual("{alias1}[*]", ModelType.For("alias1").MakeArrayType().FullName); - // note the inner assembly qualified name - Assert.AreEqual("System.Collections.Generic.IEnumerable`1[[{alias1}[*], Umbraco.Core, Version=0.5.0.0, Culture=neutral, PublicKeyToken=null]]", typeof(IEnumerable<>).MakeGenericType(ModelType.For("alias1").MakeArrayType()).FullName); - } - - } -} diff --git a/legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs b/legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs deleted file mode 100644 index 6be2b72ad1..0000000000 --- a/legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Routing -{ - public abstract class BaseUrlProviderTest : BaseWebTest - { - protected IUmbracoContextAccessor UmbracoContextAccessor { get; } = new TestUmbracoContextAccessor(); - - protected abstract bool HideTopLevelNodeFromPath { get; } - - protected override void Compose() - { - base.Compose(); - Builder.Services.AddTransient(); - } - - protected override void ComposeSettings() - { - var contentSettings = new ContentSettings(); - var userPasswordConfigurationSettings = new UserPasswordConfigurationSettings(); - - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(contentSettings)); - Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings)); - } - - protected IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider) - { - var webRoutingSettings = new WebRoutingSettings(); - return new UrlProvider( - new TestUmbracoContextAccessor(umbracoContext), - Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(new[] { urlProvider }), - new MediaUrlProviderCollection(Enumerable.Empty()), - Mock.Of()); - } - } -} diff --git a/legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs deleted file mode 100644 index ac79dee6cf..0000000000 --- a/legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Linq; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Tests.PublishedContent; -using Umbraco.Tests.TestHelpers; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class MediaUrlProviderTests : BaseWebTest - { - private DefaultMediaUrlProvider _mediaUrlProvider; - - public override void SetUp() - { - base.SetUp(); - - var loggerFactory = NullLoggerFactory.Instance; - var mediaFileManager = new MediaFileManager(Mock.Of(), Mock.Of(), - loggerFactory.CreateLogger(), Mock.Of()); - var contentSettings = new ContentSettings(); - var dataTypeService = Mock.Of(); - var propertyEditors = new MediaUrlGeneratorCollection(new IMediaUrlGenerator[] - { - new FileUploadPropertyEditor(DataValueEditorFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, UploadAutoFillProperties, ContentService), - new ImageCropperPropertyEditor(DataValueEditorFactory, loggerFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, IOHelper, UploadAutoFillProperties, ContentService), - }); - _mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors, UriUtility); - } - - public override void TearDown() - { - base.TearDown(); - - _mediaUrlProvider = null; - } - - [Test] - public void Get_Media_Url_Resolves_Url_From_Upload_Property_Editor() - { - const string expected = "/media/rfeiw584/test.jpg"; - - var umbracoContext = GetUmbracoContext("/"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Resolves_Url_From_Image_Cropper_Property_Editor() - { - const string expected = "/media/rfeiw584/test.jpg"; - - var configuration = new ImageCropperConfiguration(); - var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue - { - Src = expected - }); - - var umbracoContext = GetUmbracoContext("/"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Can_Resolve_Absolute_Url() - { - const string mediaUrl = "/media/rfeiw584/test.jpg"; - var expected = $"http://localhost{mediaUrl}"; - - var umbracoContext = GetUmbracoContext("http://localhost"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute() - { - const string expected = "http://localhost/media/rfeiw584/test.jpg"; - - var umbracoContext = GetUmbracoContext("http://localhost"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Relative); - - Assert.AreEqual(expected, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported() - { - var umbracoContext = GetUmbracoContext("/"); - var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null); - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute, propertyAlias: "test"); - - Assert.AreEqual(string.Empty, resolvedUrl); - } - - [Test] - public void Get_Media_Url_Can_Resolve_Variant_Property_Url() - { - var umbracoContext = GetUmbracoContext("http://localhost"); - - var umbracoFilePropertyType = CreatePropertyType(Constants.PropertyEditors.Aliases.UploadField, null, ContentVariation.Culture); - - const string enMediaUrl = "/media/rfeiw584/en.jpg"; - const string daMediaUrl = "/media/uf8ewud2/da.jpg"; - - var property = new SolidPublishedPropertyWithLanguageVariants - { - Alias = "umbracoFile", - PropertyType = umbracoFilePropertyType, - }; - - property.SetSourceValue("en", enMediaUrl, true); - property.SetSourceValue("da", daMediaUrl); - - var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); - var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; - - var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto, "da"); - Assert.AreEqual(daMediaUrl, resolvedUrl); - } - - private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext) - { - var webRoutingSettings = new WebRoutingSettings(); - return new UrlProvider( - new TestUmbracoContextAccessor(umbracoContext), - Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(Enumerable.Empty()), - new MediaUrlProviderCollection(new []{_mediaUrlProvider}), - Mock.Of() - ); - } - - private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration) - { - var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); - - var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), - new[] {umbracoFilePropertyType}, ContentVariation.Nothing); - - return new SolidPublishedContent(contentType) - { - Id = 1234, - Key = Guid.NewGuid(), - Properties = new[] - { - new SolidPublishedProperty - { - Alias = "umbracoFile", - SolidSourceValue = propertyValue, - SolidHasValue = true, - PropertyType = umbracoFilePropertyType - } - } - }; - } - - private static PublishedPropertyType CreatePropertyType(string propertyEditorAlias, object dataTypeConfiguration, ContentVariation variation) - { - var uploadDataType = new PublishedDataType(1234, propertyEditorAlias, new Lazy(() => dataTypeConfiguration)); - - var propertyValueConverters = new PropertyValueConverterCollection(new IPropertyValueConverter[] - { - new UploadPropertyConverter(), - new ImageCropperValueConverter(Mock.Of>()), - }); - - var publishedModelFactory = Mock.Of(); - var publishedContentTypeFactory = new Mock(); - publishedContentTypeFactory.Setup(x => x.GetDataType(It.IsAny())) - .Returns(uploadDataType); - - return new PublishedPropertyType("umbracoFile", 42, true, variation, propertyValueConverters, publishedModelFactory, publishedContentTypeFactory.Object); - } - } -} diff --git a/legacy/Umbraco.Tests/Umbraco.Tests.csproj b/legacy/Umbraco.Tests/Umbraco.Tests.csproj deleted file mode 100644 index a83c0d6453..0000000000 --- a/legacy/Umbraco.Tests/Umbraco.Tests.csproj +++ /dev/null @@ -1,199 +0,0 @@ - - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5D3B8245-ADA6-453F-A008-50ED04BFE770} - Library - Properties - Umbraco.Tests - Umbraco.Tests - v4.7.2 - 512 - ..\ - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - - - true - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - 8 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - latest - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.0.0 - - - - - 1.11.31 - - - - - - - - - - - - 5.0.0 - - - - - 5.0.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Always - - - - - {29aa69d9-b597-4395-8d42-43b1263c240a} - Umbraco.Core - - - {0fad7d2a-d7dd-45b1-91fd-488bb6cdacea} - Umbraco.Examine.Lucene - - - {3ae7bf57-966b-45a5-910a-954d7c554441} - Umbraco.Infrastructure - - - {33085570-9bf2-4065-a9b0-a29d920d13ba} - Umbraco.Persistence.SqlCe - - - {f6de8da0-07cc-4ef2-8a59-2bc81dbb3830} - Umbraco.PublishedCache.NuCache - - - {a499779c-1b3b-48a8-b551-458e582e6e96} - Umbraco.Tests.Common - - - {651E1350-91B6-44B7-BD60-7207006D7003} - Umbraco.Web - - - - - - - - - $(NuGetPackageFolders.Split(';')[0]) - - - - - - - - - - \ No newline at end of file diff --git a/legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs deleted file mode 100644 index a138c6ce94..0000000000 --- a/legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Features; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Extensions; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Web.Controllers -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.None)] - public class AuthenticationControllerTests : TestWithDatabaseBase - { - protected override void ComposeApplication(bool withApplication) - { - base.ComposeApplication(withApplication); - //if (!withApplication) return; - - // replace the true IUserService implementation with a mock - // so that each test can configure the service to their liking - Builder.Services.AddUnique(f => Mock.Of()); - - // kill the true IEntityService too - Builder.Services.AddUnique(f => Mock.Of()); - - Builder.Services.AddUnique(); - } - - - // TODO Reintroduce when moved to .NET Core - // [Test] - // public async System.Threading.Tasks.Task GetCurrentUser_Fips() - // { - // ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) - // { - // //setup some mocks - // var userServiceMock = Mock.Get(ServiceContext.UserService); - // userServiceMock.Setup(service => service.GetUserById(It.IsAny())) - // .Returns(() => null); - // - // if (Thread.GetDomain().GetData(".appPath") != null) - // { - // HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter())); - // } - // else - // { - // var baseDir = IOHelper.MapPath("").TrimEnd(Path.DirectorySeparatorChar); - // HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter())); - // } - // - // var usersController = new AuthenticationController( - // new TestUserPasswordConfig(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // umbracoContextAccessor, - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance(), - // Factory.GetInstance() - // ); - // return usersController; - // } - // - // Mock.Get(Current.SqlContext) - // .Setup(x => x.Query()) - // .Returns(new Query(Current.SqlContext)); - // - // var syntax = new SqlCeSyntaxProvider(); - // - // Mock.Get(Current.SqlContext) - // .Setup(x => x.SqlSyntax) - // .Returns(syntax); - // - // var mappers = new MapperCollection(new[] - // { - // new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) - // }); - // - // Mock.Get(Current.SqlContext) - // .Setup(x => x.Mappers) - // .Returns(mappers); - // - // // Testing what happens if the system were configured to only use FIPS-compliant algorithms - // var typ = typeof(CryptoConfig); - // var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); - // var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); - // var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); - // var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; - // - // try - // { - // if (!originalFipsValue) - // { - // haveFld.SetValue(null, true); - // isFld.SetValue(null, true); - // } - // - // var runner = new TestRunner(CtrlFactory); - // var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get); - // - // var obj = JsonConvert.DeserializeObject(response.Item2); - // Assert.AreEqual(-1, obj.UserId); - // } - // finally - // { - // if (!originalFipsValue) - // { - // haveFld.SetValue(null, false); - // isFld.SetValue(null, false); - // } - // } - // } - } -} diff --git a/legacy/Umbraco.Tests/unit-test-log4net.CI.config b/legacy/Umbraco.Tests/unit-test-log4net.CI.config deleted file mode 100644 index d7035032ef..0000000000 --- a/legacy/Umbraco.Tests/unit-test-log4net.CI.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/legacy/Umbraco.Tests/unit-test-logger.config b/legacy/Umbraco.Tests/unit-test-logger.config deleted file mode 100644 index 62fa1353b2..0000000000 --- a/legacy/Umbraco.Tests/unit-test-logger.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs index fcd5ba8ccc..e2ac5ab870 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs @@ -12,6 +12,8 @@ public class EnumSchemaFilter : ISchemaFilter { if (context.Type.IsEnum) { + model.Type = "string"; + model.Format = null; model.Enum.Clear(); foreach (var name in Enum.GetNames(context.Type)) { diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs b/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs index f29e0465f5..d4d63acf2b 100644 --- a/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs @@ -10,7 +10,8 @@ public abstract class QueryOptionBase private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IRequestRoutingService _requestRoutingService; - public QueryOptionBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, + public QueryOptionBase( + IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService) { _publishedSnapshotAccessor = publishedSnapshotAccessor; diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs index 468f52021f..834a68e2e2 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Umbraco.Cms.Core; using Umbraco.Cms.Persistence.EFCore.Migrations; using Umbraco.Extensions; +using Umbraco.Cms.Persistence.EFCore; namespace Umbraco.Cms.Persistence.EFCore.Sqlite; diff --git a/src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs b/src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs new file mode 100644 index 0000000000..0f29e5944c --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/Constants-ProviderNames.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Cms.Persistence.EFCore; + +public static partial class Constants +{ + public static class ProviderNames + { + public const string SQLLite = "Microsoft.Data.Sqlite"; + + public const string SQLServer = "Microsoft.Data.SqlClient"; + } +} diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html index adda418723..83e067ac23 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html @@ -21,5 +21,5 @@ -
+
diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index e22df693e9..e3b7ddef9b 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -34,7 +34,7 @@ public class TypeFinder : ITypeFinder "ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog "System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.", "Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite", - "ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension + "ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension }; private static readonly ConcurrentDictionary TypeNamesCache = new(); diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index d8623a90a7..19edc6f1ba 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -17,7 +17,7 @@ Opret gruppe Slet Deaktivér - Edit settings + Redigér indstillinger Tøm papirkurv Aktivér Eksportér dokumenttype @@ -26,7 +26,7 @@ Redigér i Canvas Log af Flyt - Notificeringer + Notifikationer Offentlig adgang Udgiv Afpublicér @@ -84,7 +84,7 @@ Tillad adgang til at oversætte en node Tillad adgang til at gemme en node Tillad adgang til at oprette en indholdsskabelon - Tillad adgang til at oprette notificeringer for noder + Tillad adgang til at oprette notifikationer for noder Indhold @@ -105,10 +105,10 @@ Domænet '%0%' er nu opdateret eller rediger nuværende domæner + Yderligere understøttes også første niveau af stien efter domænet, f.eks. "example.com/en" eller "/en". ]]> Nedarv Sprog - eller nedarv sprog fra forældre noder. Gælder også
+ eller nedarv sprog fra forældernoder. Gælder også
for den aktuelle node, medmindre et domæne nedenfor også indstiller et sprog.]]>
Domæner @@ -120,7 +120,7 @@ Fortryd indryk afsnit Indsæt formularfelt Indsæt grafisk overskrift - Redigér Html + Redigér HTML Indryk afsnit Kursiv Centrér @@ -154,7 +154,7 @@ Slet tag Fortryd Bekræft - Flere publiseringsmuligheder + Flere publiceringsmuligheder Indsæt @@ -243,21 +243,21 @@ Ups: dette dokument er udgivet, men er ikke i cachen (intern fejl) Kunne ikke hente URL'en Dette dokument er udgivet, men dets URL ville kollidere med indholdet %0% - Dette dokument er udgivet, men dets URL kan ikke dirigeres + Dette dokument er udgivet, men dets URL kan ikke genereres Udgiv Udgivet Udgivet (Ventede ændringer) Udgivelsesstatus - Udgiv med undersider for at udgive %0% og alle sider under og dermed gøre deres indhold offentligt tilgængelige.]]> + Udgiv med undersider for at udgive %0% og alle sider under, og dermed gøre deres indhold offentligt tilgængeligt.]]>
- Udgiv med undersider for at udgive de valgte sprog og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]> + Udgiv med undersider for at udgive de valgte sprog og de samme sprog for sider under, og dermed gøre deres indhold offentligt tilgængeligt.]]> Udgivelsesdato Afpubliceringsdato Fjern dato Vælg dato Sorteringsrækkefølgen er opdateret - For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider + For at sortere, træk siderne eller klik på en af kolonneoverskrifterne. Du kan vælge flere sider ved at holde "shift" eller "control" nede mens du vælger. Statistik @@ -298,8 +298,8 @@ Fjern denne tekstboks Indholdsrod Inkluder ikke-udgivet indhold. - Denne værdi er skjult.Hvis du har brug for adgang til at se denne værdi, bedes du - kontakte din web-administrator. + Denne værdi er skjult. Hvis du har brug for adgang til at se denne værdi, bedes du + kontakte din administrator. Denne værdi er skjult. Hvilke sprog vil du gerne udgive? @@ -334,11 +334,11 @@ Klik for at uploade eller klik her for at vælge filer - Kan ikke uploade denne fil, den har ikke en godkendt filtype - Maks filstørrelse er + Kan ikke uploade denne fil; den har ikke en godkendt filtype + Maksimal filstørrelse er Medie rod - Overordnet og destinations mappe kan ikke være den samme - Oprettelse af mappen under parent med id %0% fejlede + Overordnet og destinationsmappe kan ikke være den samme + Oprettelse af mappen under node med id %0% fejlede Omdøbning af mappen med id %0% fejlede Træk dine filer ind i dropzonen for, at uploade dem til mediebiblioteket. @@ -348,7 +348,7 @@ Opret et nyt medlem Alle medlemmer - Medlemgrupper har ingen yderligere egenskaber til redigering. + Medlemsgrupper har ingen yderligere egenskaber til redigering. Totrinsbekræftelse @@ -358,7 +358,7 @@ Kopiering af medietypen fejlede Flytning af medietypen fejlede - Auto vælg + Vælg automatisk Kopiering af medlemstypen fejlede @@ -370,18 +370,18 @@ Angiv et navn for mappen Vælg en type og skriv en titel - "dokumenttyper".]]> + Indstillinger under "Dokumenttyper".]]> - Document Types within the Settings section.]]> + Dokumenttyper i Indstillinger-sektionen.]]> Den valgte side i træet tillader ikke at sider oprettes under den. Rediger tilladelser for denne dokumenttype. Opret en ny dokumenttype - Dokumenttyper inde i Indstillinger sektionen, ved at ændre Tillad på rodniveau indestillingen under Permissions.]]> + Dokumenttyper inde i Indstillinger sektionen, ved at ændre Tillad på rodniveau indstillingen under Permissions.]]> - "media typer".]]> + "Medietyper".]]> Det valgte medie i træet tillader ikke at medier oprettes under det. Rediger tilladelser for denne medietype. @@ -436,7 +436,7 @@ Udgivelse vil gøre de valgte sider synlige på sitet. Afpublicering vil fjerne de valgte sider og deres undersider fra sitet. - Afpublicering vil fjerne denne side og alle dets undersider fra websitet. + Afpublicering vil fjerne denne side og alle dens undersider fra websitet. Du har ikke-gemte ændringer. Hvis du ændrer dokumenttype, kasseres ændringerne. @@ -871,6 +871,7 @@ Avatar til Overskrift system felt + Primær Blå @@ -1084,8 +1085,8 @@ Relater det kopierede element til originalen - %0%]]> - Notificeringer er gemt for + %0%]]> + Notifikationer er gemt for
     RET       

Opdateringssammendrag:

EnabledEventsUrlTypesEnabledEventsUrlTypes
%6%

Hav en fortsat god dag!

De bedste hilsner fra Umbraco robotten

]]> [%0%] Notificering om %1% udført på %2% - Notificeringer + Notifikationer Handlinger @@ -2104,25 +2105,25 @@ Mange hilsner fra Umbraco robotten Test bestået Test fejlet Åben backoffice søgning - Åben/Luk backoffice hjælp - Åben/Luk dine profil indstillinger + Åben/luk backoffice hjælp + Åben/luk dine profilindstillinger Tilføj domæne på %0% Opret ny node under %0% Opsæt offentlig adgang på %0% Opsæt rettigheder på %0% - Juster soterings rækkefølgen for %0% - Opret indholds skabelon baseret på %0% - Åben kontext menu for + Juster sorteringsrækkefølgen for %0% + Opret indholdsskabelon baseret på %0% + Åben kontekstmenu for Aktivt sprog Skift sprog til Opret ny mappe - Delvist View - Delvist View Macro + Partial View + Partial View Macro Medlem Data type Søg i viderestillings dashboardet - Søg i brugergruppe sektionen - Søg i bruger sektionen + Søg i brugergruppesektionen + Søg i brugersektionen Opret element Opret Rediger @@ -2146,16 +2147,16 @@ Mange hilsner fra Umbraco robotten Referencer - Denne Data Type har ingen referencer. - Brugt i Medie Typer - Brugt i Medlems Typer - Brugt af - Brugt i Dokumenter - Brugt i Medlemmer - Brugt i Medier + Denne Datatype har ingen referencer. + Bruges i Medietyper + Bruges i Medlemstyper + Bruges af + Bruges i Dokumenter + Bruges i Medlemmer + Bruges i Medier - Slet gemte søgning + Slet gemt søgning Log type Vælg alle Fravælg alle @@ -2187,7 +2188,7 @@ Mange hilsner fra Umbraco robotten Slet denne søgning Find logs med request Id Find logs med Namespace - Find logs med maskin navn + Find logs med maskinnavn Åben Henter Hver 2 sekunder @@ -2224,19 +2225,19 @@ Mange hilsner fra Umbraco robotten Redigerings udseende Data modeller katalog udseende - Baggrunds farve + Baggrundsfarve Ikon farve Indholds model Label Speciel visning Vis speciel visning beskrivelsen - Overskrift hvordan denne block præsenteres i backoffice interfacet. Vælg en - .html fil der indeholder din præsensation. + Overskriv hvordan denne blok præsenteres i backoffice. Vælg en + .html fil der indeholder din visning. Indstillings model - Rederings lagets størrelse + Redigeringsvinduets størrelse Tilføj speciel visning - Tilføj instillinger + Tilføj indstillinger %0%?]]> @@ -2261,7 +2262,7 @@ Mange hilsner fra Umbraco robotten Skjul indholdseditoren Skjul indholds redigerings knappen samt indholdseditoren i Blok Redigerings vinduet Direkte redigering - Tilføjer direkte redigering a det første felt. Yderligere felter optræder kun i redigerings vinduet. + Tilføjer direkte redigering af det første felt. Yderligere felter optræder kun i redigerings vinduet. Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem? Annuller oprettelse? @@ -2284,11 +2285,11 @@ Mange hilsner fra Umbraco robotten Alle blokke, der er oprettet i dette område, vil blive slettet. Layout-opsætning Struktur - Størrelses opsætning + Størrelsesopsætning Tilgængelige kolonne-størrelser - Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindre ikke blokken i at optræde i et mindre område. - TIlgængelige række-størrelser - Vælg hvor mange rækker denne blok på optage i layoutet. + Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindrer ikke blokken i at optræde i et mindre område. + Tilgængelige række-størrelser + Vælg hvor mange rækker denne blok må optage i layoutet. Tillad på rodniveau Gør denne blok tilgængelig i layoutets rodniveau. Hvis dette ikke er valgt, kan denne blok kun bruges inden for andre blokkes definerede områder. Blok-områder @@ -2302,11 +2303,11 @@ Mange hilsner fra Umbraco robotten - + Træk for at skalere Tilføj indhold label - Overskriv labellen for tilføj indholds knappen i dette område. - Tilføj skalerings muligheder + Overskriv labelen for tilføj indholds knappen i dette område. + Tilføj skaleringsmuligheder Tilføj Blok Tilføj gruppe Tilføj gruppe eller Blok @@ -2319,14 +2320,14 @@ Mange hilsner fra Umbraco robotten Avanceret Tilladelser Installer demo konfiguration - Dette indeholder Blokke for Overskrift, Beriget-Tekst, Billede og To-Koloners-Layout.]]> + Dette indeholder Blokke for Overskrift, Formateret Tekst, Billede og To-Kolonners-Layout.]]> Installer - Sortings tilstand - Afslut sortings tilstand + Sortingstilstand + Afslut sortingstilstand Dette område alias skal være unikt sammenlignet med andre områder af denne Blok. Konfigurer område Slet område - Tilføj mulighed for %0% koloner + Tilføj mulighed for %0% kolonner Indsæt Blok Vis på linje med tekst @@ -2358,12 +2359,12 @@ Mange hilsner fra Umbraco robotten Vis i nyt vindue Åben forhåndsvisning i nyt vindue Forhåndsvisning af indholdet? - Du har afslutet forhåndsvisning, vil du starte forhåndsvisning igen for at + Du har afsluttet forhåndsvisning, vil du starte forhåndsvisning igen for at se seneste gemte version af indholdet? Se udgivet indhold Se udgivet indhold? - Du er i forhåndsvisning, vil du afslutte for at se den udgivet + Du er i forhåndsvisning, vil du afslutte for at se den udgivne version? Se udgivet version @@ -2373,7 +2374,7 @@ Mange hilsner fra Umbraco robotten Mappeoprettelse Filskrivning for pakker Filskrivning - Medie mappeoprettelse + Mediemappeoprettelse resultat diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 00cb113ddf..35ccb53a3b 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -56,6 +56,8 @@ Create Content Template Resend Invitation Hide unavailable options + Change Data Type + Edit content Content @@ -158,6 +160,7 @@ Confirm More publishing options Submit + Generate models and close Media deleted @@ -198,6 +201,8 @@ Save Save History (all variants) + Content unpublished for languages: %0% + Unpublish The folder name cannot contain illegal characters. @@ -325,6 +330,12 @@ Create new Paste from clipboard This item is in the Recycle Bin + No content can be added for this item + Save is not allowed + Publish is not allowed + Send for approval is not allowed + Schedule is not allowed + Unpublish is not allowed %0%]]> @@ -349,12 +360,19 @@ Failed to rename the folder with id %0% Drag and drop your file(s) into the area One or more file security validations have failed + Parent and destination folders cannot be the same + Upload is not allowed in this location. Create a new member All Members Member groups have no additional properties for editing. Two-Factor Authentication + A member with this login already exists + The member is already in group '%0%' + The member already has a password set + Lockout is not enabled for this member + The member is not in group '%0%' Failed to copy content type @@ -476,7 +494,6 @@ Link Anchor / querystring Name - Manage hostnames Close this window Are you sure you want to delete %0% of %1% items]]> @@ -581,6 +598,7 @@ Yes, remove You are deleting the layout Modifying layout will result in loss of data for any existing content that is based on this configuration. + Select configuration @@ -689,6 +707,9 @@ Select the folder to move to in the tree structure below was moved underneath + Changing a property editor on a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json. + %0% will delete the properties and their data from the following items]]> + I understand this action will delete the properties and data based on this Data Type Your data has been saved, but before you can publish this page there are some @@ -729,6 +750,8 @@ Please place cursor at the left of the two cells you wish to merge You cannot split a cell that hasn't been merged. This property is invalid + An unknown failure has occurred + Optimistic concurrency failure, object has been modified About @@ -893,6 +916,13 @@ Last Updated Skip to menu Skip to content + Primary + Change + Crop section + Generic + Media + Revert + Validate Blue @@ -919,6 +949,7 @@ General Editor Toggle allow culture variants + Add tab Background colour @@ -1368,6 +1399,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont so uninstall with caution. If in doubt, contact the package author.]]> Package version Verified to work on Umbraco Cloud + Package migrations have successfully completed. + All package migrations have successfully completed. Paste with full formatting (Not recommended) @@ -1425,6 +1458,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Publish to publish %0% and thereby making its content publicly available.

You can publish this page and all its subpages by checking Include unpublished subpages below. ]]>
+ Insufficient user permissions to publish all descendant documents + %0% could not be published because the item is in the recycle bin. + Validation failed for required language '%0%'. This language was saved but not published. You have not configured any approved colours @@ -1517,25 +1553,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Rollback to Select version View + + Versions + Current draft version + Current published version Edit script file - Concierge Content - Courier - Developer Forms - Help - Umbraco Configuration Wizard Media Members - Newsletters Packages Marketplace Settings - Statistics Translation Users @@ -1576,6 +1609,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont column headers to sort the entire collection of items
+ This node has no child nodes to sort Validation @@ -1587,7 +1621,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Operation was cancelled by a 3rd party add-in This file is being uploaded as part of a folder, but creating a new folder is not allowed here Creating a new folder is not allowed here - Publishing was cancelled by a 3rd party add-in Property type already exists Property type created DataType: %1%]]> @@ -1601,7 +1634,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Stylesheet saved without any errors Datatype saved Dictionary item saved - Publishing failed because the parent page isn't published Content published and visible on the website Content Template saved @@ -1668,7 +1700,25 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Your system information has successfully been copied to the clipboard Could not copy your system information to the clipboard - Webhook saved + Webhook saved + Saved. To view the changes please reload your browser + %0% documents published and visible on the website + %0% published and visible on the website + %0% documents published for languages %1% and visible on the website + A schedule for publishing has been updated + %0% saved + %0% changes have been sent for approval + Content variation %0% unpublished + The mandatory language '%0%' was unpublished. All languages for this content item are now unpublished. + Cannot publish the document since the required '%0%' is not published + Validation failed for language '%0%' + The release date cannot be in the past + Cannot schedule the document for publishing since the required '%0%' is not published + Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language + The expire date cannot be in the past + The expire date cannot be before the release date + An error occurred while enabling version cleanup for %0% + An error occurred while disabling version cleanup for %0% Add style @@ -1932,6 +1982,18 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Prevent cleanup Enable cleanup NOTE! The cleanup of historically content versions are disabled globally. These settings will not take effect before it is enabled.]]> + You can't move the group %0% to this tab because the group will get the same alias as a tab: "%1%". Rename the group to continue. + Available configurations + Create a new configuration + %0%?]]> + %0%?]]> + %0%?]]> + This will also delete all items below this tab. + This will also delete all items below this group. + Add tab + Convert to tab + Drag properties here to place directly on the tab + Changing a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json. Create webhook @@ -1967,6 +2029,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Fall back language none + %0% is shared across languages and segments.]]> + %0% is shared across all languages.]]> + %0% is shared across all segments.]]> + Shared: Languages + Shared: Segments Add parameter @@ -2320,10 +2387,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont This two-factor provider is now disabled Something went wrong with trying to disable this two-factor provider Do you want to disable this two-factor provider for this user? + A user with this login already exists + The password must have at least one digit ('0'-'9') + The password must have at least one lowercase ('a'-'z') + The password must have at least one non alphanumeric character + The password must use at least %0% different characters + The password must have at least one uppercase ('A'-'Z') + The password must be at least %0% characters long + Allow access to all languages + The user already has a password set + The user is already in group '%0%' + Lockout is not enabled for this user + The user is not in group '%0%' + Configure Two-Factor Validation - No validation Validate as an email address Validate as a number Validate as a URL @@ -2350,6 +2429,14 @@ To manage your website, simply open the Umbraco backoffice and start adding cont %1% more.]]> %1% too many.]]> The content amount requirements are not met for one or more areas. + Invalid member group name + Invalid user group name + Invalid token + Invalid username + Email '%0%' is already taken + User group name '%0%' is already taken + Member group name '%0%' is already taken + Username '%0%' is already taken + + +
+ +
+ At least {{installer.current.model.minCharLength}} characters long diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index 29294a08c4..5688ea1b12 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -1,5 +1,5 @@
-
+
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index ff647ed411..24a3cb4265 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -1,8 +1,8 @@
- diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html index 5a6a0fc766..48430b579e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html @@ -12,7 +12,7 @@

-
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index a58aac7b8d..fd2a87d5b2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -1,5 +1,5 @@
-
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less index 1e9538d670..42ba81a6bb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less @@ -1,7 +1,7 @@ .umb-block-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - grid-auto-rows: minmax(30px, auto); + grid-auto-rows: minmax(150px, auto); gap: 20px; margin-bottom: 20px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less index a40983e07c..7be8008b8a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less @@ -104,6 +104,7 @@ umb-block-card { font-size: 14px; color: @ui-action-type; margin: 0 16px -1px; + word-wrap: break-word; } .__subname { color: @gray-4; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html index 4576a30418..637404718b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle-group.html @@ -4,10 +4,11 @@ checked="item.checked" disabled="item.disabled" input-id="{{item.inputId}}" + aria-labelledby="{{item.labelId}}" on-click="change(item)">
-
+
{{ item.description }}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less index 13f934c251..b0ce88a446 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less @@ -5,5 +5,6 @@ img { width: 100%; + max-height: 50vh; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index 339ee29c90..4462af5429 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -6,7 +6,8 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index 985eec7e99..89f95f688d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -20,7 +20,7 @@

- +  + {{item.name}} - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js index 435ece4bb9..df26473720 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js @@ -9,8 +9,9 @@ function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) { $scope.model = { - folderName: "", - creatingFolder: false + allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container', + folderName: "", + creatingFolder: false }; var node = $scope.currentNode; diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html index bd6ef087f4..36a0fce9f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html @@ -10,20 +10,21 @@
    -
  • -
  • -
  • -
@@ -35,6 +36,7 @@ Do something else
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js index 550ad7ed35..4210d162d3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js @@ -9,8 +9,9 @@ function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) { $scope.model = { - folderName: "", - creatingFolder: false + allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container', + folderName: "", + creatingFolder: false }; var node = $scope.currentNode; diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html index 5bcf6c42bf..ac6f633db8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html @@ -1,4 +1,5 @@
+ - + + Add Block + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less index afb4316ce8..9ceadc7a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less @@ -1,5 +1,11 @@ .umb-block-list-block-configuration { + uui-button { + --uui-button-border-radius: 6px; + font-weight: 700; + min-height: 80px; + } + .__add-button { position: relative; display: inline-flex; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less index d2d875aa94..40454189db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less @@ -67,7 +67,7 @@ &.--noValue { text-align: center; border-radius: @baseBorderRadius; - color: white; + color: @ui-action-discreet-type; transition: color 120ms; &:hover, &:focus { color: @ui-action-discreet-type-hover; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index 69a2e42f19..5f6f784f55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -1,38 +1,46 @@
-
-
+
+
- - + + - - -
-
- - - -
+ +
-
-
- -
-
-
-
#{{item.value}}
- -
-
-
- - -
-
+
+ + +
+
+
+
+ +
+
+
+
#{{item.value}}
+ +
+
+
+ + +
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index 5406927d38..b16722e7de 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -1,191 +1,228 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController", - function ($scope, angularHelper, $element, eventsService) { + function ($scope, angularHelper, $element, eventsService) { - const vm = this; + const vm = this; - vm.add = add; - vm.remove = remove; - vm.edit = edit; - vm.cancel = cancel; + vm.add = add; + vm.addOnEnter = addOnEnter; + vm.validateLabel = validateLabel; + vm.remove = remove; + vm.edit = edit; + vm.cancel = cancel; - vm.show = show; - vm.hide = hide; - vm.change = change; + vm.show = show; + vm.hide = hide; + vm.change = change; - vm.labelEnabled = false; - vm.editItem = null; + vm.labelEnabled = false; + vm.editItem = null; - // NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. - const defaultColor = "000000"; - const defaultLabel = null; + // NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. + const defaultColor = "000000"; + const defaultLabel = null; - $scope.newColor = defaultColor; - $scope.newLabel = defaultLabel; - $scope.hasError = false; - $scope.focusOnNew = false; + $scope.newColor = defaultColor; + $scope.newLabel = defaultLabel; + $scope.colorHasError = false; + $scope.labelHasError = false; + $scope.focusOnNew = false; - $scope.options = { - type: "color", - color: defaultColor, - allowEmpty: false, - showAlpha: false - }; + $scope.options = { + type: "color", + color: defaultColor, + allowEmpty: false, + showAlpha: false + }; - function hide() { - // show the add button - $element.find(".btn.add").show(); + function hide() { + // show the add button + $element.find(".btn.add").show(); + } + + function show() { + // hide the add button + $element.find(".btn.add").hide(); + } + + function change(color) { + angularHelper.safeApply($scope, function () { + if (color) { + $scope.newColor = color.toHexString().trimStart("#"); + $scope.colorHasError = !colorIsValid(); } + }); + } - function show() { - // hide the add button - $element.find(".btn.add").hide(); - } - - function change(color) { - angularHelper.safeApply($scope, function () { - if (color) { - $scope.newColor = color.toHexString().trimStart("#"); - } - }); - } - - var evts = []; - evts.push(eventsService.on("toggleValue", function (e, args) { - if (args.inputId === "useLabel") { - vm.labelEnabled = args.value; - } - })); - - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - - if (!Utilities.isArray($scope.model.value)) { - //make an array from the dictionary - var items = []; - for (var i in $scope.model.value) { - var oldValue = $scope.model.value[i]; - if (Object.prototype.hasOwnProperty.call(oldValue, "value")) { - items.push({ - value: oldValue.value, - label: oldValue.label, - sortOrder: oldValue.sortOrder, - id: i - }); - } else { - items.push({ - value: oldValue, - label: oldValue, - sortOrder: oldValue.sortOrder, - id: i - }); - } - } - - //ensure the items are sorted by the provided sort order - items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); - - //now make the editor model the array - $scope.model.value = items; - } - - // ensure labels - for (var ii = 0; ii < $scope.model.value.length; ii++) { - var item = $scope.model.value[ii]; - item.label = Object.prototype.hasOwnProperty.call(item, "label") ? item.label : item.value; - } - - function validLabel(label) { - return label !== null && typeof label !== "undefined" && label !== "" && label.length && label.length > 0; - } - - function remove(item, evt) { - - evt.preventDefault(); - - $scope.model.value = _.reject($scope.model.value, function (x) { - return x.value === item.value && x.label === item.label; - }); - - setDirty(); - } - - function add(evt) { - evt.preventDefault(); - - if ($scope.newColor) { - var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor; - var exists = _.find($scope.model.value, function (item) { - return item != vm.editItem && (item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase()); - }); - if (!exists) { - if (vm.editItem == null) { - $scope.model.value.push({ - value: $scope.newColor, - label: newLabel - }); - } else { - - if(vm.editItem.value === vm.editItem.label && vm.editItem.value === newLabel) { - vm.editItem.label = $scope.newColor; - - } - else { - vm.editItem.label = newLabel; - } - - vm.editItem.value = $scope.newColor; - - vm.editItem = null; - } - - $scope.newLabel = ""; - $scope.hasError = false; - $scope.focusOnNew = true; - setDirty(); - return; - } - - // there was an error, do the highlight (will be set back by the directive) - $scope.hasError = true; - } - } - - function edit(item, evt) { - evt.preventDefault(); - - vm.editItem = item; - - $scope.newColor = item.value; - $scope.newLabel = item.label; - } - - function cancel(evt) { - evt.preventDefault(); - - vm.editItem = null; - $scope.newColor = defaultColor; - $scope.newLabel = defaultLabel; - } - - function setDirty() { - if (vm.modelValueForm) { - vm.modelValueForm.selectedColor.$setDirty(); - } - } - - $scope.sortableOptions = { - axis: 'y', - containment: 'parent', - cursor: 'move', - //handle: ".handle, .thumbnail", - items: '> div.control-group', - tolerance: 'pointer', - update: function () { - setDirty(); - } - }; + var evts = []; + evts.push(eventsService.on("toggleValue", function (e, args) { + if (args.inputId === "useLabel") { + vm.labelEnabled = args.value; + } + })); + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } }); + + if (!Utilities.isArray($scope.model.value)) { + //make an array from the dictionary + var items = []; + for (var i in $scope.model.value) { + var oldValue = $scope.model.value[i]; + if (Object.prototype.hasOwnProperty.call(oldValue, "value")) { + items.push({ + value: oldValue.value, + label: oldValue.label, + sortOrder: oldValue.sortOrder, + id: i + }); + } else { + items.push({ + value: oldValue, + label: oldValue, + sortOrder: oldValue.sortOrder, + id: i + }); + } + } + + //ensure the items are sorted by the provided sort order + items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); + + //now make the editor model the array + $scope.model.value = items; + } + + // ensure labels + for (var ii = 0; ii < $scope.model.value.length; ii++) { + var item = $scope.model.value[ii]; + item.label = Object.prototype.hasOwnProperty.call(item, "label") ? item.label : item.value; + } + + function remove(item, evt) { + + evt.preventDefault(); + + $scope.model.value = _.reject($scope.model.value, function (x) { + return x.value === item.value && x.label === item.label; + }); + + setDirty(); + } + + function colorIsValid() { + var colorExists = _.find($scope.model.value, function (item) { + return item != vm.editItem && item.value.toUpperCase() === $scope.newColor.toUpperCase(); + }); + + return colorExists ? false : true; + } + + function getLabel() { + var validLabel = $scope.newLabel !== null && typeof $scope.newLabel !== "undefined" && $scope.newLabel !== "" && $scope.newLabel.length && $scope.newLabel.length > 0; + return validLabel ? $scope.newLabel : $scope.newColor; + } + + function labelIsValid() { + var label = getLabel(); + label = label.toUpperCase(); + + var labelExists = _.find($scope.model.value, function (item) { + return item != vm.editItem && item.label.toUpperCase() === label; + }); + + return labelExists ? false : true; + } + + function validateLabel() { + $scope.labelHasError = !labelIsValid(); + } + + function addOnEnter(evt) { + if (evt.keyCode === 13) { + add(evt); + } + } + + function add(evt) { + evt.preventDefault(); + + if ($scope.newColor) { + + $scope.colorHasError = !colorIsValid(); + $scope.labelHasError = !labelIsValid(); + + if ($scope.labelHasError || $scope.colorHasError) { + return; + } + + var newLabel = getLabel(); + + if (vm.editItem == null) { + $scope.model.value.push({ + value: $scope.newColor, + label: newLabel + }); + } else { + + if (vm.editItem.value === vm.editItem.label && vm.editItem.value === newLabel) { + vm.editItem.label = $scope.newColor; + + } + else { + vm.editItem.label = newLabel; + } + + vm.editItem.value = $scope.newColor; + + vm.editItem = null; + } + + $scope.newLabel = ""; + $scope.colorHasError = false; + $scope.labelHasError = false; + $scope.focusOnNew = true; + setDirty(); + return; + + } + } + + function edit(item, evt) { + evt.preventDefault(); + + vm.editItem = item; + + $scope.newColor = item.value; + $scope.newLabel = item.label; + } + + function cancel(evt) { + evt.preventDefault(); + + vm.editItem = null; + $scope.newColor = defaultColor; + $scope.newLabel = defaultLabel; + } + + function setDirty() { + if (vm.modelValueForm) { + vm.modelValueForm.selectedColor.$setDirty(); + } + } + + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + cursor: 'move', + //handle: ".handle, .thumbnail", + items: '> div.control-group', + tolerance: 'pointer', + update: function () { + setDirty(); + } + }; + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index 453347bc1b..80d0cc16c9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -20,8 +20,9 @@ angular.module('umbraco') var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; $scope.acceptFileExt = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); + /** - * Called when the umgImageGravity component updates the focal point value + * Called when the umbImageGravity component updates the focal point value * @param {any} left * @param {any} top */ @@ -81,7 +82,7 @@ angular.module('umbraco') function imageLoaded(isCroppable, hasDimensions) { $scope.isCroppable = isCroppable; $scope.hasDimensions = hasDimensions; - }; + } /** * Called when the file collection changes diff --git a/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs index f498189ff0..3a4715b537 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs @@ -73,7 +73,7 @@ public class UmbTwoFactorLoginController : SurfaceController MemberIdentityUser? user = await _memberSignInManager.GetTwoFactorAuthenticationUserAsync(); if (user == null!) { - _logger.LogWarning("PostVerify2FACode :: No verified member found, returning 404"); + _logger.LogWarning("Verify2FACode :: No verified member found, returning 404"); return NotFound(); } diff --git a/tests/Umbraco.TestData/LoadTestController.cs b/tests/Umbraco.TestData/LoadTestController.cs index 741fe4e94b..676b5c0256 100644 --- a/tests/Umbraco.TestData/LoadTestController.cs +++ b/tests/Umbraco.TestData/LoadTestController.cs @@ -7,6 +7,7 @@ using System.Threading; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -99,7 +100,7 @@ public class LoadTestController : Controller private readonly IDataTypeService _dataTypeService; private readonly IFileService _fileService; private readonly IHostApplicationLifetime _hostApplicationLifetime; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IHostEnvironment _hostEnvironment; private readonly IShortStringHelper _shortStringHelper; public LoadTestController( @@ -108,7 +109,7 @@ public class LoadTestController : Controller IDataTypeService dataTypeService, IFileService fileService, IShortStringHelper shortStringHelper, - IHostingEnvironment hostingEnvironment, + IHostEnvironment hostEnvironment, IHostApplicationLifetime hostApplicationLifetime) { _contentTypeService = contentTypeService; @@ -116,7 +117,7 @@ public class LoadTestController : Controller _dataTypeService = dataTypeService; _fileService = fileService; _shortStringHelper = shortStringHelper; - _hostingEnvironment = hostingEnvironment; + _hostEnvironment = hostEnvironment; _hostApplicationLifetime = hostApplicationLifetime; } @@ -321,7 +322,7 @@ public class LoadTestController : Controller public IActionResult ColdBootRestart() { Directory.Delete( - _hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData, "DistCache")), + _hostEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData, "DistCache")), true); DoRestart(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 28bcb052fc..02c46ac329 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -23,7 +23,7 @@ "prompt": "^1.2.0", "tslib": "^2.4.0", "typescript": "^4.8.3", - "wait-on": "^6.0.1" + "wait-on": "^7.2.0" } }, "node_modules/@colors/colors": { @@ -177,12 +177,14 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/axios": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", - "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "dev": true, "dependencies": { - "follow-redirects": "^1.14.7" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -364,9 +366,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "dev": true, "funding": [ { @@ -563,15 +565,15 @@ "dev": true }, "node_modules/joi": { - "version": "17.6.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.3.tgz", - "integrity": "sha512-YlQsIaS9MHYekzf1Qe11LjTkNzx9qhYluK3172z38RxYoAUf82XMX1p1DG1H4Wtk2ED/vPdSn9OggqtDu+aTow==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "dev": true, "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", + "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, @@ -635,9 +637,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -777,6 +779,12 @@ "node": ">= 6.0.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -867,9 +875,9 @@ } }, "node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -930,22 +938,22 @@ } }, "node_modules/wait-on": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", - "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", "dev": true, "dependencies": { - "axios": "^0.25.0", - "joi": "^17.6.0", + "axios": "^1.6.1", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^7.5.4" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" }, "bin": { "wait-on": "bin/wait-on" }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" } }, "node_modules/webidl-conversions": { diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 2e86d70d5e..adcdf64f1d 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -16,7 +16,7 @@ "del": "^6.0.0", "ncp": "^2.0.0", "prompt": "^1.2.0", - "wait-on": "^6.0.1" + "wait-on": "^7.2.0" }, "dependencies": { "@umbraco/json-models-builders": "^1.0.6", diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts index 882eaf1bbf..2239613571 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts @@ -36,6 +36,19 @@ test("Check the youtube link works as expected", async ({ page, umbracoUi }) => } else { await expect(page).toHaveURL(/.*UmbracoLearningBase/); + await page.close(); } }); + test("Check the Our Umbraco link works as expected", async ({ page, umbracoUi }) => { + // Action + await umbracoUi.clickElement(umbracoUi.getGlobalHelp()); + let ourUmbracoLink = await page.locator('[key="help_umbracoForum"]'); + await ourUmbracoLink.click(); + let ourUmbraco = page.waitForEvent("popup"); + let ourUmbracoPopup = await ourUmbraco; + + //Assert + await expect(ourUmbracoPopup).toHaveURL(/.*our.umbraco.com/); + await ourUmbracoPopup.close(); + }); }); \ No newline at end of file diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs new file mode 100644 index 0000000000..3447dff634 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs @@ -0,0 +1,611 @@ +using Examine; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Examine.DependencyInjection; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Infrastructure.Search; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; +using Umbraco.Cms.Web.BackOffice.Security; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class BackOfficeExamineSearcherTests : ExamineBaseTest +{ + [SetUp] + public void Setup() + { + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.InternalIndexName)); + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.ExternalIndexName)); + var httpContext = new DefaultHttpContext(); + httpContext.RequestServices = Services; + Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext); + } + + [TearDown] + public void TearDown() + { + // When disposing examine, it does a final write, which ends up locking the file if the indexing is not done yet. So we have this wait to circumvent that. + Thread.Sleep(1500); + // Sometimes we do not dispose all services in time and the test fails because the log file is locked. Resulting in all other tests failing as well + Services.DisposeIfDisposable(); + } + + private IBackOfficeExamineSearcher BackOfficeExamineSearcher => GetRequiredService(); + + private IContentTypeService ContentTypeService => GetRequiredService(); + + private ILocalizationService LocalizationService => GetRequiredService(); + + private ContentService ContentService => (ContentService)GetRequiredService(); + + private IUserStore BackOfficeUserStore => + GetRequiredService>(); + + private IBackOfficeSignInManager BackOfficeSignInManager => GetRequiredService(); + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder + .AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddExamineIndexes(); + builder.AddBackOfficeIdentity(); + builder.Services.AddHostedService(); + } + + private IEnumerable BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0) => + BackOfficeExamineSearcher.Search(query, UmbracoEntityTypes.Document, + pageSize, pageIndex, out _, ignoreUserStartNodes: true); + + private async Task SetupUserIdentity(string userId) + { + var identity = + await BackOfficeUserStore.FindByIdAsync(userId, CancellationToken.None); + await BackOfficeSignInManager.SignInAsync(identity, false); + } + + private async Task CreateDefaultPublishedContent(string contentName) + { + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .Build(); + + var createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + return createdContent; + } + + private async Task CreateDefaultPublishedContentWithTwoLanguages(string englishNodeName, string danishNodeName) + { + const string usIso = "en-US"; + const string dkIso = "da"; + + var langDa = new LanguageBuilder() + .WithCultureInfo(dkIso) + .Build(); + LocalizationService.Save(langDa); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .WithContentVariation(ContentVariation.Culture) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithId(0) + .WithCultureName(usIso, englishNodeName) + .WithCultureName(dkIso, danishNodeName) + .WithContentType(contentType) + .Build(); + var createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + return createdContent; + } + + [Test] + public async Task Search_Published_Content_With_Empty_Query() + { + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + // Arrange + const string contentName = "TestContent"; + await CreateDefaultPublishedContent(contentName); + + string query = string.Empty; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + Assert.AreEqual(0, actual.Count()); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + await CreateDefaultPublishedContent(contentName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentName); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Non_Existing_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + await CreateDefaultPublishedContent(contentName); + + string query = "ContentTest"; + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + Assert.AreEqual(0, actual.Count()); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Content_Id() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "RandomContentName"; + PublishResult createdContent = await CreateDefaultPublishedContent(contentName); + + string contentId = createdContent.Content.Id.ToString(); + + string query = contentId; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentName); + Assert.AreEqual(searchResults.First().Id, contentId); + } + + [Test] + public async Task Search_Two_Published_Content_With_Similar_Names_By_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestName Original"; + const string secondContentName = "TestName Copy"; + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var firstContent = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(firstContent), Constants.UmbracoIndexes.InternalIndexName); + + var secondContent = new ContentBuilder() + .WithId(0) + .WithName(secondContentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(secondContent), Constants.UmbracoIndexes.InternalIndexName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(2, searchResults.Count()); + // Checks if the first content in the search is the original content + Assert.AreEqual(searchResults.First().Id, firstContent.Id.ToString()); + // Checks if the score for the original name is higher than the score for the copy + Assert.Greater(searchResults.First().Score, searchResults.Last().Score); + } + + [Test] + public async Task Search_For_Child_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "ParentTestContent"; + const string childContentName = "ChildTestContent"; + + var contentType = new ContentTypeBuilder() + .WithName("Document") + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + var childContent = new ContentBuilder() + .WithName(childContentName) + .WithContentType(contentType) + .WithParentId(content.Id) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childContent), Constants.UmbracoIndexes.InternalIndexName); + + string parentQuery = content.Id.ToString(); + + string childQuery = childContent.Id.ToString(); + + // Act + IEnumerable parentContentActual = BackOfficeExamineSearch(parentQuery); + IEnumerable childContentActual = BackOfficeExamineSearch(childQuery); + + // Assert + IEnumerable contentActual = parentContentActual.ToArray(); + IEnumerable searchResults = childContentActual.ToArray(); + Assert.AreEqual(1, contentActual.Count()); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(contentActual.First().Values["nodeName"], contentName); + Assert.AreEqual(searchResults.First().Values["nodeName"], childContentName); + } + + [Test] + public async Task Search_For_Child_In_Child_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "ParentTestContent"; + const string childContentName = "ChildTestContent"; + const string childChildContentName = "ChildChildTestContent"; + + var contentType = new ContentTypeBuilder() + .WithName("Document") + .Build(); + ContentTypeService.Save(contentType); + + var content = new ContentBuilder() + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + var childContent = new ContentBuilder() + .WithName(childContentName) + .WithContentType(contentType) + .WithParentId(content.Id) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childContent), Constants.UmbracoIndexes.InternalIndexName); + + var childChildContent = new ContentBuilder() + .WithName(childChildContentName) + .WithContentType(contentType) + .WithParentId(childContent.Id) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childChildContent), Constants.UmbracoIndexes.InternalIndexName); + + string parentQuery = content.Id.ToString(); + string childQuery = childContent.Id.ToString(); + string childChildQuery = childChildContent.Id.ToString(); + + // Act + IEnumerable parentContentActual = BackOfficeExamineSearch(parentQuery); + IEnumerable childContentActual = BackOfficeExamineSearch(childQuery); + IEnumerable childChildContentActual = BackOfficeExamineSearch(childChildQuery); + + IEnumerable parentSearchResults = parentContentActual.ToArray(); + IEnumerable childSearchResults = childContentActual.ToArray(); + IEnumerable childChildSearchResults = childChildContentActual.ToArray(); + + // Assert + Assert.AreEqual(1, parentSearchResults.Count()); + Assert.AreEqual(1, childSearchResults.Count()); + Assert.AreEqual(1, childChildSearchResults.Count()); + Assert.AreEqual(parentSearchResults.First().Values["nodeName"], contentName); + Assert.AreEqual(childSearchResults.First().Values["nodeName"], childContentName); + Assert.AreEqual(childChildSearchResults.First().Values["nodeName"], childChildContentName); + } + + [Test] + public async Task Search_Published_Content_With_Query_With_Content_Name_No_User_Logged_In() + { + // Arrange + const string contentName = "TestContent"; + + PublishResult createdContent = await CreateDefaultPublishedContent(contentName); + + string query = createdContent.Content.Id.ToString(); + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + Assert.AreEqual(0, actual.Count()); + } + + // Multiple Languages + [Test] + public async Task Search_Published_Content_By_Content_Name_With_Two_Languages() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string usIso = "en-US"; + const string dkIso = "da"; + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + var langDa = new LanguageBuilder() + .WithCultureInfo(dkIso) + .Build(); + LocalizationService.Save(langDa); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .WithContentVariation(ContentVariation.Culture) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName); + + var content = new ContentBuilder() + .WithId(0) + .WithCultureName(usIso, englishNodeName) + .WithCultureName(dkIso, danishNodeName) + .WithContentType(contentType) + .Build(); + PublishResult createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName); + + string query = createdContent.Content.Id.ToString(); + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + [Test] + public async Task Search_For_Published_Content_Name_With_Two_Languages_By_Default_Language_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName); + + string query = englishNodeName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + [Test] + public async Task Search_For_Published_Content_Name_With_Two_Languages_By_Non_Default_Language_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName); + + string query = danishNodeName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + [Test] + public async Task Search_Published_Content_With_Two_Languages_By_Id() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string englishNodeName = "EnglishNode"; + const string danishNodeName = "DanishNode"; + + var contentNode = await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName); + + string query = contentNode.Content.Id.ToString(); + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + var nodeNameDa = searchResults.First().Values["nodeName_da"]; + var nodeNameEn = searchResults.First().Values["nodeName_en-us"]; + Assert.AreEqual(englishNodeName, nodeNameEn); + Assert.AreEqual(danishNodeName, nodeNameDa); + } + + // Check All Indexed Values + [Test] + public async Task Check_All_Indexed_Values_For_Published_Content_With_No_Properties() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + ContentTypeService.Save(contentType); + + var contentNode = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(contentNode), Constants.UmbracoIndexes.InternalIndexName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + + string contentNodePublish = string.Empty; + if (contentNode.Published) + { + contentNodePublish = "y"; + } + + string contentTypeCultureVariations = string.Empty; + + if (contentType.Variations == ContentVariation.Nothing) + { + contentTypeCultureVariations = "n"; + } + + Assert.Multiple(() => + { + Assert.AreEqual(searchResults.First().Values["__NodeId"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__IndexType"], "content"); + Assert.AreEqual(searchResults.First().Values["__NodeTypeAlias"], contentNode.ContentType.Alias); + Assert.AreEqual(searchResults.First().Values["__Published"], contentNodePublish); + Assert.AreEqual(searchResults.First().Values["id"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__Key"], contentNode.Key.ToString()); + Assert.AreEqual(searchResults.First().Values["parentID"], contentNode.ParentId.ToString()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentNode.Name); + Assert.AreEqual(searchResults.First().Values["__VariesByCulture"], contentTypeCultureVariations); + Assert.AreEqual(searchResults.First().Values["__Icon"], contentNode.ContentType.Icon); + }); + } + + [Test] + public async Task Check_All_Indexed_Values_For_Published_Content_With_Properties() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + const string contentName = "TestContent"; + const string propertyEditorName = "TestBox"; + + var contentType = new ContentTypeBuilder() + .WithId(0) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithName(propertyEditorName) + .WithAlias("testBox") + .Done() + .Build(); + ContentTypeService.Save(contentType); + + var contentNode = new ContentBuilder() + .WithId(0) + .WithName(contentName) + .WithContentType(contentType) + .WithPropertyValues(new { testBox = "TestValue" }) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(contentNode), Constants.UmbracoIndexes.InternalIndexName); + + string query = contentName; + + // Act + IEnumerable actual = BackOfficeExamineSearch(query); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + + string contentNodePublish = string.Empty; + string contentTypeCultureVariations = string.Empty; + + if (contentNode.Published) + { + contentNodePublish = "y"; + } + + if (contentType.Variations == ContentVariation.Nothing) + { + contentTypeCultureVariations = "n"; + } + + Assert.Multiple(() => + { + Assert.AreEqual(searchResults.First().Values["__NodeId"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__IndexType"], "content"); + Assert.AreEqual(searchResults.First().Values["__NodeTypeAlias"], contentNode.ContentType.Alias); + Assert.AreEqual(searchResults.First().Values["__Published"], contentNodePublish); + Assert.AreEqual(searchResults.First().Values["id"], contentNode.Id.ToString()); + Assert.AreEqual(searchResults.First().Values["__Key"], contentNode.Key.ToString()); + Assert.AreEqual(searchResults.First().Values["parentID"], contentNode.ParentId.ToString()); + Assert.AreEqual(searchResults.First().Values["nodeName"], contentNode.Name); + Assert.AreEqual(searchResults.First().Values["__VariesByCulture"], contentTypeCultureVariations); + Assert.AreEqual(searchResults.First().Values["__Icon"], contentNode.ContentType.Icon); + }); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index 381464faf3..3d97f4ede0 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -1,4 +1,5 @@ using System.Data; +using Examine; using Examine.Lucene.Providers; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -25,6 +26,8 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest protected IRuntimeState RunningRuntimeState { get; } = Mock.Of(x => x.Level == RuntimeLevel.Run); + protected IExamineManager ExamineManager => GetRequiredService(); + protected override void ConfigureTestServices(IServiceCollection services) => services.AddSingleton(); @@ -113,6 +116,43 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest return new DisposableWrapper(syncMode, index, luceneDir); } + private AutoResetEvent indexingHandle = new(false); + + protected async Task ExecuteAndWaitForIndexing(Action indexUpdatingAction, string indexName) => + await ExecuteAndWaitForIndexing( + () => + { + indexUpdatingAction(); + return null; + }, indexName); + + /// + /// Performs and action and waits for the specified index to be done indexing. + /// + /// The action that causes the index to be updated. + /// The name of the index to wait for rebuild. + /// The type returned from the action. + /// The result of the action. + protected async Task ExecuteAndWaitForIndexing (Func indexUpdatingAction, string indexName) + { + // Set up an action to release the handle when the index is populated. + if (ExamineManager.TryGetIndex(indexName, out IIndex index) is false) + { + throw new InvalidOperationException($"Could not find index: {indexName}"); + } + + index.IndexOperationComplete += (_, _) => + { + indexingHandle.Set(); + }; + + // Perform the action, and wait for the handle to be freed, meaning the index is done populating. + var result = indexUpdatingAction(); + await indexingHandle.WaitOneAsync(); + + return result; + } + private class DisposableWrapper : IDisposable { private readonly IDisposable[] _disposables; @@ -127,4 +167,10 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest } } } + + protected string GetIndexPath(string indexName) + { + var root = TestContext.CurrentContext.TestDirectory.Split("Umbraco.Tests.Integration")[0]; + return Path.Combine(root, "Umbraco.Tests.Integration", "umbraco", "Data", "TEMP", "ExamineIndexes", indexName); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs new file mode 100644 index 0000000000..38eb270b3c --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs @@ -0,0 +1,417 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using Examine; +using Examine.Search; +using Lucene.Net.QueryParsers.Classic; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Examine; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +public class ExamineExternalIndexSearcherTest : IExamineExternalIndexSearcherTest +{ + private readonly AppCaches _appCaches; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IEntityService _entityService; + private readonly IExamineManager _examineManager; + private readonly ILocalizationService _languageService; + private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IUmbracoTreeSearcherFields _treeSearcherFields; + private readonly IUmbracoMapper _umbracoMapper; + + public ExamineExternalIndexSearcherTest( + IExamineManager examineManager, + ILocalizationService languageService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IEntityService entityService, + IUmbracoTreeSearcherFields treeSearcherFields, + AppCaches appCaches, + IUmbracoMapper umbracoMapper, + IPublishedUrlProvider publishedUrlProvider) + { + _examineManager = examineManager; + _languageService = languageService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _entityService = entityService; + _treeSearcherFields = treeSearcherFields; + _appCaches = appCaches; + _umbracoMapper = umbracoMapper; + _publishedUrlProvider = publishedUrlProvider; + } + + public IEnumerable Search( + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, + out long totalFound, + string? searchFrom = null, + bool ignoreUserStartNodes = false) + { + var sb = new StringBuilder(); + + string type; + var indexName = Constants.UmbracoIndexes.ExternalIndexName; + var fields = _treeSearcherFields.GetBackOfficeFields().ToList(); + + ISet fieldsToLoad = new HashSet(_treeSearcherFields.GetBackOfficeFieldsToLoad()); + + // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string + // manipulation for things like start paths, member types, etc... + //if (Examine.ExamineExtensions.TryParseLuceneQuery(query)) + //{ + + //} + + //special GUID check since if a user searches on one specifically we need to escape it + if (Guid.TryParse(query, out Guid g)) + { + query = "\"" + g + "\""; + } + + IUser? currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; + + switch (entityType) + { + case UmbracoEntityTypes.Member: + indexName = Constants.UmbracoIndexes.MembersIndexName; + type = "member"; + fields.AddRange(_treeSearcherFields.GetBackOfficeMembersFields()); + foreach (var field in _treeSearcherFields.GetBackOfficeMembersFieldsToLoad()) + { + fieldsToLoad.Add(field); + } + + if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && + searchFrom.Trim() != "-1") + { + sb.Append("+__NodeTypeAlias:"); + sb.Append(searchFrom); + sb.Append(" "); + } + + break; + case UmbracoEntityTypes.Media: + type = "media"; + fields.AddRange(_treeSearcherFields.GetBackOfficeMediaFields()); + foreach (var field in _treeSearcherFields.GetBackOfficeMediaFieldsToLoad()) + { + fieldsToLoad.Add(field); + } + + var allMediaStartNodes = currentUser != null + ? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches) + : Array.Empty(); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + break; + case UmbracoEntityTypes.Document: + type = "content"; + fields.AddRange(_treeSearcherFields.GetBackOfficeDocumentFields()); + foreach (var field in _treeSearcherFields.GetBackOfficeDocumentFieldsToLoad()) + { + fieldsToLoad.Add(field); + } + + var allContentStartNodes = currentUser != null + ? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches) + : Array.Empty(); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + break; + default: + throw new NotSupportedException("The " + typeof(BackOfficeExamineSearcher) + + " currently does not support searching against object type " + + entityType); + } + + if (!_examineManager.TryGetIndex(indexName, out IIndex? index)) + { + throw new InvalidOperationException("No index found by name " + indexName); + } + + if (!BuildQuery(sb, query, searchFrom, fields, type)) + { + totalFound = 0; + return Enumerable.Empty(); + } + + ISearchResults? result = index.Searcher + .CreateQuery() + .NativeQuery(sb.ToString()) + .SelectFields(fieldsToLoad) + //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested + .Execute(QueryOptions.SkipTake(Convert.ToInt32(pageSize * pageIndex), pageSize)); + + totalFound = result.TotalItemCount; + + return result; + } + + private bool BuildQuery(StringBuilder sb, string query, string? searchFrom, List fields, string type) + { + //build a lucene query: + // the nodeName will be boosted 10x without wildcards + // then nodeName will be matched normally with wildcards + // the rest will be normal without wildcards + + var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList(); + + // the chars [*-_] in the query will mess everything up so let's remove those + // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. + query = Regex.Replace(query, "[\\*]", string.Empty); + query = Regex.Replace(query, "[\\-_]", " "); + + + //check if text is surrounded by single or double quotes, if so, then exact match + var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") + || Regex.IsMatch(query, "^\'.*?\'$"); + + if (surroundedByQuotes) + { + //strip quotes, escape string, the replace again + query = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote); + + query = QueryParserBase.Escape(query); + + //nothing to search + if (searchFrom.IsNullOrWhiteSpace() && query.IsNullOrWhiteSpace()) + { + return false; + } + + //update the query with the query term + if (query.IsNullOrWhiteSpace() == false) + { + //add back the surrounding quotes + query = string.Format("{0}{1}{0}", "\"", query); + + sb.Append("+("); + + AppendNodeNamePhraseWithBoost(sb, query, allLangs); + + foreach (var f in fields) + { + //additional fields normally + sb.Append(f); + sb.Append(": ("); + sb.Append(query); + sb.Append(") "); + } + + sb.Append(") "); + } + } + else + { + var trimmed = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote); + + //nothing to search + if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace()) + { + return false; + } + + //update the query with the query term + if (trimmed.IsNullOrWhiteSpace() == false) + { + query = QueryParserBase.Escape(query); + + var querywords = query.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries); + + sb.Append("+("); + + AppendNodeNameExactWithBoost(sb, query, allLangs); + + AppendNodeNameWithWildcards(sb, querywords, allLangs); + + foreach (var f in fields) + { + var queryWordsReplaced = new string[querywords.Length]; + + // when searching file names containing hyphens we need to replace the hyphens with spaces + if (f.Equals(UmbracoExamineFieldNames.UmbracoFileFieldName)) + { + for (var index = 0; index < querywords.Length; index++) + { + queryWordsReplaced[index] = + querywords[index].Replace("\\-", " ").Replace("_", " ").Trim(" "); + } + } + else + { + queryWordsReplaced = querywords; + } + + //additional fields normally + sb.Append(f); + sb.Append(":"); + sb.Append("("); + foreach (var w in queryWordsReplaced) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + + sb.Append(")"); + sb.Append(" "); + } + + sb.Append(") "); + } + } + + //must match index type + sb.Append("+__IndexType:"); + sb.Append(type); + + return true; + } + + private void AppendNodeNamePhraseWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + } + } + + private void AppendNodeNameExactWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + } + } + + private void AppendNodeNameWithWildcards(StringBuilder sb, string[] querywords, IEnumerable allLangs) + { + //node name normally with wildcards + sb.Append("nodeName:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + + sb.Append(") "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name normally with wildcards + sb.Append($"nodeName_{lang}:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + + sb.Append(") "); + } + } + + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[]? startNodeIds, string? searchFrom, bool ignoreUserStartNodes, IEntityService entityService) + { + if (sb == null) + { + throw new ArgumentNullException(nameof(sb)); + } + + if (entityService == null) + { + throw new ArgumentNullException(nameof(entityService)); + } + + UdiParser.TryParse(searchFrom, true, out Udi? udi); + searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString(); + + TreeEntityPath? entityPath = + int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var searchFromId) && + searchFromId > 0 + ? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault() + : null; + if (entityPath != null) + { + // find... only what's underneath + sb.Append("+__Path:"); + AppendPath(sb, entityPath.Path, false); + sb.Append(" "); + } + else if (startNodeIds?.Length == 0) + { + // make sure we don't find anything + sb.Append("+__Path:none "); + } + else if (startNodeIds?.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction + { + IEnumerable entityPaths = entityService.GetAllPaths(objectType, startNodeIds); + + // for each start node, find the start node, and what's underneath + // +__Path:(-1*,1234 -1*,1234,* -1*,5678 -1*,5678,* ...) + sb.Append("+__Path:("); + var first = true; + foreach (TreeEntityPath ep in entityPaths) + { + if (first) + { + first = false; + } + else + { + sb.Append(" "); + } + + AppendPath(sb, ep.Path, true); + } + + sb.Append(") "); + } + } + + private void AppendPath(StringBuilder sb, string path, bool includeThisNode) + { + path = path.Replace("-", "\\-").Replace(",", "\\,"); + if (includeThisNode) + { + sb.Append(path); + sb.Append(" "); + } + + sb.Append(path); + sb.Append("\\,*"); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs new file mode 100644 index 0000000000..41b66a193c --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs @@ -0,0 +1,137 @@ +using Examine; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.Examine.DependencyInjection; +using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Infrastructure.Search; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; +using Umbraco.Cms.Web.BackOffice.Security; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class ExamineExternalIndexTests : ExamineBaseTest +{ + private const string ContentName = "TestContent"; + + [SetUp] + public void Setup() + { + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.InternalIndexName)); + TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.ExternalIndexName)); + var httpContext = new DefaultHttpContext(); + httpContext.RequestServices = Services; + Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext); + } + + [TearDown] + public void TearDown() + { + // When disposing examine, it does a final write, which ends up locking the file if the indexing is not done yet. So we have this wait to circumvent that. + Thread.Sleep(1500); + // Sometimes we do not dispose all services in time and the test fails because the log file is locked. Resulting in all other tests failing as well + Services.DisposeIfDisposable(); + } + + private IExamineExternalIndexSearcherTest ExamineExternalIndexSearcher => + GetRequiredService(); + + private IContentTypeService ContentTypeService => GetRequiredService(); + + private ContentService ContentService => (ContentService)GetRequiredService(); + + private IUserStore BackOfficeUserStore => + GetRequiredService>(); + + private IBackOfficeSignInManager BackOfficeSignInManager => GetRequiredService(); + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder + .AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddExamineIndexes(); + builder.AddBackOfficeIdentity(); + builder.Services.AddHostedService(); + } + + private IEnumerable ExamineExternalIndexSearch(string query, int pageSize = 20, int pageIndex = 0) => + ExamineExternalIndexSearcher.Search(query, UmbracoEntityTypes.Document, + pageSize, pageIndex, out _, ignoreUserStartNodes: true); + + private async Task SetupUserIdentity(string userId) + { + var identity = + await BackOfficeUserStore.FindByIdAsync(userId, CancellationToken.None); + await BackOfficeSignInManager.SignInAsync(identity, false); + } + + [Test] + public async Task Search_Published_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + ContentTypeService.Save(contentType); + + var content = new ContentBuilder() + .WithId(0) + .WithName(ContentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.ExternalIndexName); + + // Act + IEnumerable actual = ExamineExternalIndexSearch(ContentName); + + // Assert + IEnumerable searchResults = actual.ToArray(); + Assert.AreEqual(1, searchResults.Count()); + Assert.AreEqual(searchResults.First().Values["nodeName"], ContentName); + } + + [Test] + public async Task Search_Unpublished_Content_With_Query_By_Content_Name() + { + // Arrange + await SetupUserIdentity(Constants.Security.SuperUserIdAsString); + + var contentType = new ContentTypeBuilder() + .WithId(0) + .Build(); + ContentTypeService.Save(contentType); + + var content = new ContentBuilder() + .WithId(0) + .WithName(ContentName) + .WithContentType(contentType) + .Build(); + await ExecuteAndWaitForIndexing(() => ContentService.Save(content), Constants.UmbracoIndexes.ExternalIndexName); + + // Act + IEnumerable actual = ExamineExternalIndexSearch(ContentName); + + // Assert + Assert.AreEqual(0, actual.Count()); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs new file mode 100644 index 0000000000..eba1ebf7bb --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IExamineExternalIndexSearcherTest.cs @@ -0,0 +1,18 @@ +using Examine; +using Umbraco.Cms.Core.Models.ContentEditing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; + +public interface IExamineExternalIndexSearcherTest +{ + + IEnumerable Search( + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, + out long totalFound, + string? searchFrom = null, + bool ignoreUserStartNodes = false); + +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs index 01ca9284f7..21a06c3643 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs @@ -19,7 +19,6 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Web.Common.DependencyInjection; using Directory = Lucene.Net.Store.Directory; using StaticServiceProvider = Umbraco.Cms.Core.DependencyInjection.StaticServiceProvider; @@ -37,6 +36,7 @@ public class IndexInitializer private readonly PropertyEditorCollection _propertyEditors; private readonly IScopeProvider _scopeProvider; private readonly IShortStringHelper _shortStringHelper; + private readonly IContentTypeService _contentTypeService; public IndexInitializer( IShortStringHelper shortStringHelper, @@ -45,7 +45,8 @@ public class IndexInitializer IScopeProvider scopeProvider, ILoggerFactory loggerFactory, IOptions contentSettings, - ILocalizationService localizationService) + ILocalizationService localizationService, + IContentTypeService contentTypeService) { _shortStringHelper = shortStringHelper; _propertyEditors = propertyEditors; @@ -54,6 +55,7 @@ public class IndexInitializer _loggerFactory = loggerFactory; _contentSettings = contentSettings; _localizationService = localizationService; + _contentTypeService = contentTypeService; } public IndexInitializer( @@ -62,7 +64,7 @@ public class IndexInitializer MediaUrlGeneratorCollection mediaUrlGenerators, IScopeProvider scopeProvider, ILoggerFactory loggerFactory, - IOptions contentSettings) + IOptions contentSettings, IContentTypeService contentTypeService) : this( shortStringHelper, propertyEditors, @@ -70,7 +72,7 @@ public class IndexInitializer scopeProvider, loggerFactory, contentSettings, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), contentTypeService) { } @@ -83,7 +85,9 @@ public class IndexInitializer _shortStringHelper, _scopeProvider, publishedValuesOnly, - _localizationService); + _localizationService, + _contentTypeService, + _loggerFactory.CreateLogger()); return contentValueSetBuilder; } From cb765544557776b84f4a94bdd2f1a4489e5ab013 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 15 Nov 2023 09:34:27 +0100 Subject: [PATCH 106/146] 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 d2ff2ea46ea00a8082b77d533a8f45d15bf212cb) --- .../Persistence/NuCacheContentRepository.cs | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs index f54158dac1..f76b176d5e 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs @@ -224,7 +224,7 @@ AND cmsContentNu.nodeId IS NULL IContentCacheDataSerializer serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document); - IEnumerable dtos = GetContentNodeDtos(sql, Constants.ObjectTypes.Document); + IEnumerable dtos = GetContentNodeDtos(sql); foreach (ContentSourceDto row in dtos) { @@ -242,7 +242,7 @@ AND cmsContentNu.nodeId IS NULL IContentCacheDataSerializer serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document); - IEnumerable dtos = GetContentNodeDtos(sql, Constants.ObjectTypes.Document); + IEnumerable dtos = GetContentNodeDtos(sql); foreach (ContentSourceDto row in dtos) { @@ -265,7 +265,7 @@ AND cmsContentNu.nodeId IS NULL IContentCacheDataSerializer serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document); - IEnumerable dtos = GetContentNodeDtos(sql, Constants.ObjectTypes.Document); + IEnumerable dtos = GetContentNodeDtos(sql); foreach (ContentSourceDto row in dtos) { @@ -301,7 +301,7 @@ AND cmsContentNu.nodeId IS NULL IContentCacheDataSerializer serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media); - IEnumerable dtos = GetContentNodeDtos(sql, Constants.ObjectTypes.Media); + IEnumerable dtos = GetMediaNodeDtos(sql); foreach (ContentSourceDto row in dtos) { @@ -319,7 +319,7 @@ AND cmsContentNu.nodeId IS NULL IContentCacheDataSerializer serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media); - IEnumerable dtos = GetContentNodeDtos(sql, Constants.ObjectTypes.Media); + IEnumerable dtos = GetMediaNodeDtos(sql); foreach (ContentSourceDto row in dtos) { @@ -342,7 +342,7 @@ AND cmsContentNu.nodeId IS NULL IContentCacheDataSerializer serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media); - IEnumerable dtos = GetContentNodeDtos(sql, Constants.ObjectTypes.Media); + IEnumerable dtos = GetMediaNodeDtos(sql); foreach (ContentSourceDto row in dtos) { @@ -990,7 +990,32 @@ WHERE cmsContentNu.nodeId IN ( return s; } - private IEnumerable GetContentNodeDtos(Sql sql, Guid nodeObjectType) + private IEnumerable GetMediaNodeDtos(Sql 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 dtos; + if (_nucacheSettings.Value.UsePagedSqlQuery) + { + // Use a more efficient COUNT query + Sql? sqlCountQuery = SqlMediaSourcesCount() + .Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media)); + + Sql? sqlCount = + SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); + + dtos = Database.QueryPaged(_nucacheSettings.Value.SqlPageSize, sql, sqlCount); + } + else + { + dtos = Database.Fetch(sql); + } + + return dtos; + } + + private IEnumerable GetContentNodeDtos(Sql 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. @@ -1000,7 +1025,7 @@ WHERE cmsContentNu.nodeId IN ( { // Use a more efficient COUNT query Sql? sqlCountQuery = SqlContentSourcesCount() - .Append(SqlObjectTypeNotTrashed(SqlContext, nodeObjectType)); + .Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document)); Sql? sqlCount = SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); From 4ff2572d787b8f63b4d8b8ad530524e4cafdd2db Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 7 Dec 2023 09:51:44 +0100 Subject: [PATCH 107/146] Use named HttpClients (#15375) * Use named HttpClients * Register as named client * Set headers on request message. * Add User agent header to http client * Add headers within Try-catch * Add using for HttpResponseMessage * Update src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs Co-authored-by: Ronald Barendse * Update src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs Co-authored-by: Kenn Jacobsen * Log key instead of entire webhook * Update src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs Co-authored-by: Ronald Barendse * Create constant for User agent header name * Update src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs Co-authored-by: Ronald Barendse * Use datetime.now --------- Co-authored-by: Zeegaan Co-authored-by: Ronald Barendse Co-authored-by: Kenn Jacobsen --- src/Umbraco.Core/Constants-HttpClients.cs | 13 ++++++ .../EmbedProviders/OEmbedProviderBase.cs | 2 +- .../BackgroundJobs/Jobs/WebhookFiring.cs | 42 ++++++++++++------- .../UmbracoBuilderExtensions.cs | 8 +++- 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Constants-HttpClients.cs b/src/Umbraco.Core/Constants-HttpClients.cs index 677f442085..a9c693f39d 100644 --- a/src/Umbraco.Core/Constants-HttpClients.cs +++ b/src/Umbraco.Core/Constants-HttpClients.cs @@ -14,5 +14,18 @@ public static partial class Constants /// Name for http client which ignores certificate errors. /// public const string IgnoreCertificateErrors = "Umbraco:HttpClients:IgnoreCertificateErrors"; + + /// + /// Name for http client which sends webhook requests. + /// + public const string WebhookFiring = "Umbraco:HttpClients:WebhookFiring"; + + public static class Headers + { + /// + /// User agent name for the product name. + /// + public const string UserAgentProductName = "Umbraco-Cms"; + } } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs index 9385dcf6c9..59d0f171ef 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs @@ -55,7 +55,7 @@ public abstract class OEmbedProviderBase : IEmbedProvider if (_httpClient == null) { _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Umbraco-CMS"); + _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(Constants.HttpClients.Headers.UserAgentProductName); } using (var request = new HttpRequestMessage(HttpMethod.Get, url)) diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs index 11eaa3bb8a..3d16960144 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs @@ -6,7 +6,6 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; @@ -15,11 +14,11 @@ public class WebhookFiring : IRecurringBackgroundJob { private readonly ILogger _logger; private readonly IWebhookRequestService _webhookRequestService; - private readonly IJsonSerializer _jsonSerializer; private readonly IWebhookLogFactory _webhookLogFactory; private readonly IWebhookLogService _webhookLogService; private readonly IWebhookService _webHookService; private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IHttpClientFactory _httpClientFactory; private WebhookSettings _webhookSettings; public TimeSpan Period => _webhookSettings.Period; @@ -32,20 +31,20 @@ public class WebhookFiring : IRecurringBackgroundJob public WebhookFiring( ILogger logger, IWebhookRequestService webhookRequestService, - IJsonSerializer jsonSerializer, IWebhookLogFactory webhookLogFactory, IWebhookLogService webhookLogService, IWebhookService webHookService, IOptionsMonitor webhookSettings, - ICoreScopeProvider coreScopeProvider) + ICoreScopeProvider coreScopeProvider, + IHttpClientFactory httpClientFactory) { _logger = logger; _webhookRequestService = webhookRequestService; - _jsonSerializer = jsonSerializer; _webhookLogFactory = webhookLogFactory; _webhookLogService = webhookLogService; _webHookService = webHookService; _coreScopeProvider = coreScopeProvider; + _httpClientFactory = httpClientFactory; _webhookSettings = webhookSettings.CurrentValue; webhookSettings.OnChange(x => _webhookSettings = x); } @@ -72,8 +71,7 @@ public class WebhookFiring : IRecurringBackgroundJob return; } - HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None); - + using HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None); if ((response?.IsSuccessStatusCode ?? false) || request.RetryCount >= _webhookSettings.MaximumRetries) { await _webhookRequestService.DeleteAsync(request); @@ -90,24 +88,36 @@ public class WebhookFiring : IRecurringBackgroundJob private async Task SendRequestAsync(IWebhook webhook, string eventName, string? serializedObject, int retryCount, CancellationToken cancellationToken) { - using var httpClient = new HttpClient(); + using HttpClient httpClient = _httpClientFactory.CreateClient(Constants.HttpClients.WebhookFiring); - var stringContent = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json); - stringContent.Headers.TryAddWithoutValidation("Umb-Webhook-Event", eventName); - - foreach (KeyValuePair header in webhook.Headers) + using var request = new HttpRequestMessage(HttpMethod.Post, webhook.Url) { - stringContent.Headers.TryAddWithoutValidation(header.Key, header.Value); - } + Version = httpClient.DefaultRequestVersion, + VersionPolicy = httpClient.DefaultVersionPolicy, + }; HttpResponseMessage? response = null; try { - response = await httpClient.PostAsync(webhook.Url, stringContent, cancellationToken); + // Add headers + request.Headers.Add("Umb-Webhook-Event", eventName); + request.Headers.Add("Umb-Webhook-RetryCount", retryCount.ToString()); + request.Headers.Add("Umb-Webhook-Date", DateTime.Now.ToString("R")); + + foreach (KeyValuePair header in webhook.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + + // Set content + request.Content = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json); + + // Send request + response = await httpClient.SendAsync(request, cancellationToken); } catch (Exception ex) { - _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook); + _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook.Key); } var webhookResponseModel = new WebhookResponseModel diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 40436fd798..8939b6fcb4 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using System.Net.Http.Headers; using System.Reflection; using Dazinator.Extensions.FileProviders.GlobPatternFilter; using Microsoft.AspNetCore.Builder; @@ -10,7 +11,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog.Extensions.Logging; @@ -23,6 +23,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Blocks; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Diagnostics; @@ -261,6 +262,11 @@ public static partial class UmbracoBuilderExtensions ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); + builder.Services.AddHttpClient(Constants.HttpClients.WebhookFiring, (services, client) => + { + var productVersion = services.GetRequiredService().SemanticVersion.ToSemanticStringWithoutBuild(); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.HttpClients.Headers.UserAgentProductName, productVersion)); + }); return builder; } From 80a6ec44ba226075f8d654bc002460e77a57d922 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 7 Dec 2023 10:26:55 +0100 Subject: [PATCH 108/146] Migrate url columms to NVarCharMax (#15331) --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../ChangeWebhookUrlColumnsToNvarcharMax.cs | 89 +++++++++++++++++++ .../Persistence/Dtos/WebhookDto.cs | 1 + .../Persistence/Dtos/WebhookLogDto.cs | 1 + 4 files changed, 92 insertions(+) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 3cacfd3eac..74be791185 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -103,5 +103,6 @@ public class UmbracoPlan : MigrationPlan To("{23BA95A4-FCCE-49B0-8AA1-45312B103A9B}"); To("{7DDCE198-9CA4-430C-8BBC-A66D80CA209F}"); To("{F74CDA0C-7AAA-48C8-94C6-C6EC3C06F599}"); + To("{21C42760-5109-4C03-AB4F-7EA53577D1F5}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs new file mode 100644 index 0000000000..1e24cb9497 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs @@ -0,0 +1,89 @@ +using System.Linq.Expressions; +using System.Text; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Column; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_0_0; + +public class ChangeWebhookUrlColumnsToNvarcharMax : MigrationBase +{ + public ChangeWebhookUrlColumnsToNvarcharMax(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + // We don't need to run this migration for SQLite, since ntext is not a thing there, text is just text. + if (DatabaseType == DatabaseType.SQLite) + { + return; + } + + MigrateNtextColumn("url", Constants.DatabaseSchema.Tables.Webhook, x => x.Url); + MigrateNtextColumn("url", Constants.DatabaseSchema.Tables.WebhookLog, x => x.Url); + } + + private void MigrateNtextColumn(string columnName, string tableName, Expression> fieldSelector, bool nullable = true) + { + var columnType = ColumnType(tableName, columnName); + if (columnType is null || columnType.Equals("nvarchar", StringComparison.InvariantCultureIgnoreCase) is false) + { + return; + } + + var oldColumnName = $"Old{columnName}"; + + // Rename the column so we can create the new one and copy over the data. + Rename + .Column(columnName) + .OnTable(tableName) + .To(oldColumnName) + .Do(); + + // Create new column with the correct type + // This is pretty ugly, but we have to do ti this way because the CacheInstruction.Instruction column doesn't support nullable. + // So we have to populate with some temporary placeholder value before we copy over the actual data. + ICreateColumnOptionBuilder builder = Create + .Column(columnName) + .OnTable(tableName) + .AsCustom("nvarchar(max)"); + + if (nullable is false) + { + builder + .NotNullable() + .WithDefaultValue("Placeholder"); + } + else + { + builder.Nullable(); + } + + builder.Do(); + + // Copy over data NPOCO doesn't support this for some reason, so we'll have to do it like so + // While we're add it we'll also set all the old values to be NULL since it's recommended here: + // https://learn.microsoft.com/en-us/sql/t-sql/data-types/ntext-text-and-image-transact-sql?view=sql-server-ver16#remarks + StringBuilder queryBuilder = new StringBuilder() + .AppendLine($"UPDATE {tableName}") + .AppendLine("SET") + .Append($"\t{SqlSyntax.GetFieldNameForUpdate(fieldSelector)} = {SqlSyntax.GetQuotedTableName(tableName)}.{SqlSyntax.GetQuotedColumnName(oldColumnName)}"); + + if (nullable) + { + queryBuilder.AppendLine($"\n,\t{SqlSyntax.GetQuotedColumnName(oldColumnName)} = NULL"); + } + + Sql copyDataQuery = Database.SqlContext.Sql(queryBuilder.ToString()); + Database.Execute(copyDataQuery); + + // Delete old column + Delete + .Column(oldColumnName) + .FromTable(tableName) + .Do(); + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs index abcf160b03..2fb0d13555 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs @@ -19,6 +19,7 @@ internal class WebhookDto public Guid Key { get; set; } [Column(Name = "url")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] [NullSetting(NullSetting = NullSettings.NotNull)] public string Url { get; set; } = string.Empty; diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs index f98226248e..27adcee54f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs @@ -30,6 +30,7 @@ internal class WebhookLogDto public DateTime Date { get; set; } [Column(Name = "url")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] [NullSetting(NullSetting = NullSettings.NotNull)] public string Url { get; set; } = string.Empty; From 1a93dae0c44c031ec14d03344dd1c33bfa4b11e6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 7 Dec 2023 11:25:31 +0100 Subject: [PATCH 109/146] Ensure the toggles return booleans and not integers. (#15354) --- .../src/common/services/umbdataformatter.service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 8bf8eecf91..5cf975bf8e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -295,13 +295,13 @@ saveModel.memberGroups = _.keys(_.pick(prop.value, value => value === true)); break; case '_umb_approved': - saveModel.isApproved = prop.value; + saveModel.isApproved = prop.value == true; break; case '_umb_lockedOut': - saveModel.isLockedOut = prop.value; + saveModel.isLockedOut = prop.value == true; break; case '_umb_twoFactorEnabled': - saveModel.isTwoFactorEnabled = prop.value; + saveModel.isTwoFactorEnabled = prop.value == true; break; } } From 0534c5f77608973775cad6a293ca36f94a97afb5 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:10:19 +0100 Subject: [PATCH 110/146] Log retrycount and properly log request headers (#15390) --- .../Services/WebhookLogFactory.cs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Services/WebhookLogFactory.cs b/src/Umbraco.Core/Services/WebhookLogFactory.cs index ec88bb52c4..fe1238cb0f 100644 --- a/src/Umbraco.Core/Services/WebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/WebhookLogFactory.cs @@ -1,6 +1,6 @@ using System.Net; +using System.Text; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Webhooks; namespace Umbraco.Cms.Core.Services; @@ -15,20 +15,38 @@ public class WebhookLogFactory : IWebhookLogFactory Key = Guid.NewGuid(), Url = webhook.Url, WebhookKey = webhook.Key, + RetryCount = responseModel.RetryCount, }; if (responseModel.HttpResponseMessage is not null) { - log.RequestBody = await responseModel.HttpResponseMessage!.RequestMessage!.Content!.ReadAsStringAsync(cancellationToken); + if (responseModel.HttpResponseMessage.RequestMessage?.Content is not null) + { + log.RequestBody = await responseModel.HttpResponseMessage.RequestMessage.Content.ReadAsStringAsync(cancellationToken); + log.RequestHeaders = CalculateHeaders(responseModel.HttpResponseMessage); + } + log.ResponseBody = await responseModel.HttpResponseMessage.Content.ReadAsStringAsync(cancellationToken); - log.StatusCode = MapStatusCodeToMessage(responseModel.HttpResponseMessage.StatusCode); - log.RetryCount = responseModel.RetryCount; log.ResponseHeaders = responseModel.HttpResponseMessage.Headers.ToString(); - log.RequestHeaders = responseModel.HttpResponseMessage.RequestMessage.Headers.ToString(); + log.StatusCode = MapStatusCodeToMessage(responseModel.HttpResponseMessage.StatusCode); } return log; } private string MapStatusCodeToMessage(HttpStatusCode statusCode) => $"{statusCode.ToString()} ({(int)statusCode})"; + + private string CalculateHeaders(HttpResponseMessage responseMessage) + { + IEnumerable>> headers = responseMessage.RequestMessage!.Headers.Concat(responseMessage.RequestMessage.Content!.Headers); + + var result = new StringBuilder(); + + foreach (KeyValuePair> header in headers) + { + result.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}\n"); + } + + return result.ToString(); + } } From d31bb14f5720ff0a8535a678ae5db5030eb62c55 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:11:38 +0100 Subject: [PATCH 111/146] Link to log on exception/error (#15391) --- .../src/views/webhooks/overlays/details.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html index 4e60ed2d63..def552114a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html @@ -46,6 +46,7 @@ +
@@ -61,6 +62,18 @@ {{vm.formatData(model.log.responseBody) | json}} +
+ + From b50353b2388212902ee8fc9d39cf91a138190967 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:25:26 +0100 Subject: [PATCH 112/146] V13: Log webhook firing exceptions when they happen (#15393) * Refactor Webhook logging to handle exceptions * Add Exception occured property to WebhookLog. --- src/Umbraco.Core/Models/WebhookLog.cs | 2 + .../Models/WebhookResponseModel.cs | 8 -- .../Services/IWebhookLogFactory.cs | 10 +- .../Services/WebhookLogFactory.cs | 32 ++++-- .../BackgroundJobs/Jobs/WebhookFiring.cs | 10 +- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_13_0_0/AddExceptionOccured.cs | 25 ++++ .../Persistence/Dtos/WebhookLogDto.cs | 3 + .../Factories/WebhookLogFactory.cs | 2 + .../Mapping/WebhookMapDefinition.cs | 1 + .../Models/WebhookLogViewModel.cs | 3 + .../src/views/webhooks/overlays/details.html | 108 +++++++++--------- 12 files changed, 126 insertions(+), 79 deletions(-) delete mode 100644 src/Umbraco.Core/Models/WebhookResponseModel.cs create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs diff --git a/src/Umbraco.Core/Models/WebhookLog.cs b/src/Umbraco.Core/Models/WebhookLog.cs index e65abdf990..aba3b15713 100644 --- a/src/Umbraco.Core/Models/WebhookLog.cs +++ b/src/Umbraco.Core/Models/WebhookLog.cs @@ -25,4 +25,6 @@ public class WebhookLog public string ResponseHeaders { get; set; } = string.Empty; public string ResponseBody { get; set; } = string.Empty; + + public bool ExceptionOccured { get; set; } } diff --git a/src/Umbraco.Core/Models/WebhookResponseModel.cs b/src/Umbraco.Core/Models/WebhookResponseModel.cs deleted file mode 100644 index 1f40443806..0000000000 --- a/src/Umbraco.Core/Models/WebhookResponseModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Core.Models; - -public class WebhookResponseModel -{ - public HttpResponseMessage? HttpResponseMessage { get; set; } - - public int RetryCount { get; set; } -} diff --git a/src/Umbraco.Core/Services/IWebhookLogFactory.cs b/src/Umbraco.Core/Services/IWebhookLogFactory.cs index feaa12ef4a..87b43ab5ab 100644 --- a/src/Umbraco.Core/Services/IWebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/IWebhookLogFactory.cs @@ -1,9 +1,15 @@ using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Webhooks; namespace Umbraco.Cms.Core.Services; public interface IWebhookLogFactory { - Task CreateAsync(string eventAlias, WebhookResponseModel responseModel, IWebhook webhook, CancellationToken cancellationToken); + Task CreateAsync( + string eventAlias, + HttpRequestMessage requestMessage, + HttpResponseMessage? httpResponseMessage, + int retryCount, + Exception? exception, + IWebhook webhook, + CancellationToken cancellationToken); } diff --git a/src/Umbraco.Core/Services/WebhookLogFactory.cs b/src/Umbraco.Core/Services/WebhookLogFactory.cs index fe1238cb0f..2c80f15e27 100644 --- a/src/Umbraco.Core/Services/WebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/WebhookLogFactory.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Services; public class WebhookLogFactory : IWebhookLogFactory { - public async Task CreateAsync(string eventAlias, WebhookResponseModel responseModel, IWebhook webhook, CancellationToken cancellationToken) + public async Task CreateAsync(string eventAlias, HttpRequestMessage requestMessage, HttpResponseMessage? httpResponseMessage, int retryCount, Exception? exception, IWebhook webhook, CancellationToken cancellationToken) { var log = new WebhookLog { @@ -15,20 +15,34 @@ public class WebhookLogFactory : IWebhookLogFactory Key = Guid.NewGuid(), Url = webhook.Url, WebhookKey = webhook.Key, - RetryCount = responseModel.RetryCount, + RetryCount = retryCount, + RequestHeaders = requestMessage.Headers.ToString(), + RequestBody = await requestMessage.Content?.ReadAsStringAsync(cancellationToken)!, + ExceptionOccured = exception is not null, }; - if (responseModel.HttpResponseMessage is not null) + if (httpResponseMessage is not null) { - if (responseModel.HttpResponseMessage.RequestMessage?.Content is not null) + log.StatusCode = MapStatusCodeToMessage(httpResponseMessage.StatusCode); + log.ResponseHeaders = httpResponseMessage.Headers.ToString(); + log.ResponseBody = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); + } + else if (exception is HttpRequestException httpRequestException) + { + if (httpRequestException.StatusCode is not null) { - log.RequestBody = await responseModel.HttpResponseMessage.RequestMessage.Content.ReadAsStringAsync(cancellationToken); - log.RequestHeaders = CalculateHeaders(responseModel.HttpResponseMessage); + log.StatusCode = MapStatusCodeToMessage(httpRequestException.StatusCode.Value); + } + else + { + log.StatusCode = httpRequestException.HttpRequestError.ToString(); } - log.ResponseBody = await responseModel.HttpResponseMessage.Content.ReadAsStringAsync(cancellationToken); - log.ResponseHeaders = responseModel.HttpResponseMessage.Headers.ToString(); - log.StatusCode = MapStatusCodeToMessage(responseModel.HttpResponseMessage.StatusCode); + log.ResponseBody = $"{httpRequestException.HttpRequestError}: {httpRequestException.Message}"; + } + else if (exception is not null) + { + log.ResponseBody = exception.Message; } return log; diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs index 3d16960144..bda15fd389 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs @@ -97,6 +97,7 @@ public class WebhookFiring : IRecurringBackgroundJob }; HttpResponseMessage? response = null; + Exception? exception = null; try { // Add headers @@ -117,16 +118,11 @@ public class WebhookFiring : IRecurringBackgroundJob } catch (Exception ex) { + exception = ex; _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook.Key); } - var webhookResponseModel = new WebhookResponseModel - { - HttpResponseMessage = response, - RetryCount = retryCount, - }; - - WebhookLog log = await _webhookLogFactory.CreateAsync(eventName, webhookResponseModel, webhook, cancellationToken); + WebhookLog log = await _webhookLogFactory.CreateAsync(eventName, request, response, retryCount, exception, webhook, cancellationToken); await _webhookLogService.CreateAsync(log); return response; diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 74be791185..3503a85b57 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -104,5 +104,6 @@ public class UmbracoPlan : MigrationPlan To("{7DDCE198-9CA4-430C-8BBC-A66D80CA209F}"); To("{F74CDA0C-7AAA-48C8-94C6-C6EC3C06F599}"); To("{21C42760-5109-4C03-AB4F-7EA53577D1F5}"); + To("{6158F3A3-4902-4201-835E-1ED7F810B2D8}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs new file mode 100644 index 0000000000..2ef666867b --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs @@ -0,0 +1,25 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_0_0; + +public class AddExceptionOccured : MigrationBase +{ + public AddExceptionOccured(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + if (ColumnExists(Constants.DatabaseSchema.Tables.WebhookLog, "exceptionOccured") == false) + { + // Use a custom SQL query to prevent selecting explicit columns (sortOrder doesn't exist yet) + List webhookLogDtos = Database.Fetch($"SELECT * FROM {Constants.DatabaseSchema.Tables.WebhookLog}"); + + Delete.Table(Constants.DatabaseSchema.Tables.WebhookLog).Do(); + Create.Table().Do(); + + Database.InsertBatch(webhookLogDtos); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs index 27adcee54f..8e409cd0b3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs @@ -61,4 +61,7 @@ internal class WebhookLogDto [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] [NullSetting(NullSetting = NullSettings.NotNull)] public string ResponseBody { get; set; } = string.Empty; + + [Column(Name = "exceptionOccured")] + public bool ExceptionOccured { get; set; } } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs index ff1378ed2d..f060525624 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs @@ -21,6 +21,7 @@ internal static class WebhookLogFactory RequestHeaders = log.RequestHeaders, ResponseHeaders = log.ResponseHeaders, WebhookKey = log.WebhookKey, + ExceptionOccured = log.ExceptionOccured, }; public static WebhookLog DtoToEntity(WebhookLogDto dto) => @@ -38,5 +39,6 @@ internal static class WebhookLogFactory RequestHeaders = dto.RequestHeaders, ResponseHeaders = dto.ResponseHeaders, WebhookKey = dto.WebhookKey, + ExceptionOccured = dto.ExceptionOccured, }; } diff --git a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs index 197a06278c..a7564fb6ac 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs @@ -47,5 +47,6 @@ public class WebhookMapDefinition : IMapDefinition target.RequestHeaders = source.RequestHeaders; target.ResponseHeaders = source.ResponseHeaders; target.WebhookKey = source.WebhookKey; + target.ExceptionOccured = source.ExceptionOccured; } } diff --git a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs index f63282b7cb..0b3ff552a7 100644 --- a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs +++ b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs @@ -37,4 +37,7 @@ public class WebhookLogViewModel [DataMember(Name = "responseBody")] public string ResponseBody { get; set; } = string.Empty; + + [DataMember(Name = "exceptionOccured")] + public bool ExceptionOccured { get; set; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html index def552114a..1c07398dba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html @@ -1,69 +1,71 @@ -
+
+ name="model.title" + name-locked="true" + hide-alias="true" + hide-icon="true" + hide-description="true"> - - - + + + - -
-
- - -
-
{{model.log.statusCode}}
-
-
+ +
+
+ + +
+
{{model.log.statusCode}}
+
+
- -
{{model.log.formattedLogDate}}
-
+ +
{{model.log.formattedLogDate}}
+
- -
{{model.log.url}}
-
+ +
{{model.log.url}}
+
- -
{{model.log.eventAlias}}
-
+ +
{{model.log.eventAlias}}
+
- -
{{model.log.retryCount}}
-
+ +
{{model.log.retryCount}}
+
-
+
-
- -
- - - -
{{model.log.requestHeaders}}
- {{vm.formatData(model.log.requestBody) | json}} -
-
+ + + + +
{{model.log.requestHeaders}}
+ {{vm.formatData(model.log.requestBody) | json}} + +
+
{{model.log.responseHeaders}}
- {{vm.formatData(model.log.responseBody) | json}} + {{vm.formatData(model.log.responseBody) | json}} +
-
-
+
@@ -78,15 +80,15 @@ - - - - + + + + From 4b5bbbd531f251be3d95968d9b1eb802af999874 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:23:21 +0100 Subject: [PATCH 113/146] Change webhook-> logs to Deliveries --- src/Umbraco.Core/EmbeddedResources/Lang/da.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/en.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 2 +- .../src/views/webhooks/overview.controller.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index d8623a90a7..83a02173af 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -1012,7 +1012,7 @@ Tilføj dokument type Tilføj medie Type Opret header - Logs + Leverancer Der er ikke tilføjet nogen webhook headers diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 00cb113ddf..1fd67a2d78 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1939,7 +1939,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Document Type Add Media Type Create header - Logs + Deliveries No webhook headers have been added No events were found. Enabled diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index c6c0280997..e1aef8f434 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2021,7 +2021,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Document Type Add Media Type Create header - Logs + Deliveries No webhook headers have been added No events were found. Enabled diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js index 790b199d71..a8dcf8658b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js @@ -23,7 +23,7 @@ function loadNavigation() { - const labelKeys = ["treeHeaders_webhooks", "webhooks_logs"]; + const labelKeys = ["treeHeaders_webhooks", "webhooks_deliveries"]; localizationService.localizeMany(labelKeys).then(data => { vm.page.labels.webhooks = data[0]; From f65e76ba5ae1317611413fdff0243fe4d0831c63 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Fri, 8 Dec 2023 09:04:51 +0100 Subject: [PATCH 114/146] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 33e6ec9c6f..ba885cb668 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.0.0-rc4", + "version": "13.0.0-rc5", "assemblyVersion": { "precision": "build" }, From 9e96bba9bcba3858c284def9f7276efc9a675374 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 8 Dec 2023 15:00:18 +0100 Subject: [PATCH 115/146] Disable transitive package version pinning (#15406) * Disable transitive package version pinning * Take top-level dependencies on Azure.Identity and System.Net.Http * Take top-level dependencies on System.Security.Cryptography.Xml and System.Text.RegularExpressions --- Directory.Packages.props | 20 ++++++++----------- ...co.Cms.Persistence.EFCore.SqlServer.csproj | 2 ++ .../Umbraco.Cms.Persistence.EFCore.csproj | 2 ++ .../Umbraco.Cms.Persistence.SqlServer.csproj | 2 ++ .../Umbraco.Examine.Lucene.csproj | 2 ++ .../Umbraco.Web.Common.csproj | 4 ++++ tests/Directory.Packages.props | 4 ++-- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0f28210de3..7279fa21c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,17 +2,14 @@ true - true - - + - @@ -37,16 +34,14 @@ - - - + @@ -83,14 +78,15 @@ - - + - + - - + + + + diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj index 5229f513a2..7e6fc6153d 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj index 39f035407b..4500c2812b 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj index 0de13a39b6..75e2a6fe60 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj index 76349f9671..7e28eb6971 100644 --- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj +++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj @@ -8,6 +8,8 @@ + + diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index c7e259d283..02a039a6db 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -20,6 +20,10 @@ + + + + diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index a8031cc500..1ecbee80a5 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -4,7 +4,7 @@ - + @@ -22,4 +22,4 @@ - + \ No newline at end of file From 258ab59286f91c480bec4f8fbddb90e2f4f61b12 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst Date: Thu, 7 Dec 2023 13:15:04 +0100 Subject: [PATCH 116/146] Updated the keys for adding a block list --- .../blockListEditorDataType.spec.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorDataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorDataType.spec.ts index 0ca492c30f..f2cb5d5441 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorDataType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorDataType.spec.ts @@ -6,21 +6,21 @@ import {BlockListDataTypeBuilder} from "@umbraco/json-models-builders/dist/lib/b test.describe('BlockListEditorDataType', () => { const blockListName = 'BlockListTest'; const elementName = 'TestElement'; - + const elementAlias = AliasHelper.toAlias(elementName); - + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); await umbracoApi.documentTypes.ensureNameNotExists(elementName); }); - + test.afterEach(async({page, umbracoApi, umbracoUi}) => { await umbracoApi.dataTypes.ensureNameNotExists(blockListName); await umbracoApi.documentTypes.ensureNameNotExists(elementName); }) - + test('can create an empty block list datatype', async ({page, umbracoApi, umbracoUi}) => { await umbracoUi.goToSection(ConstantHelper.sections.settings); @@ -52,7 +52,7 @@ test.describe('BlockListEditorDataType', () => { await umbracoUi.navigateToDataType(blockListName); // Adds an element to the block list - await umbracoUi.clickElement(umbracoUi.getButtonByKey(ConstantHelper.buttons.add)); + await umbracoUi.clickElement(umbracoUi.getButtonByKey('blockEditor_addBlockType')); await page.locator('[data-element="editor-container"]').locator('[data-element="tree-item-' + elementName + '"]').click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.submitChanges)); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.save)); @@ -83,7 +83,7 @@ test.describe('BlockListEditorDataType', () => { await umbracoUi.navigateToDataType(blockListName); // Adds an element to the block list - await umbracoUi.clickElement(umbracoUi.getButtonByKey(ConstantHelper.buttons.add)); + await umbracoUi.clickElement(umbracoUi.getButtonByKey('blockEditor_addBlockType')); await page.locator('[data-element="editor-container"]').locator('[data-element="tree-item-' + elementNameTwo + '"]').click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.submitChanges)); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.save)); @@ -138,7 +138,7 @@ test.describe('BlockListEditorDataType', () => { await umbracoUi.navigateToDataType(blockListName); // Tries adding the same element to the block list editor - await umbracoUi.clickElement(umbracoUi.getButtonByKey(ConstantHelper.buttons.add)); + await umbracoUi.clickElement(umbracoUi.getButtonByKey('blockEditor_addBlockType')); await page.locator('[data-element="editor-container"]').locator('[data-element="tree-item-' + elementName + '"]').click(); // Assert @@ -147,12 +147,12 @@ test.describe('BlockListEditorDataType', () => { await expect(page.locator('[label-key="blockEditor_labelcreateNewElementType"]')).toBeVisible(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.close)); await expect(page.locator('[block-config-model="block"]')).toHaveCount(1); - }); + }); test('can edit a block list editor', async ({page, umbracoApi, umbracoUi}, testInfo) => { // We need to increase the timeout because the test is taking too long to end await testInfo.slow() - + const elementNameTwo = 'SecondElement'; const elementTwoAlias = AliasHelper.toAlias(elementNameTwo); const stylesheetName = 'TestStyleSheet'; @@ -254,7 +254,7 @@ test.describe('BlockListEditorDataType', () => { test('can delete a block list editor', async ({page, umbracoApi, umbracoUi}) => { const elementNameTwo = 'SecondElement'; const elementTwoAlias = AliasHelper.toAlias(elementNameTwo); - + await umbracoApi.documentTypes.ensureNameNotExists(elementNameTwo); const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); @@ -286,4 +286,4 @@ test.describe('BlockListEditorDataType', () => { // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementNameTwo); }); -}); \ No newline at end of file +}); From d752853d8955a92b527550a82e340c4986a5b817 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 10 Dec 2023 19:18:32 +0100 Subject: [PATCH 117/146] Fix disposed DbContext issue (#15090) (#15410) --- ...izeMemberApplicationNotificationHandler.cs | 24 ++++++---- ...mbracoEFCoreServiceCollectionExtensions.cs | 25 ---------- .../UmbracoPooledDbContextFactory.cs | 47 ------------------- .../UmbracoDbContext.cs | 5 ++ 4 files changed, 20 insertions(+), 81 deletions(-) delete mode 100644 src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs diff --git a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs index 242fd47857..06b6472506 100644 --- a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs +++ b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; @@ -11,20 +12,20 @@ namespace Umbraco.Cms.Api.Delivery.Handlers; internal sealed class InitializeMemberApplicationNotificationHandler : INotificationAsyncHandler { - private readonly IMemberApplicationManager _memberApplicationManager; private readonly IRuntimeState _runtimeState; private readonly ILogger _logger; private readonly DeliveryApiSettings _deliveryApiSettings; + private readonly IServiceScopeFactory _serviceScopeFactory; public InitializeMemberApplicationNotificationHandler( - IMemberApplicationManager memberApplicationManager, IRuntimeState runtimeState, IOptions deliveryApiSettings, - ILogger logger) + ILogger logger, + IServiceScopeFactory serviceScopeFactory) { - _memberApplicationManager = memberApplicationManager; _runtimeState = runtimeState; _logger = logger; + _serviceScopeFactory = serviceScopeFactory; _deliveryApiSettings = deliveryApiSettings.Value; } @@ -35,26 +36,31 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica return; } + // we cannot inject the IMemberApplicationManager because it ultimately takes a dependency on the DbContext ... and during + // install that is not allowed (no connection string means no DbContext) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService(); + if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true) { - await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); return; } if (ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls) is false) { - await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); return; } if (_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls.Any() && ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls) is false) { - await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); return; } - await _memberApplicationManager.EnsureMemberApplicationAsync( + await memberApplicationManager.EnsureMemberApplicationAsync( _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls, _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls, cancellationToken); diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index 3d7e01a0ad..7694f83dd2 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -1,14 +1,10 @@ -using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Persistence.EFCore.Factories; using Umbraco.Cms.Persistence.EFCore.Locking; using Umbraco.Cms.Persistence.EFCore.Scoping; @@ -22,13 +18,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { - var optionsBuilder = new DbContextOptionsBuilder(); - services.TryAddSingleton>( - sp => - { - SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); - }); services.AddPooledDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -52,13 +41,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions connectionString = connectionString.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); } - var optionsBuilder = new DbContextOptionsBuilder(); - services.TryAddSingleton>( - sp => - { - defaultEFCoreOptionsAction?.Invoke(optionsBuilder, providerName, connectionString); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); - }); services.AddPooledDbContextFactory(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -99,13 +81,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions { optionsAction ??= (sp, options) => { }; - var optionsBuilder = new DbContextOptionsBuilder(); - - services.TryAddSingleton>(sp => - { - optionsAction.Invoke(sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); - }); services.AddPooledDbContextFactory(optionsAction); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); diff --git a/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs b/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs deleted file mode 100644 index de0f7db200..0000000000 --- a/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Services; - -namespace Umbraco.Cms.Persistence.EFCore.Factories; - -/// -internal class UmbracoPooledDbContextFactory : PooledDbContextFactory - where TContext : DbContext -{ - private readonly IRuntimeState _runtimeState; - private readonly DbContextOptions _options; - - /// - public UmbracoPooledDbContextFactory(IRuntimeState runtimeState, DbContextOptions options, int poolSize = 1024 /*DbContextPool.DefaultPoolSize*/) : base(options, poolSize) - { - _runtimeState = runtimeState; - _options = options; - } - - /// - public override TContext CreateDbContext() - { - if (_runtimeState.Level == RuntimeLevel.Run) - { - return base.CreateDbContext(); - } - else - { - return (TContext?)Activator.CreateInstance(typeof(TContext), _options) ?? throw new InvalidOperationException("Unable to create DbContext"); - } - } - - /// - public override async Task CreateDbContextAsync(CancellationToken cancellationToken = default) - { - if (_runtimeState.Level == RuntimeLevel.Run) - { - return await base.CreateDbContextAsync(cancellationToken); - } - else - { - return (TContext?)Activator.CreateInstance(typeof(TContext), _options) ?? throw new InvalidOperationException("Unable to create DbContext"); - } - } -} diff --git a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs index a27630bd34..3df757ee15 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs @@ -1,3 +1,4 @@ +using System.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; @@ -55,6 +56,10 @@ public class UmbracoDbContext : DbContext { ILogger logger = StaticServiceProvider.Instance.GetRequiredService>(); logger.LogCritical("No connection string was found, cannot setup Umbraco EF Core context"); + + // we're throwing an exception here to make it abundantly clear that one should never utilize (or have a + // dependency on) the DbContext before the connection string has been initialized by the installer. + throw new ConfigurationErrorsException("No connection string was found, cannot setup Umbraco EF Core context"); } IEnumerable migrationProviders = StaticServiceProvider.Instance.GetServices(); From 406848b76f0c84cb250b4c51fda71902116342de Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 11 Dec 2023 08:49:37 +0100 Subject: [PATCH 118/146] Remove date header from webhook request and use constants (#15407) * Remove date header * Move webhook event header names to constants --- src/Umbraco.Core/Constants-WebhookEvents.cs | 54 +++++++++++-------- .../BackgroundJobs/Jobs/WebhookFiring.cs | 7 ++- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Core/Constants-WebhookEvents.cs b/src/Umbraco.Core/Constants-WebhookEvents.cs index 41b9849f08..28c4a42dd2 100644 --- a/src/Umbraco.Core/Constants-WebhookEvents.cs +++ b/src/Umbraco.Core/Constants-WebhookEvents.cs @@ -1,84 +1,96 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { public static class WebhookEvents { - public static class Aliases + public static class HeaderNames { + /// + /// Gets the Umb-Webhook-Event HTTP header name. + /// + public const string EventName = "Umb-Webhook-Event"; /// - /// Webhook event alias for content versions deleted + /// Gets the Umb-Webhook-RetryCount HTTP header name. + /// + public const string RetryCount = "Umb-Webhook-RetryCount"; + } + + public static class Aliases + { + /// + /// Webhook event alias for content versions deleted. /// public const string ContentDeletedVersions = "Umbraco.ContentDeletedVersions"; /// - /// Webhook event alias for content blueprint saved + /// Webhook event alias for content blueprint saved. /// public const string ContentSavedBlueprint = "Umbraco.ContentSavedBlueprint"; /// - /// Webhook event alias for content blueprint deleted + /// Webhook event alias for content blueprint deleted. /// public const string ContentDeletedBlueprint = "Umbraco.ContentDeletedBlueprint"; /// - /// Webhook event alias for content moved into the recycle bin. + /// Webhook event alias for content moved into the recycle bin. /// public const string ContentMovedToRecycleBin = "Umbraco.ContentMovedToRecycleBin"; /// - /// Webhook event alias for content sorted. + /// Webhook event alias for content sorted. /// public const string ContentSorted = "Umbraco.ContentSorted"; /// - /// Webhook event alias for content moved. + /// Webhook event alias for content moved. /// public const string ContentMoved = "Umbraco.ContentMoved"; /// - /// Webhook event alias for content copied. + /// Webhook event alias for content copied. /// public const string ContentCopied = "Umbraco.ContentCopied"; /// - /// Webhook event alias for content emptied recycle bin. + /// Webhook event alias for content emptied recycle bin. /// public const string ContentEmptiedRecycleBin = "Umbraco.ContentEmptiedRecycleBin"; /// - /// Webhook event alias for content rolled back. + /// Webhook event alias for content rolled back. /// public const string ContentRolledBack = "Umbraco.ContentRolledBack"; /// - /// Webhook event alias for content saved. + /// Webhook event alias for content saved. /// public const string ContentSaved = "Umbraco.ContentSaved"; /// - /// Webhook event alias for content publish. + /// Webhook event alias for content publish. /// public const string ContentPublish = "Umbraco.ContentPublish"; /// - /// Webhook event alias for content delete. + /// Webhook event alias for content delete. /// public const string ContentDelete = "Umbraco.ContentDelete"; /// - /// Webhook event alias for content unpublish. + /// Webhook event alias for content unpublish. /// public const string ContentUnpublish = "Umbraco.ContentUnpublish"; /// - /// Webhook event alias for media delete. + /// Webhook event alias for media delete. /// public const string MediaDelete = "Umbraco.MediaDelete"; /// - /// Webhook event alias for media save. + /// Webhook event alias for media save. /// public const string MediaSave = "Umbraco.MediaSave"; } @@ -86,22 +98,22 @@ public static partial class Constants public static class Types { /// - /// Webhook event type for content. + /// Webhook event type for content. /// public const string Content = "Content"; /// - /// Webhook event type for content media. + /// Webhook event type for content media. /// public const string Media = "Media"; /// - /// Webhook event type for content member. + /// Webhook event type for content member. /// public const string Member = "Member"; /// - /// Webhook event type for others, this is the default category if you have not chosen one. + /// Webhook event type for others, this is the default category if you have not chosen one. /// public const string Other = "Other"; } diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs index bda15fd389..e6d25aff0e 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs @@ -1,4 +1,4 @@ -using System.Net.Mime; +using System.Net.Mime; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -101,9 +101,8 @@ public class WebhookFiring : IRecurringBackgroundJob try { // Add headers - request.Headers.Add("Umb-Webhook-Event", eventName); - request.Headers.Add("Umb-Webhook-RetryCount", retryCount.ToString()); - request.Headers.Add("Umb-Webhook-Date", DateTime.Now.ToString("R")); + request.Headers.Add(Constants.WebhookEvents.HeaderNames.EventName, eventName); + request.Headers.Add(Constants.WebhookEvents.HeaderNames.RetryCount, retryCount.ToString()); foreach (KeyValuePair header in webhook.Headers) { From b5544aa52052de6a783e85ff6ef89dfc3e98b613 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 21 Nov 2023 09:49:04 +0100 Subject: [PATCH 119/146] Suppress flow when queueing background threads --- .../Examine/ExamineIndexRebuilder.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs index a1c70d0ec3..ced539ebc0 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs @@ -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 From 3a697d90fccd6831a83190f8fee95b5c3091928d Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 21 Nov 2023 09:49:04 +0100 Subject: [PATCH 120/146] Suppress flow when queueing background threads --- .../Examine/ExamineIndexRebuilder.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs index 5394cdc275..2a89327a33 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs @@ -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 { @@ -96,12 +106,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 From 13cc320f1911d3175453dc925635930f6d4712fa Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:59:03 +0100 Subject: [PATCH 121/146] 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 Co-authored-by: Zeegaan --- .../Repositories/Implement/CreatedPackageSchemaRepository.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 9f921266ca..093fadd8fe 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -266,7 +266,12 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository definition.Name.Replace(' ', '_'))); Directory.CreateDirectory(directoryName); + var expectedRoot = _hostingEnvironment.MapPathContentRoot(_createdPackagesFolderPath); var finalPackagePath = Path.Combine(directoryName, fileName); + if (finalPackagePath.StartsWith(expectedRoot) == false) + { + throw new IOException("Invalid path due to the package name"); + } // Clean existing files foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath }) From 50a107f88561f7a1b27cc24ebcb247ed5ae76110 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:59:03 +0100 Subject: [PATCH 122/146] Merge pull request from GHSA-6324-52pr-h4p5 Co-authored-by: Zeegaan --- .../Repositories/Implement/CreatedPackageSchemaRepository.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 61f0fe126d..8152eb70e9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -311,7 +311,12 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository definition.Name.Replace(' ', '_'))); Directory.CreateDirectory(directoryName); + var expectedRoot = _hostingEnvironment.MapPathContentRoot(_createdPackagesFolderPath); var finalPackagePath = Path.Combine(directoryName, fileName); + if (finalPackagePath.StartsWith(expectedRoot) == false) + { + throw new IOException("Invalid path due to the package name"); + } // Clean existing files foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath }) From be5a740c960a39b33d3506a52a23d6e1d4474b93 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:59:35 +0100 Subject: [PATCH 123/146] 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 Co-authored-by: Jey --- .../Controllers/AuthenticationController.cs | 15 ++++++++++++--- .../src/common/resources/auth.resource.js | 4 ++-- .../views/components/application/umb-login.html | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 4892f53012..4ec726ae09 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -402,6 +402,9 @@ public class AuthenticationController : UmbracoApiControllerBase } BackOfficeIdentityUser? identityUser = await _userManager.FindByEmailAsync(model.Email); + + await Task.Delay(RandomNumberGenerator.GetInt32(400, 2500)); // To randomize response time preventing user enumeration + if (identityUser != null) { IUser? user = _userService.GetByEmail(model.Email); @@ -422,14 +425,20 @@ public class AuthenticationController : UmbracoApiControllerBase var mailMessage = new EmailMessage(from, user.Email, subject, message, true); - await _emailSender.SendAsync(mailMessage, Constants.Web.EmailTypes.PasswordReset, true); + try + { + await _emailSender.SendAsync(mailMessage, Constants.Web.EmailTypes.PasswordReset, true); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending email, please check your SMTP configuration: {ErrorMessage}", ex.Message); + return Ok(); + } _userManager.NotifyForgotPasswordRequested(User, user.Id.ToString()); } } - await Task.Delay(RandomNumberGenerator.GetInt32(400, 2500)); - return Ok(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index e09718176c..7b0a10cf31 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -28,7 +28,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { * }); * * @returns {Promise} resourcePromise object - * + * */ get2FAProviders: function () { @@ -203,7 +203,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { "PostRequestPasswordReset"), { email: email }), - 'Request password reset failed for email ' + email); + 'An email with password reset instructions will be sent to the specified address if it matched our records'); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index 4a9dc85865..69dc038cb8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -158,7 +158,7 @@
- +
From 3331788c5cf35c8591f92ebcdd958af2db6a4eec Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:59:35 +0100 Subject: [PATCH 124/146] Merge pull request from GHSA-8qp8-9rpw-j46c * Ensure that missing access rules do not break the site (#15081) (cherry picked from commit 67771450797eb2c72449ddcabfc38d5ebf99c7f3) * 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: Kenn Jacobsen Co-authored-by: jey Co-authored-by: Jey --- .../Persistence/Dtos/AccessDto.cs | 2 +- .../Controllers/AuthenticationController.cs | 15 ++++++++++++--- .../src/common/resources/auth.resource.js | 4 ++-- .../views/components/application/umb-login.html | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs index 354083dfa8..0821232826 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/AccessDto.cs @@ -37,5 +37,5 @@ internal class AccessDto [ResultColumn] [Reference(ReferenceType.Many, ReferenceMemberName = "AccessId")] - public List Rules { get; set; } = null!; + public List Rules { get; set; } = new(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index beb8787c79..19cc4ae70f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -404,6 +404,9 @@ public class AuthenticationController : UmbracoApiControllerBase } BackOfficeIdentityUser? identityUser = await _userManager.FindByEmailAsync(model.Email); + + await Task.Delay(RandomNumberGenerator.GetInt32(400, 2500)); // To randomize response time preventing user enumeration + if (identityUser != null) { IUser? user = _userService.GetByEmail(model.Email); @@ -424,14 +427,20 @@ public class AuthenticationController : UmbracoApiControllerBase var mailMessage = new EmailMessage(from, user.Email, subject, message, true); - await _emailSender.SendAsync(mailMessage, Constants.Web.EmailTypes.PasswordReset, true); + try + { + await _emailSender.SendAsync(mailMessage, Constants.Web.EmailTypes.PasswordReset, true); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending email, please check your SMTP configuration: {ErrorMessage}", ex.Message); + return Ok(); + } _userManager.NotifyForgotPasswordRequested(User, user.Id.ToString()); } } - await Task.Delay(RandomNumberGenerator.GetInt32(400, 2500)); - return Ok(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index e09718176c..7b0a10cf31 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -28,7 +28,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { * }); * * @returns {Promise} resourcePromise object - * + * */ get2FAProviders: function () { @@ -203,7 +203,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { "PostRequestPasswordReset"), { email: email }), - 'Request password reset failed for email ' + email); + 'An email with password reset instructions will be sent to the specified address if it matched our records'); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index 4a9dc85865..69dc038cb8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -158,7 +158,7 @@
- +
From cdd4d2a00078f2bdec8959038f57ea6fbe7a8cf1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:59:59 +0100 Subject: [PATCH 125/146] 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 Co-authored-by: Zeegaan --- .../Controllers/AnalyticsController.cs | 3 +++ .../Controllers/LanguageController.cs | 1 + .../PublishedSnapshotCacheStatusController.cs | 3 +++ .../Controllers/RedirectUrlManagementController.cs | 13 +++++++++++-- .../Controllers/StylesheetController.cs | 3 +++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs index 1820f2b5e0..b9980308b9 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs @@ -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; diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index bb515b61fc..aaf067022c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -18,6 +18,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers; /// Backoffice controller supporting the dashboard for language administration. /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class LanguageController : UmbracoAuthorizedJsonController { private readonly ILocalizationService _localizationService; diff --git a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs index c8c391d990..91cd16a0f6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs @@ -1,13 +1,16 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Controllers; [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class PublishedSnapshotCacheStatusController : UmbracoAuthorizedApiController { private readonly DistributedCache _distributedCache; diff --git a/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs index c4d7d47d87..08057d82fa 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using System.Security; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -14,11 +15,13 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Controllers; [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public class RedirectUrlManagementController : UmbracoAuthorizedApiController { private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; @@ -45,6 +48,8 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController _configManipulator = configManipulator ?? throw new ArgumentNullException(nameof(configManipulator)); } + private bool IsEnabled => _webRoutingSettings.CurrentValue.DisableRedirectUrlTracking == false; + /// /// Returns true/false of whether redirect tracking is enabled or not /// @@ -52,9 +57,8 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController [HttpGet] public IActionResult GetEnableState() { - var enabled = _webRoutingSettings.CurrentValue.DisableRedirectUrlTracking == false; var userIsAdmin = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin() ?? false; - return Ok(new { enabled, userIsAdmin }); + return Ok(new { enabled = IsEnabled, userIsAdmin }); } //add paging @@ -104,6 +108,11 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController [HttpPost] public IActionResult DeleteRedirectUrl(Guid id) { + if (IsEnabled is false) + { + return BadRequest("Redirect URL tracking is disabled, and therefore no URLs can be deleted."); + } + _redirectUrlService.Delete(id); return Ok(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs index 32adfcfdf6..616edfa04f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs @@ -1,8 +1,10 @@ +using Microsoft.AspNetCore.Authorization; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; using Stylesheet = Umbraco.Cms.Core.Models.ContentEditing.Stylesheet; @@ -12,6 +14,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers; /// The API controller used for retrieving available stylesheets /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class StylesheetController : UmbracoAuthorizedJsonController { private readonly IFileService _fileService; From 42ae37e502c78452c3a8429bd16e23be9886dac4 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:59:59 +0100 Subject: [PATCH 126/146] Merge pull request from GHSA-cfr5-7p54-4qg8 * Apply authorization policies to controllers * Return bad request if we urltracking is disabled --------- Co-authored-by: Zeegaan --- .../Controllers/AnalyticsController.cs | 3 +++ .../Controllers/LanguageController.cs | 1 + .../PublishedSnapshotCacheStatusController.cs | 3 +++ .../Controllers/RedirectUrlManagementController.cs | 13 +++++++++++-- .../Controllers/StylesheetController.cs | 3 +++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs index 1820f2b5e0..b9980308b9 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AnalyticsController.cs @@ -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; diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 4cb7dc52fc..cef2352ab4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -18,6 +18,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers; /// Backoffice controller supporting the dashboard for language administration. /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class LanguageController : UmbracoAuthorizedJsonController { private readonly ILocalizationService _localizationService; diff --git a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs index c8c391d990..91cd16a0f6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs @@ -1,13 +1,16 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Controllers; [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class PublishedSnapshotCacheStatusController : UmbracoAuthorizedApiController { private readonly DistributedCache _distributedCache; diff --git a/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs index d87398d574..34cd759dbb 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using System.Security; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -14,11 +15,13 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Controllers; [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public class RedirectUrlManagementController : UmbracoAuthorizedApiController { private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; @@ -45,6 +48,8 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController _configManipulator = configManipulator ?? throw new ArgumentNullException(nameof(configManipulator)); } + private bool IsEnabled => _webRoutingSettings.CurrentValue.DisableRedirectUrlTracking == false; + /// /// Returns true/false of whether redirect tracking is enabled or not /// @@ -52,9 +57,8 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController [HttpGet] public IActionResult GetEnableState() { - var enabled = _webRoutingSettings.CurrentValue.DisableRedirectUrlTracking == false; var userIsAdmin = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin() ?? false; - return Ok(new { enabled, userIsAdmin }); + return Ok(new { enabled = IsEnabled, userIsAdmin }); } //add paging @@ -104,6 +108,11 @@ public class RedirectUrlManagementController : UmbracoAuthorizedApiController [HttpPost] public IActionResult DeleteRedirectUrl(Guid id) { + if (IsEnabled is false) + { + return BadRequest("Redirect URL tracking is disabled, and therefore no URLs can be deleted."); + } + _redirectUrlService.Delete(id); return Ok(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs index 32adfcfdf6..616edfa04f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs @@ -1,8 +1,10 @@ +using Microsoft.AspNetCore.Authorization; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Extensions; using Stylesheet = Umbraco.Cms.Core.Models.ContentEditing.Stylesheet; @@ -12,6 +14,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers; /// The API controller used for retrieving available stylesheets /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class StylesheetController : UmbracoAuthorizedJsonController { private readonly IFileService _fileService; From 4a7ad4a562e7f07641d4a33cd246509f4ab59cd8 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 11 Dec 2023 14:00:23 +0100 Subject: [PATCH 127/146] Merge pull request from GHSA-v98m-398x-269r --- .../src/views/common/login.controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js index 5827f7e530..ec039dfdd7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js @@ -11,7 +11,12 @@ angular.module('umbraco').controller("Umbraco.LoginController", function (events //check if there's a returnPath query string, if so redirect to it var locationObj = $location.search(); if (locationObj.returnPath) { - path = decodeURIComponent(locationObj.returnPath); + // ensure that the returnPath is a valid URL under the current origin (prevents DOM-XSS among other things) + const returnPath = decodeURIComponent(locationObj.returnPath); + const url = new URL(returnPath, window.location.origin); + if (url.origin === window.location.origin) { + path = returnPath; + } } // Ensure path is not absolute From 237e4e56b2b1a9163f9cee2637ba01cf62239f6a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:00:23 +0100 Subject: [PATCH 128/146] Merge pull request from GHSA-v98m-398x-269r Co-authored-by: kjac --- .../src/views/common/login.controller.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js index 13ca4cb193..ec039dfdd7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js @@ -11,11 +11,12 @@ angular.module('umbraco').controller("Umbraco.LoginController", function (events //check if there's a returnPath query string, if so redirect to it var locationObj = $location.search(); if (locationObj.returnPath) { - // decodeURIComponent(...) does not play nice with OAuth redirect URLs, so until we have a - // dedicated login screen for the new back-office, we need to hardcode this exception - path = locationObj.returnPath.indexOf("/security/back-office/authorize") > 0 - ? locationObj.returnPath - : decodeURIComponent(locationObj.returnPath); + // ensure that the returnPath is a valid URL under the current origin (prevents DOM-XSS among other things) + const returnPath = decodeURIComponent(locationObj.returnPath); + const url = new URL(returnPath, window.location.origin); + if (url.origin === window.location.origin) { + path = returnPath; + } } // Ensure path is not absolute From 5dfa76600a9bd673888ff7f2a6ac5c87297cb981 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 14 Nov 2023 09:14:01 +0100 Subject: [PATCH 129/146] Post fix --- .../Implement/CreatedPackageSchemaRepository.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 093fadd8fe..a7f8f8ed6b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -264,15 +264,17 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository _hostingEnvironment.MapPathContentRoot(Path.Combine( _createdPackagesFolderPath, definition.Name.Replace(' ', '_'))); - Directory.CreateDirectory(directoryName); - var expectedRoot = _hostingEnvironment.MapPathContentRoot(_createdPackagesFolderPath); - var finalPackagePath = Path.Combine(directoryName, fileName); + 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); + + // Clean existing files foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath }) { From 4a0d364d10032d007e60d959d7f495e4ccadb760 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 14 Nov 2023 09:14:01 +0100 Subject: [PATCH 130/146] Post fix --- .../Implement/CreatedPackageSchemaRepository.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 8152eb70e9..6fcb0c9e66 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -309,15 +309,17 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository _hostingEnvironment.MapPathContentRoot(Path.Combine( _createdPackagesFolderPath, definition.Name.Replace(' ', '_'))); - Directory.CreateDirectory(directoryName); - var expectedRoot = _hostingEnvironment.MapPathContentRoot(_createdPackagesFolderPath); - var finalPackagePath = Path.Combine(directoryName, fileName); + 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); + + // Clean existing files foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath }) { From fe6d6f7156466685637f7f7d7fba6c2e0100db7e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 14 Nov 2023 09:14:01 +0100 Subject: [PATCH 131/146] Post fix --- .../Implement/CreatedPackageSchemaRepository.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 61f0fe126d..6fcb0c9e66 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -309,9 +309,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 }) From fa0b8c7b4dd7e6465d05c6a46be91351124ac7d1 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 12 Dec 2023 09:55:56 +0100 Subject: [PATCH 132/146] Ensure thread safety for block editor constructor caches (#15425) --- .../BlockEditorPropertyValueConstructorCacheBase.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs index 3edd1a680e..3af7b4beba 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; @@ -8,7 +9,7 @@ public abstract class BlockEditorPropertyValueConstructorCacheBase where T : IBlockReference { private readonly - Dictionary<(Guid, Guid?), Func> + ConcurrentDictionary<(Guid, Guid?), Func> _constructorCache = new(); public bool TryGetValue((Guid ContentTypeKey, Guid? SettingsTypeKey) key, [MaybeNullWhen(false)] out Func value) From e37e2b5973d0b8335e69ff1ac2cde64596ee283a Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 12 Dec 2023 11:20:44 +0100 Subject: [PATCH 133/146] bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index ba885cb668..308cdf5c7d 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.0.0-rc5", + "version": "13.0.0", "assemblyVersion": { "precision": "build" }, From 6f123224de9f4544e82f73b9d93d8e582a713319 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:03:49 +0100 Subject: [PATCH 134/146] V13: Regression with global window.uui export (#15412) * revert change to load commonjs version of uui * revert to load uui * build login first * build login assets as iife to support loading inside the backoffice --- .../Umbraco.Cms.StaticAssets.csproj | 4 +- .../umbraco/UmbracoBackOffice/Default.cshtml | 4 - .../umbraco/UmbracoLogin/Index.cshtml | 8 +- .../WebAssets/BackOfficeWebAssets.cs | 2 + .../WebAssets/JsInitialize.js | 4 + .../gulp/tasks/dependencies.js | 16 ++++ src/Umbraco.Web.UI.Login/index.html | 2 +- src/Umbraco.Web.UI.Login/package-lock.json | 93 ++++++++++++++++++- src/Umbraco.Web.UI.Login/package.json | 5 +- src/Umbraco.Web.UI.Login/src/external.ts | 9 -- src/Umbraco.Web.UI.Login/src/index.ts | 5 +- src/Umbraco.Web.UI.Login/vite.config.ts | 12 ++- 12 files changed, 132 insertions(+), 32 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Login/src/external.ts diff --git a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj index f2b95c3a8d..d2db9b11eb 100644 --- a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj +++ b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj @@ -23,10 +23,10 @@ - - + + diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml index 29c3e95245..faa84121e6 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml @@ -37,10 +37,6 @@ Umbraco @Html.Raw(await runtimeMinifier.RenderCssHereAsync(BackOfficeWebAssets.UmbracoInitCssBundleName)) - - - -