From fb73a6a66b6b38c1d84beb3c94abfe8d8f40cead Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 18 Oct 2023 12:17:29 +0200 Subject: [PATCH 01/95] 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 02/95] 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 03/95] 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 04/95] 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 05/95] 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 06/95] 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 07/95] 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 08/95] 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 09/95] 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 10/95] 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 11/95] 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 12/95] 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 13/95] 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 14/95] 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 15/95] 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 16/95] 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 17/95] 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 18/95] 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 19/95] 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 20/95] 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 21/95] 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 22/95] 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 23/95] 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 24/95] 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 25/95] 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 26/95] 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 27/95] 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 28/95] 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 29/95] 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 30/95] 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 31/95] 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 32/95] 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 33/95] 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 35/95] 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 42/95] 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 43/95] 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 44/95] 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 45/95] 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 46/95] 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 47/95] 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 48/95] 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 49/95] 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 57/95] 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 c893027f7c905f3effece323ca99eded1f5061bd Mon Sep 17 00:00:00 2001 From: Amalie Wowern Date: Tue, 3 Oct 2023 19:35:57 +0200 Subject: [PATCH 58/95] 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 59/95] 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 60/95] 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 61/95] 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 62/95] 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 63/95] 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 64/95] 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 65/95] 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 66/95] 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 67/95] 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 68/95] 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 d315a1082ef27e3ff8aa75df229945943dfe2974 Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Fri, 1 Dec 2023 13:05:50 +0100 Subject: [PATCH 69/95] 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 70/95] 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 c6d01178c06a519423b3c37fea7bbdd0e536319f Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 1 Dec 2023 14:51:42 +0100 Subject: [PATCH 71/95] 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 72/95] 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 941705b03de1d00676c2889c4438465e2709d55a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 4 Dec 2023 14:34:47 +0100 Subject: [PATCH 73/95] 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 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 74/95] 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 75/95] 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 17eda54bfba9ef0c3a09e7db78b003f57aaf51f7 Mon Sep 17 00:00:00 2001 From: Richard Ockerby Date: Sun, 29 Oct 2023 00:51:34 +0100 Subject: [PATCH 76/95] 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 cb765544557776b84f4a94bdd2f1a4489e5ab013 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 15 Nov 2023 09:34:27 +0100 Subject: [PATCH 77/95] 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 d752853d8955a92b527550a82e340c4986a5b817 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 10 Dec 2023 19:18:32 +0100 Subject: [PATCH 78/95] 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 b5544aa52052de6a783e85ff6ef89dfc3e98b613 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 21 Nov 2023 09:49:04 +0100 Subject: [PATCH 79/95] 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 80/95] 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 81/95] 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 82/95] 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 83/95] 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 84/95] 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 85/95] 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 86/95] 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 87/95] 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 88/95] 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 89/95] 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 90/95] 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 1fe2ec08be6266ab8154bbc1dc3f0b06a3376d27 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 12 Dec 2023 15:35:15 +0100 Subject: [PATCH 91/95] bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index dcb4240d7c..2fd485a967 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.1", + "version": "10.8.2", "assemblyVersion": { "precision": "build" }, From 677ff59d0474dce397af43676573499e0e27c73e Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 12 Dec 2023 14:35:29 +0100 Subject: [PATCH 92/95] Loosen up policies so editors can get what they need --- src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs | 4 +++- .../Controllers/StylesheetController.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index aaf067022c..285e1127eb 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -18,7 +18,6 @@ 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; @@ -43,6 +42,7 @@ public class LanguageController : UmbracoAuthorizedJsonController /// /// [HttpGet] + [Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)] public IDictionary GetAllCultures() => CultureInfo.GetCultures(CultureTypes.AllCultures).DistinctBy(x => x.Name).OrderBy(x => x.EnglishName) .ToDictionary(x => x.Name, x => x.EnglishName); @@ -52,6 +52,7 @@ public class LanguageController : UmbracoAuthorizedJsonController /// /// [HttpGet] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public IEnumerable? GetAllLanguages() { IEnumerable allLanguages = _localizationService.GetAllLanguages(); @@ -60,6 +61,7 @@ public class LanguageController : UmbracoAuthorizedJsonController } [HttpGet] + [Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)] public ActionResult GetLanguage(int id) { ILanguage? lang = _localizationService.GetLanguageById(id); diff --git a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs index 616edfa04f..1fc5f641f6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs @@ -14,7 +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)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public class StylesheetController : UmbracoAuthorizedJsonController { private readonly IFileService _fileService; From 0c355b0cf1be81aeb177e564813903157547ca10 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 12 Dec 2023 15:47:43 +0100 Subject: [PATCH 93/95] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 617555fa04..7a67ed81fd 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.4", + "version": "12.3.5", "assemblyVersion": { "precision": "build" }, From 73fab105d2314d4e284a46438eea69ff8fb27360 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 12 Dec 2023 14:35:29 +0100 Subject: [PATCH 94/95] Loosen up policies so editors can get what they need --- src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs | 5 +++-- .../Controllers/StylesheetController.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index cef2352ab4..4e5ed094ac 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -18,7 +18,6 @@ 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; @@ -36,7 +35,7 @@ public class LanguageController : UmbracoAuthorizedJsonController /// /// [HttpGet] - public IDictionary GetAllCultures() + [Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)]public IDictionary GetAllCultures() => CultureInfo.GetCultures(CultureTypes.AllCultures).DistinctBy(x => x.Name).OrderBy(x => x.EnglishName).ToDictionary(x => x.Name, x => x.EnglishName); /// @@ -44,6 +43,7 @@ public class LanguageController : UmbracoAuthorizedJsonController /// /// [HttpGet] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public IEnumerable? GetAllLanguages() { IEnumerable allLanguages = _localizationService.GetAllLanguages(); @@ -52,6 +52,7 @@ public class LanguageController : UmbracoAuthorizedJsonController } [HttpGet] + [Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)] public ActionResult GetLanguage(int id) { ILanguage? lang = _localizationService.GetLanguageById(id); diff --git a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs index 616edfa04f..1fc5f641f6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs @@ -14,7 +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)] +[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public class StylesheetController : UmbracoAuthorizedJsonController { private readonly IFileService _fileService; From c2c1285d1756b0dee2e35b3946d9ca74cc7ce164 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 12 Dec 2023 15:56:54 +0100 Subject: [PATCH 95/95] Fix json schema --- 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 @@ -