From eb04d55d2b7b1cfd68d2a205033d3951454652cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 16 Mar 2020 20:00:39 +0100 Subject: [PATCH 01/55] Makes sure supplied arguments cannot be used more than once. --- src/Umbraco.Core/FactoryExtensions.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/FactoryExtensions.cs b/src/Umbraco.Core/FactoryExtensions.cs index 8514525417..8ae2f76af3 100644 --- a/src/Umbraco.Core/FactoryExtensions.cs +++ b/src/Umbraco.Core/FactoryExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Umbraco.Core.Composing; @@ -77,15 +78,28 @@ namespace Umbraco.Core var ctorParameters = ctor.GetParameters(); var ctorArgs = new object[ctorParameters.Length]; + var availableArgs = new List(args); var i = 0; foreach (var parameter in ctorParameters) { // no! IsInstanceOfType is not ok here // ReSharper disable once UseMethodIsInstanceOfType - var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); - ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType); + var idx = availableArgs.FindIndex(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); + if(idx >= 0) + { + // Found a suitable supplied argument + ctorArgs[i++] = availableArgs[idx]; + + // A supplied argument can be used at most once + availableArgs.RemoveAt(idx); + } + else + { + // None of the provided arguments is suitable: get an instance from the factory + ctorArgs[i++] = factory.GetInstance(parameter.ParameterType); + } } return ctor.Invoke(ctorArgs); } } -} \ No newline at end of file +} From c3f59602c21d6a07f553066af7e8dd742114fd83 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Wed, 22 Apr 2020 11:33:27 +0100 Subject: [PATCH 02/55] Add GetMainDom method to UmbracoApplication so GetRuntime can be overridden (cherry picked from commit 549b4c58a7b8dec8eae15cb0cbb3bd1e1268ead8) --- src/Umbraco.Web/UmbracoApplication.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index f8ee238da7..b5ecacfd13 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Web; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Runtime; using Umbraco.Web.Runtime; @@ -17,16 +18,24 @@ namespace Umbraco.Web { var logger = SerilogLogger.CreateWithDefaultConfiguration(); + var runtime = new WebRuntime(this, logger, GetMainDom(logger)); + + return runtime; + } + + /// + /// Returns a new MainDom + /// + public static IMainDom GetMainDom(ILogger logger) + { // Determine if we should use the sql main dom or the default var appSettingMainDomLock = ConfigurationManager.AppSettings[Constants.AppSettings.MainDomLock]; var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" ? (IMainDomLock)new SqlMainDomLock(logger) : new MainDomSemaphoreLock(logger); - - var runtime = new WebRuntime(this, logger, new MainDom(logger, mainDomLock)); - return runtime; + return new MainDom(logger, mainDomLock); } /// From bac42c6fb29352efee074e0194fd821870a57a65 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 30 Apr 2020 22:46:06 +1000 Subject: [PATCH 03/55] change to protected --- src/Umbraco.Web/UmbracoApplication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index b5ecacfd13..f5667a5a85 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web /// /// Returns a new MainDom /// - public static IMainDom GetMainDom(ILogger logger) + protected IMainDom GetMainDom(ILogger logger) { // Determine if we should use the sql main dom or the default var appSettingMainDomLock = ConfigurationManager.AppSettings[Constants.AppSettings.MainDomLock]; From 0717d5b2467c78e24a5c0cbee7570e6b11e7fe8b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 May 2020 18:57:55 +1000 Subject: [PATCH 04/55] Fixes: SqlMainDom setting for Azure does not work in Load Balancing #8038 --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index f3bfe4eefc..0107ecb8da 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Web; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -16,7 +17,7 @@ namespace Umbraco.Core.Runtime internal class SqlMainDomLock : IMainDomLock { private string _lockId; - private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom"; + private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom"; private const string UpdatedSuffix = "_updated"; private readonly ILogger _logger; private IUmbracoDatabase _db; @@ -126,6 +127,14 @@ namespace Umbraco.Core.Runtime } + /// + /// Returns the keyvalue table key for the current server/app + /// + private string MainDomKey { get; } = MainDomKeyPrefix + "-" + + (NetworkHelper.MachineName // eg DOMAIN\SERVER + + HttpRuntime.AppDomainAppId) // eg /LM/S3SVC/11/ROOT + .GenerateHash(); + private void ListeningLoop() { while (true) From 2dd938bf29134910f3769b5a26501b473d7185d2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 May 2020 19:12:20 +1000 Subject: [PATCH 05/55] Change to re-use the MainDomId of the normal maindom --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 0107ecb8da..5f5d0d607f 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -3,6 +3,7 @@ using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using System.Web; @@ -130,10 +131,12 @@ namespace Umbraco.Core.Runtime /// /// Returns the keyvalue table key for the current server/app /// - private string MainDomKey { get; } = MainDomKeyPrefix + "-" - + (NetworkHelper.MachineName // eg DOMAIN\SERVER - + HttpRuntime.AppDomainAppId) // eg /LM/S3SVC/11/ROOT - .GenerateHash(); + /// + /// The key is the the normal MainDomId which takes into account the AppDomainAppId and the physical file path of the app and this is + /// combined with the current machine name. The machine name is required because the default semaphore lock is machine wide so it implicitly + /// takes into account machine name whereas this needs to be explicitly per machine. + /// + private string MainDomKey { get; } = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId()).GenerateHash(); private void ListeningLoop() { From c6055cf0e3043020fbce00de809ea662f88d01f9 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Mon, 11 May 2020 14:39:12 +0100 Subject: [PATCH 06/55] Add keys for custom audit events #8050 (#8051) --- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 2 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index d7ccb13193..e764b5591e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -168,6 +168,7 @@ Content sent for publishing Content sent for publishing for languages: %0% Sort child items performed by user + %0% Copy Publish Publish @@ -180,6 +181,7 @@ Send To Publish Send To Publish Sort + Custom History (all variants) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index e4c764e112..e1b1df2c34 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -170,6 +170,7 @@ Content sent for publishing Content sent for publishing for languages: %0% Sort child items performed by user + %0% Copy Publish Publish @@ -183,6 +184,7 @@ Send To Publish Send To Publish Sort + Custom History (all variants) From 89143b5daef2b6996250c6c1a16c741417e2bb1e Mon Sep 17 00:00:00 2001 From: Alan Mac Kenna Date: Sat, 2 May 2020 14:47:59 +0100 Subject: [PATCH 07/55] upgraded tinymce to 4.9.10 --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 8ab5980107..c298f063a7 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -42,7 +42,7 @@ "npm": "6.13.6", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", - "tinymce": "4.9.9", + "tinymce": "4.9.10", "typeahead.js": "0.11.1", "underscore": "1.9.1" }, From b2a9e17c90ab5c9d2bfa7c7b6d2e738a152073e0 Mon Sep 17 00:00:00 2001 From: Alan Thom Date: Fri, 8 May 2020 13:41:28 +0100 Subject: [PATCH 08/55] Adding ng-trim="false" attribute to textarea property editor --- .../src/views/propertyeditors/textarea/textarea.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index d255c4a5d6..87f6ffeac9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -1,6 +1,6 @@
- + {{mandatoryMessage}} From 8059927ac340c346ecfac07b33ef24f06d9d972f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 14 May 2020 08:41:05 +0200 Subject: [PATCH 09/55] Remove "double tabbing" in the datatype picker (#8102) --- .../datatypepicker/datatypepicker.html | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html index b31a396c2d..768f8a8c24 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html @@ -37,9 +37,8 @@
{{key | umbCmsTitleCase}}
public string Culture { get; set; } + /// + /// When dealing with content variants, this is the segment for the variant + /// + public string Segment { get; set; } + /// /// An array of metadata that is parsed out from the file info posted to the server which is set on the client. /// diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index 96a072330b..653b4f427c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -26,6 +26,7 @@ fileManager.setFiles({ propertyAlias: vm.propertyAlias, culture: vm.culture, + segment: vm.segment, files: [] }); //clear the current files @@ -92,6 +93,11 @@ vm.culture = null; } + //normalize segment to null if it's not there + if (!vm.segment) { + vm.segment = null; + } + // TODO: need to figure out what we can do for things like Nested Content var existingClientFiles = checkPendingClientFiles(); @@ -134,11 +140,16 @@ vm.culture = null; } + //normalize segment to null if it's not there + if (!vm.segment) { + vm.segment = null; + } + //check the file manager to see if there's already local files pending for this editor var existingClientFiles = _.map( _.filter(fileManager.getFiles(), function (f) { - return f.alias === vm.propertyAlias && f.culture === vm.culture; + return f.alias === vm.propertyAlias && f.culture === vm.culture && f.segment === vm.segment; }), function (f) { return f.file; @@ -264,7 +275,8 @@ fileManager.setFiles({ propertyAlias: vm.propertyAlias, files: args.files, - culture: vm.culture + culture: vm.culture, + segment: vm.segment }); updateModelFromSelectedFiles(args.files).then(function(newVal) { @@ -287,6 +299,7 @@ templateUrl: 'views/components/upload/umb-property-file-upload.html', bindings: { culture: "@?", + segment: "@?", propertyAlias: "@", value: "<", hideSelection: "<", diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 9e0285d58d..38aee3fc4a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -39,18 +39,22 @@ function fileManager($rootScope) { args.culture = null; } + if (!args.segment) { + args.segment = null; + } + var metaData = []; if (Utilities.isArray(args.metaData)) { metaData = args.metaData; } - //this will clear the files for the current property/culture and then add the new ones for the current property + //this will clear the files for the current property/culture/segment and then add the new ones for the current property fileCollection = _.reject(fileCollection, function (item) { - return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture); + return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture) && (!args.segment || args.segment === item.segment); }); for (var i = 0; i < args.files.length; i++) { //save the file object to the files collection - fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData }); + fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, segment: args.segment, metaData: metaData }); } }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index edf698c8a7..4cbc5e567a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -252,12 +252,13 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe for (var f in args.files) { //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key // so we know which property it belongs to on the server side - var fileKey = "file_" + args.files[f].alias + "_" + (args.files[f].culture ? args.files[f].culture : ""); + var file = args.files[f]; + var fileKey = "file_" + file.alias + "_" + (file.culture ? file.culture : "") + "_" + (file.segment ? file.segment : ""); - if (Utilities.isArray(args.files[f].metaData) && args.files[f].metaData.length > 0) { - fileKey += ("_" + args.files[f].metaData.join("_")); + if (Utilities.isArray(file.metaData) && file.metaData.length > 0) { + fileKey += ("_" + file.metaData.join("_")); } - formData.append(fileKey, args.files[f].file); + formData.append(fileKey, file.file); } }).then(function (response) { //success callback diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 17959b9950..c485f4bbc6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -34,6 +34,7 @@ fileManager.setFiles({ propertyAlias: $scope.model.alias, culture: $scope.model.culture, + segment: $scope.model.segment, files: [] }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index 2bc609714a..522278e99e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -1,5 +1,6 @@ 
3) + { + segment = parts[3]; + //normalize to null if empty + if (segment.IsNullOrWhiteSpace()) + { + segment = null; + } + } + + // TODO: anything after 4 parts we can put in metadata var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); @@ -59,6 +71,7 @@ namespace Umbraco.Web.Editors.Binders TempFilePath = file.LocalFileName, PropertyAlias = propAlias, Culture = culture, + Segment = segment, FileName = fileName }); } diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 300c777b3a..893c9f5941 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -76,7 +76,7 @@ namespace Umbraco.Web.Editors // prepare files, if any matching property and culture var files = contentItem.UploadedFiles - .Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture) + .Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture && x.Segment == propertyDto.Segment) .ToArray(); foreach (var file in files) From c36495ac9c5f00c02943b9df18f36ce5b58d1571 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 20 May 2020 10:30:07 +0200 Subject: [PATCH 13/55] Use a language that's more common to people's machines --- .../cypress/integration/Settings/languages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts index 17b7bb6805..49bcf94943 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts @@ -6,7 +6,7 @@ context('Languages', () => { }); it('Add language', () => { - const name = "Neddersass’sch (Nedderlannen)"; // Must be an option in the select box + const name = "Kyrgyz (Kyrgyzstan)"; // Must be an option in the select box cy.umbracoEnsureLanguageNameNotExists(name); From 5b7f1227f9b4f97ede6e2799a1aa36bb9c4fd823 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 22 May 2020 10:46:13 +0200 Subject: [PATCH 14/55] Bugfix for issue with hanging saves. + Fix for double post of save templates --- .../cypress/integration/Settings/templates.ts | 28 +++++++++---------- src/Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../NuCache/PublishedSnapshotService.cs | 23 ++++++--------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts index bbabbcb4bf..c135258ec5 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts @@ -6,28 +6,28 @@ context('Templates', () => { }); it('Create template', () => { - const name = "Test template"; + const name = "Test template"; - cy.umbracoEnsureTemplateNameNotExists(name); + cy.umbracoEnsureTemplateNameNotExists(name); - cy.umbracoSection('settings'); - cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); - cy.umbracoTreeItem("settings", ["Templates"]).rightclick(); + cy.umbracoTreeItem("settings", ["Templates"]).rightclick(); - cy.umbracoContextMenuAction("action-create").click(); + cy.umbracoContextMenuAction("action-create").click(); - //Type name - cy.umbracoEditorHeaderName(name); + //Type name + cy.umbracoEditorHeaderName(name); - //Save - cy.get('.btn-success').click(); + //Save + cy.get("form[name='contentForm']").submit(); - //Assert - cy.umbracoSuccessNotification().should('be.visible'); + //Assert + cy.umbracoSuccessNotification().should('be.visible'); - //Clean up - cy.umbracoEnsureTemplateNameNotExists(name); + //Clean up + cy.umbracoEnsureTemplateNameNotExists(name); }); }); diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index daa1c424bb..997de4db77 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -6,7 +6,7 @@ "devDependencies": { "cross-env": "^7.0.2", "ncp": "^2.0.0", - "cypress": "^4.5.0", + "cypress": "^4.6.0", "umbraco-cypress-testhelpers": "1.0.0-beta-38" }, "dependencies": { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 7e78b2e96f..a39e26e2b1 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -882,22 +882,15 @@ namespace Umbraco.Web.PublishedCache.NuCache // they require. // These can be run side by side in parallel. + using (_contentStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); + } - Parallel.Invoke( - () => - { - using (_contentStore.GetScopedWriteLock(_scopeProvider)) - { - NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); - } - }, - () => - { - using (_mediaStore.GetScopedWriteLock(_scopeProvider)) - { - NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); - } - }); + using (_mediaStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); + } } ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); From 5be8282ce45c3a11ff2d23eb2667e5bfc9d4ec7d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 22 May 2020 12:00:56 +0200 Subject: [PATCH 15/55] Delete templates test --- src/Umbraco.Tests.AcceptanceTest/cypress.json | 3 ++- .../cypress/integration/Settings/templates.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress.json b/src/Umbraco.Tests.AcceptanceTest/cypress.json index 051bf4a871..33978211ed 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress.json +++ b/src/Umbraco.Tests.AcceptanceTest/cypress.json @@ -6,5 +6,6 @@ "username": "", "password": "" }, - "supportFile": "cypress/support/index.ts" + "supportFile": "cypress/support/index.ts", + "videoUploadOnPasses" : false } diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts index c135258ec5..6871db7ffe 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts @@ -1,4 +1,6 @@ /// +import {DocumentTypeBuilder, TemplateBuilder} from "umbraco-cypress-testhelpers"; + context('Templates', () => { beforeEach(() => { @@ -30,4 +32,26 @@ context('Templates', () => { cy.umbracoEnsureTemplateNameNotExists(name); }); + it('Delete template', () => { + const name = "Test template"; + cy.umbracoEnsureTemplateNameNotExists(name); + + const template = new TemplateBuilder() + .withName(name) + .build(); + + cy.saveTemplate(template); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Templates", name]).rightclick(); + cy.umbracoContextMenuAction("action-delete").click(); + + cy.umbracoButtonByLabelKey("general_ok").click(); + + cy.contains(name).should('not.exist'); + + cy.umbracoEnsureTemplateNameNotExists(name); + }); }); From 1a04b9711b565d173f336bdb4dc96745c87b3e3f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 22 May 2020 12:56:17 +0200 Subject: [PATCH 16/55] Updated testhelpers reference --- src/Umbraco.Tests.AcceptanceTest/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index 997de4db77..ad125d090a 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -7,7 +7,7 @@ "cross-env": "^7.0.2", "ncp": "^2.0.0", "cypress": "^4.6.0", - "umbraco-cypress-testhelpers": "1.0.0-beta-38" + "umbraco-cypress-testhelpers": "1.0.0-beta-39" }, "dependencies": { "typescript": "^3.9.2" From 4c16421cbb60f1f0b4f2e9be4fad602f436866bd Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Sat, 23 May 2020 16:15:42 +0100 Subject: [PATCH 17/55] Update umb-button.html (#8145) Noticed the missing closing bracket on the `
From b6262bf33413493f952d088c4e61150b641998ec Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Mon, 25 May 2020 16:51:52 +0100 Subject: [PATCH 18/55] Remove ContentFinderByRedirectUrl if RedirectUrlTracking is disabled (#7761) --- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 203bae4854..c631aac5e3 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -179,7 +179,7 @@ namespace Umbraco.Web.Runtime .Remove() .Remove() .Remove(); - + // add all known factories, devs can then modify this list on application // startup either by binding to events or in their own global.asax composition.FilteredControllerFactory() @@ -203,8 +203,12 @@ namespace Umbraco.Web.Runtime .Append() .Append() //.Append() // disabled, this is an odd finder - .Append() - .Append(); + .Append(); + //only append ContentFinderByRedirectUrl if RedirectUrlTracking is not disabled + if (composition.Configs.Settings().WebRouting.DisableRedirectUrlTracking == false) + { + composition.ContentFinders().Append(); + } composition.RegisterUnique(); From 5215a769feb593d4675d832e112618a581912cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 25 May 2020 21:30:56 +0200 Subject: [PATCH 19/55] Added unit test that fails before PR and succeeds after PR --- .../Composing/ContainerConformingTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index f5c1ff9bc7..06ab246d84 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -330,6 +330,17 @@ namespace Umbraco.Tests.Composing var s1 = factory.GetInstance(); var s2 = factory.GetInstance(); Assert.AreSame(s1, s2); + + register.Register(factory => + { + var param1 = new Thing1(); + var param2 = new Thing1(); + + return factory.CreateInstance(param1, param2); + }); + + var instance = factory.GetInstance(); + Assert.AreNotEqual(instance.Thing, instance.AnotherThing); } public interface IThing { } @@ -352,5 +363,17 @@ namespace Umbraco.Tests.Composing public IEnumerable Things { get; } } + + public class Thing4 : ThingBase + { + public readonly Thing1 Thing; + public readonly Thing1 AnotherThing; + + public Thing4(Thing1 thing, Thing1 anotherThing) + { + Thing = thing; + AnotherThing = anotherThing; + } + } } } From 558ffaeba70e922240a124a47faa1718a8f3e0be Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 May 2020 09:18:09 +0200 Subject: [PATCH 20/55] Move test and use params that are easier to distinguish --- .../Composing/ContainerConformingTests.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index 06ab246d84..22978bdb43 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -330,15 +330,20 @@ namespace Umbraco.Tests.Composing var s1 = factory.GetInstance(); var s2 = factory.GetInstance(); Assert.AreSame(s1, s2); + } - register.Register(factory => + [Test] + public void CanRegisterMultipleSameTypeParametersWithCreateInstance() + { + var register = GetRegister(); + var factory = register.CreateFactory(); + register.Register(factory => { - var param1 = new Thing1(); - var param2 = new Thing1(); + var param1 = "param1"; + var param2 = "param2"; return factory.CreateInstance(param1, param2); }); - var instance = factory.GetInstance(); Assert.AreNotEqual(instance.Thing, instance.AnotherThing); } @@ -366,10 +371,10 @@ namespace Umbraco.Tests.Composing public class Thing4 : ThingBase { - public readonly Thing1 Thing; - public readonly Thing1 AnotherThing; + public readonly string Thing; + public readonly string AnotherThing; - public Thing4(Thing1 thing, Thing1 anotherThing) + public Thing4(string thing, string anotherThing) { Thing = thing; AnotherThing = anotherThing; From 41ac96d69da6af39407cceaa022435645f543ec3 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Tue, 26 May 2020 08:45:57 +0100 Subject: [PATCH 21/55] Fix HasValue and IsValue for Dropdown Property Types with no Values (#8154) --- .../dropdownFlexible/dropdownFlexible.controller.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js index a8979c949b..afbb4feb20 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js @@ -15,7 +15,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo //ensure this is a bool, old data could store zeros/ones or string versions $scope.model.config.multiple = Object.toBoolean($scope.model.config.multiple); - + + //ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected + $scope.$on("formSubmitting", function () { + if ($scope.model.value.length === 0 || $scope.model.value[0] === null) { + $scope.model.value = null; + } + }); + function convertArrayToDictionaryArray(model){ //now we need to format the items in the dictionary because we always want to have an array var newItems = []; From f33b4b7be534e6e3504da53fce20450e6201477e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 May 2020 11:49:35 +0200 Subject: [PATCH 22/55] Some cleanup and fixing the build --- .../Composing/ContainerConformingTests.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index 22978bdb43..d7a19cb553 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -336,14 +336,16 @@ namespace Umbraco.Tests.Composing public void CanRegisterMultipleSameTypeParametersWithCreateInstance() { var register = GetRegister(); - var factory = register.CreateFactory(); - register.Register(factory => - { - var param1 = "param1"; - var param2 = "param2"; - return factory.CreateInstance(param1, param2); + register.Register(c => + { + const string param1 = "param1"; + const string param2 = "param2"; + + return c.CreateInstance(param1, param2); }); + + var factory = register.CreateFactory(); var instance = factory.GetInstance(); Assert.AreNotEqual(instance.Thing, instance.AnotherThing); } From e879c5c7d9562270bacb3b5de81066848dddfa98 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 26 May 2020 13:08:20 +0200 Subject: [PATCH 23/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Migrated CodeFileController --- .../Controllers}/CodeFileController.cs | 280 +++++++++--------- .../Trees/UrlHelperExtensions.cs | 42 +++ .../ActionsResults/UmbracoProblemResult.cs | 13 + ...racoApiBehaviorApplicationModelProvider.cs | 6 +- .../Exceptions/HttpResponseException.cs | 12 + src/Umbraco.Web/Trees/UrlHelperExtensions.cs | 29 -- 6 files changed, 213 insertions(+), 169 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/CodeFileController.cs (69%) create mode 100644 src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs create mode 100644 src/Umbraco.Web.Common/ActionsResults/UmbracoProblemResult.cs diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs similarity index 69% rename from src/Umbraco.Web/Editors/CodeFileController.cs rename to src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 2de3283b95..cfb4d94ad4 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -1,59 +1,66 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Web.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; + using Umbraco.Core.Configuration; + using Umbraco.Core.Configuration.Legacy; using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; + using Umbraco.Core.Mapping; + using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Core.Strings.Css; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Web.Trees; + using Umbraco.Core.Strings; + using Umbraco.Core.Strings.Css; + using Umbraco.Extensions; + using Umbraco.Web.Models.ContentEditing; using Stylesheet = Umbraco.Core.Models.Stylesheet; using StylesheetRule = Umbraco.Web.Models.ContentEditing.StylesheetRule; -using Umbraco.Core.Mapping; -using Umbraco.Web.Routing; +using Umbraco.Web.BackOffice.Filters; + using Umbraco.Web.Common.ActionsResults; + using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Editors; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { // TODO: Put some exception filters in our webapi to return 404 instead of 500 when we throw ArgumentNullException // ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/ [PluginController("UmbracoApi")] - [PrefixlessBodyModelValidator] + //[PrefixlessBodyModelValidator] [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] public class CodeFileController : BackOfficeNotificationsController { private readonly IIOHelper _ioHelper; private readonly IFileSystems _fileSystems; + private readonly IFileService _fileService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILocalizedTextService _localizedTextService; + private readonly UmbracoMapper _umbracoMapper; + private readonly IShortStringHelper _shortStringHelper; + private readonly IGlobalSettings _globalSettings; public CodeFileController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, - UmbracoMapper umbracoMapper, IIOHelper ioHelper, IFileSystems fileSystems, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) + IFileService fileService, + IUmbracoContextAccessor umbracoContextAccessor, + ILocalizedTextService localizedTextService, + UmbracoMapper umbracoMapper, + IShortStringHelper shortStringHelper, + IGlobalSettings globalSettings) { + _ioHelper = ioHelper; _fileSystems = fileSystems; + _fileService = fileService; + _umbracoContextAccessor = umbracoContextAccessor; + _localizedTextService = localizedTextService; + _umbracoMapper = umbracoMapper; + _shortStringHelper = shortStringHelper; + _globalSettings = globalSettings; } ///
@@ -63,32 +70,33 @@ namespace Umbraco.Web.Editors /// /// Will return a simple 200 if file creation succeeds [ValidationFilter] - public HttpResponseMessage PostCreate(string type, CodeFileDisplay display) + public IActionResult PostCreate(string type, CodeFileDisplay display) { if (display == null) throw new ArgumentNullException("display"); if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; switch (type) { case Core.Constants.Trees.PartialViews: var view = new PartialView(PartialViewType.PartialView, display.VirtualPath); view.Content = display.Content; - var result = Services.FileService.CreatePartialView(view, display.Snippet, Security.CurrentUser.Id); - return result.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + var result = _fileService.CreatePartialView(view, display.Snippet, currentUser.Id); + return result.Success == true ? Ok() : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); case Core.Constants.Trees.PartialViewMacros: var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath); viewMacro.Content = display.Content; - var resultMacro = Services.FileService.CreatePartialViewMacro(viewMacro, display.Snippet, Security.CurrentUser.Id); - return resultMacro.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(resultMacro.Exception.Message); + var resultMacro = _fileService.CreatePartialViewMacro(viewMacro, display.Snippet, currentUser.Id); + return resultMacro.Success == true ? Ok() : throw HttpResponseException.CreateNotificationValidationErrorResponse(resultMacro.Exception.Message); case Core.Constants.Trees.Scripts: var script = new Script(display.VirtualPath); - Services.FileService.SaveScript(script, Security.CurrentUser.Id); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.SaveScript(script, currentUser.Id); + return Ok(); default: - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); } } @@ -100,13 +108,13 @@ namespace Umbraco.Web.Editors /// The name of the container/folder /// [HttpPost] - public HttpResponseMessage PostCreateContainer(string type, string parentId, string name) + public IActionResult PostCreateContainer(string type, string parentId, string name) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); if (name.ContainsAny(Path.GetInvalidPathChars())) { - return Request.CreateNotificationValidationErrorResponse(Services.TextService.Localize("codefile/createFolderIllegalChars")); + throw HttpResponseException.CreateNotificationValidationErrorResponse(_localizedTextService.Localize("codefile/createFolderIllegalChars")); } // if the parentId is root (-1) then we just need an empty string as we are @@ -129,24 +137,24 @@ namespace Umbraco.Web.Editors { case Core.Constants.Trees.PartialViews: virtualPath = NormalizeVirtualPath(name, Core.Constants.SystemDirectories.PartialViews); - Services.FileService.CreatePartialViewFolder(virtualPath); + _fileService.CreatePartialViewFolder(virtualPath); break; case Core.Constants.Trees.PartialViewMacros: virtualPath = NormalizeVirtualPath(name, Core.Constants.SystemDirectories.MacroPartials); - Services.FileService.CreatePartialViewMacroFolder(virtualPath); + _fileService.CreatePartialViewMacroFolder(virtualPath); break; case Core.Constants.Trees.Scripts: - virtualPath = NormalizeVirtualPath(name, GlobalSettings.UmbracoScriptsPath); - Services.FileService.CreateScriptFolder(virtualPath); + virtualPath = NormalizeVirtualPath(name, _globalSettings.UmbracoScriptsPath); + _fileService.CreateScriptFolder(virtualPath); break; case Core.Constants.Trees.Stylesheets: - virtualPath = NormalizeVirtualPath(name, GlobalSettings.UmbracoCssPath); - Services.FileService.CreateStyleSheetFolder(virtualPath); + virtualPath = NormalizeVirtualPath(name, _globalSettings.UmbracoCssPath); + _fileService.CreateStyleSheetFolder(virtualPath); break; } - return Request.CreateResponse(HttpStatusCode.OK, new CodeFileDisplay + return Ok(new CodeFileDisplay { VirtualPath = virtualPath, Path = Url.GetTreePathFromFilePath(virtualPath) @@ -159,7 +167,7 @@ namespace Umbraco.Web.Editors /// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets' /// The filename or urlencoded path of the file to open /// The file and its contents from the virtualPath - public CodeFileDisplay GetByPath(string type, string virtualPath) + public IActionResult GetByPath(string type, string virtualPath) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath"); @@ -169,55 +177,53 @@ namespace Umbraco.Web.Editors switch (type) { case Core.Constants.Trees.PartialViews: - var view = Services.FileService.GetPartialView(virtualPath); + var view = _fileService.GetPartialView(virtualPath); if (view != null) { - var display = Mapper.Map(view); + var display = _umbracoMapper.Map(view); display.FileType = Core.Constants.Trees.PartialViews; display.Path = Url.GetTreePathFromFilePath(view.Path); display.Id = System.Web.HttpUtility.UrlEncode(view.Path); - return display; + return Ok(display); } - throw new HttpResponseException(HttpStatusCode.NotFound); + break; case Core.Constants.Trees.PartialViewMacros: - var viewMacro = Services.FileService.GetPartialViewMacro(virtualPath); + var viewMacro = _fileService.GetPartialViewMacro(virtualPath); if (viewMacro != null) { - var display = Mapper.Map(viewMacro); + var display = _umbracoMapper.Map(viewMacro); display.FileType = Core.Constants.Trees.PartialViewMacros; display.Path = Url.GetTreePathFromFilePath(viewMacro.Path); display.Id = System.Web.HttpUtility.UrlEncode(viewMacro.Path); - return display; + return Ok(display); } - throw new HttpResponseException(HttpStatusCode.NotFound); - + break; case Core.Constants.Trees.Scripts: - var script = Services.FileService.GetScriptByName(virtualPath); + var script = _fileService.GetScriptByName(virtualPath); if (script != null) { - var display = Mapper.Map(script); + var display = _umbracoMapper.Map(script); display.FileType = Core.Constants.Trees.Scripts; display.Path = Url.GetTreePathFromFilePath(script.Path); display.Id = System.Web.HttpUtility.UrlEncode(script.Path); - return display; + return Ok(display); } - throw new HttpResponseException(HttpStatusCode.NotFound); - + break; case Core.Constants.Trees.Stylesheets: - var stylesheet = Services.FileService.GetStylesheetByName(virtualPath); + var stylesheet = _fileService.GetStylesheetByName(virtualPath); if (stylesheet != null) { - var display = Mapper.Map(stylesheet); + var display = _umbracoMapper.Map(stylesheet); display.FileType = Core.Constants.Trees.Stylesheets; display.Path = Url.GetTreePathFromFilePath(stylesheet.Path); display.Id = System.Web.HttpUtility.UrlEncode(stylesheet.Path); - return display; + return Ok(display); } - throw new HttpResponseException(HttpStatusCode.NotFound); + break; } - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } /// @@ -233,7 +239,7 @@ namespace Umbraco.Web.Editors switch (type) { case Core.Constants.Trees.PartialViews: - snippets = Services.FileService.GetPartialViewSnippetNames( + snippets = _fileService.GetPartialViewSnippetNames( //ignore these - (this is taken from the logic in "PartialView.ascx.cs") "Gallery", "ListChildPagesFromChangeableSource", @@ -241,13 +247,13 @@ namespace Umbraco.Web.Editors "ListImagesFromMediaFolder"); break; case Core.Constants.Trees.PartialViewMacros: - snippets = Services.FileService.GetPartialViewSnippetNames(); + snippets = _fileService.GetPartialViewSnippetNames(); break; default: throw new HttpResponseException(HttpStatusCode.NotFound); } - return snippets.Select(snippet => new SnippetDisplay() {Name = snippet.SplitPascalCasing(ShortStringHelper).ToFirstUpperInvariant(), FileName = snippet}); + return snippets.Select(snippet => new SnippetDisplay() {Name = snippet.SplitPascalCasing(_shortStringHelper).ToFirstUpperInvariant(), FileName = snippet}); } /// @@ -257,7 +263,7 @@ namespace Umbraco.Web.Editors /// /// /// - public CodeFileDisplay GetScaffold(string type, string id, string snippetName = null) + public IActionResult GetScaffold(string type, string id, string snippetName = null) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id"); @@ -267,27 +273,27 @@ namespace Umbraco.Web.Editors switch (type) { case Core.Constants.Trees.PartialViews: - codeFileDisplay = Mapper.Map(new PartialView(PartialViewType.PartialView, string.Empty)); + codeFileDisplay = _umbracoMapper.Map(new PartialView(PartialViewType.PartialView, string.Empty)); codeFileDisplay.VirtualPath = Core.Constants.SystemDirectories.PartialViews; if (snippetName.IsNullOrWhiteSpace() == false) - codeFileDisplay.Content = Services.FileService.GetPartialViewSnippetContent(snippetName); + codeFileDisplay.Content = _fileService.GetPartialViewSnippetContent(snippetName); break; case Core.Constants.Trees.PartialViewMacros: - codeFileDisplay = Mapper.Map(new PartialView(PartialViewType.PartialViewMacro, string.Empty)); + codeFileDisplay = _umbracoMapper.Map(new PartialView(PartialViewType.PartialViewMacro, string.Empty)); codeFileDisplay.VirtualPath = Core.Constants.SystemDirectories.MacroPartials; if (snippetName.IsNullOrWhiteSpace() == false) - codeFileDisplay.Content = Services.FileService.GetPartialViewMacroSnippetContent(snippetName); + codeFileDisplay.Content = _fileService.GetPartialViewMacroSnippetContent(snippetName); break; case Core.Constants.Trees.Scripts: - codeFileDisplay = Mapper.Map(new Script(string.Empty)); - codeFileDisplay.VirtualPath = GlobalSettings.UmbracoScriptsPath; + codeFileDisplay = _umbracoMapper.Map(new Script(string.Empty)); + codeFileDisplay.VirtualPath = _globalSettings.UmbracoScriptsPath; break; case Core.Constants.Trees.Stylesheets: - codeFileDisplay = Mapper.Map(new Stylesheet(string.Empty)); - codeFileDisplay.VirtualPath = GlobalSettings.UmbracoCssPath; + codeFileDisplay = _umbracoMapper.Map(new Stylesheet(string.Empty)); + codeFileDisplay.VirtualPath = _globalSettings.UmbracoCssPath; break; default: - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Unsupported editortype")); + return new UmbracoProblemResult("Unsupported editortype", HttpStatusCode.BadRequest); } // Make sure that the root virtual path ends with '/' @@ -302,7 +308,7 @@ namespace Umbraco.Web.Editors codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~"); codeFileDisplay.FileType = type; - return codeFileDisplay; + return new OkObjectResult(codeFileDisplay); } /// @@ -313,67 +319,65 @@ namespace Umbraco.Web.Editors /// Will return a simple 200 if file deletion succeeds [HttpDelete] [HttpPost] - public HttpResponseMessage Delete(string type, string virtualPath) + public IActionResult Delete(string type, string virtualPath) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath"); virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); - + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; switch (type) { case Core.Constants.Trees.PartialViews: if (IsDirectory(virtualPath, Core.Constants.SystemDirectories.PartialViews)) { - Services.FileService.DeletePartialViewFolder(virtualPath); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.DeletePartialViewFolder(virtualPath); + return Ok(); } - if (Services.FileService.DeletePartialView(virtualPath, Security.CurrentUser.Id)) + if (_fileService.DeletePartialView(virtualPath, currentUser.Id)) { - return Request.CreateResponse(HttpStatusCode.OK); + return Ok(); } - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View or folder found with the specified path"); + return new UmbracoProblemResult("No Partial View or folder found with the specified path", HttpStatusCode.NotFound); case Core.Constants.Trees.PartialViewMacros: if (IsDirectory(virtualPath, Core.Constants.SystemDirectories.MacroPartials)) { - Services.FileService.DeletePartialViewMacroFolder(virtualPath); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.DeletePartialViewMacroFolder(virtualPath); + return Ok(); } - if (Services.FileService.DeletePartialViewMacro(virtualPath, Security.CurrentUser.Id)) + if (_fileService.DeletePartialViewMacro(virtualPath, currentUser.Id)) { - return Request.CreateResponse(HttpStatusCode.OK); + return Ok(); } - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro or folder found with the specified path"); + return new UmbracoProblemResult("No Partial View Macro or folder found with the specified path", HttpStatusCode.NotFound); case Core.Constants.Trees.Scripts: - if (IsDirectory(virtualPath, GlobalSettings.UmbracoScriptsPath)) + if (IsDirectory(virtualPath, _globalSettings.UmbracoScriptsPath)) { - Services.FileService.DeleteScriptFolder(virtualPath); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.DeleteScriptFolder(virtualPath); + return Ok(); } - if (Services.FileService.GetScriptByName(virtualPath) != null) + if (_fileService.GetScriptByName(virtualPath) != null) { - Services.FileService.DeleteScript(virtualPath, Security.CurrentUser.Id); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.DeleteScript(virtualPath, currentUser.Id); + return Ok(); } - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script or folder found with the specified path"); - + return new UmbracoProblemResult("No Script or folder found with the specified path", HttpStatusCode.NotFound); case Core.Constants.Trees.Stylesheets: - if (IsDirectory(virtualPath, GlobalSettings.UmbracoCssPath)) + if (IsDirectory(virtualPath, _globalSettings.UmbracoCssPath)) { - Services.FileService.DeleteStyleSheetFolder(virtualPath); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.DeleteStyleSheetFolder(virtualPath); + return Ok(); } - if (Services.FileService.GetStylesheetByName(virtualPath) != null) + if (_fileService.GetStylesheetByName(virtualPath) != null) { - Services.FileService.DeleteStylesheet(virtualPath, Security.CurrentUser.Id); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.DeleteStylesheet(virtualPath, currentUser.Id); + return Ok(); } - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Stylesheet found with the specified path"); - + return new UmbracoProblemResult("No Stylesheet found with the specified path", HttpStatusCode.NotFound); default: - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); } throw new HttpResponseException(HttpStatusCode.NotFound); @@ -384,13 +388,13 @@ namespace Umbraco.Web.Editors /// /// /// The updated CodeFileDisplay model - public CodeFileDisplay PostSave(CodeFileDisplay display) + public IActionResult PostSave(CodeFileDisplay display) { if (display == null) throw new ArgumentNullException("display"); if (ModelState.IsValid == false) { - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + return ValidationProblem(ModelState); } switch (display.FileType) @@ -399,57 +403,57 @@ namespace Umbraco.Web.Editors var partialViewResult = CreateOrUpdatePartialView(display); if (partialViewResult.Success) { - display = Mapper.Map(partialViewResult.Result, display); + display = _umbracoMapper.Map(partialViewResult.Result, display); display.Path = Url.GetTreePathFromFilePath(partialViewResult.Result.Path); display.Id = System.Web.HttpUtility.UrlEncode(partialViewResult.Result.Path); - return display; + return Ok(display); } display.AddErrorNotification( - Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), - Services.TextService.Localize("speechBubbles/partialViewErrorText")); + _localizedTextService.Localize("speechBubbles/partialViewErrorHeader"), + _localizedTextService.Localize("speechBubbles/partialViewErrorText")); break; case Core.Constants.Trees.PartialViewMacros: var partialViewMacroResult = CreateOrUpdatePartialViewMacro(display); if (partialViewMacroResult.Success) { - display = Mapper.Map(partialViewMacroResult.Result, display); + display = _umbracoMapper.Map(partialViewMacroResult.Result, display); display.Path = Url.GetTreePathFromFilePath(partialViewMacroResult.Result.Path); display.Id = System.Web.HttpUtility.UrlEncode(partialViewMacroResult.Result.Path); - return display; + return Ok(display); } display.AddErrorNotification( - Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), - Services.TextService.Localize("speechBubbles/partialViewErrorText")); + _localizedTextService.Localize("speechBubbles/partialViewErrorHeader"), + _localizedTextService.Localize("speechBubbles/partialViewErrorText")); break; case Core.Constants.Trees.Scripts: var scriptResult = CreateOrUpdateScript(display); - display = Mapper.Map(scriptResult, display); + display = _umbracoMapper.Map(scriptResult, display); display.Path = Url.GetTreePathFromFilePath(scriptResult.Path); display.Id = System.Web.HttpUtility.UrlEncode(scriptResult.Path); - return display; + return Ok(display); //display.AddErrorNotification( - // Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), - // Services.TextService.Localize("speechBubbles/partialViewErrorText")); + // _localizedTextService.Localize("speechBubbles/partialViewErrorHeader"), + // _localizedTextService.Localize("speechBubbles/partialViewErrorText")); case Core.Constants.Trees.Stylesheets: var stylesheetResult = CreateOrUpdateStylesheet(display); - display = Mapper.Map(stylesheetResult, display); + display = _umbracoMapper.Map(stylesheetResult, display); display.Path = Url.GetTreePathFromFilePath(stylesheetResult.Path); display.Id = System.Web.HttpUtility.UrlEncode(stylesheetResult.Path); - return display; + return Ok(display); default: throw new HttpResponseException(HttpStatusCode.NotFound); } - return display; + return Ok(display); } /// @@ -524,16 +528,16 @@ namespace Umbraco.Web.Editors private IScript CreateOrUpdateScript(CodeFileDisplay display) { return CreateOrUpdateFile(display, ".js", _fileSystems.ScriptsFileSystem, - name => Services.FileService.GetScriptByName(name), - (script, userId) => Services.FileService.SaveScript(script, userId), + name => _fileService.GetScriptByName(name), + (script, userId) => _fileService.SaveScript(script, userId), name => new Script(name)); } private IStylesheet CreateOrUpdateStylesheet(CodeFileDisplay display) { return CreateOrUpdateFile(display, ".css", _fileSystems.StylesheetsFileSystem, - name => Services.FileService.GetStylesheetByName(name), - (stylesheet, userId) => Services.FileService.SaveStylesheet(stylesheet, userId), + name => _fileService.GetStylesheetByName(name), + (stylesheet, userId) => _fileService.SaveStylesheet(stylesheet, userId), name => new Stylesheet(name) ); } @@ -555,7 +559,7 @@ namespace Umbraco.Web.Editors ? relPath + display.Name : relPath.EnsureEndsWith('/') + display.Name; } - + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; var file = getFileByName(relPath); if (file != null) { @@ -565,13 +569,13 @@ namespace Umbraco.Web.Editors file.Content = display.Content; //try/catch? since this doesn't return an Attempt? - saveFile(file, Security.CurrentUser.Id); + saveFile(file, currentUser.Id); } else { file = createFile(relPath); file.Content = display.Content; - saveFile(file, Security.CurrentUser.Id); + saveFile(file, currentUser.Id); } return file; @@ -580,13 +584,13 @@ namespace Umbraco.Web.Editors private Attempt CreateOrUpdatePartialView(CodeFileDisplay display) { return CreateOrUpdatePartialView(display, Core.Constants.SystemDirectories.PartialViews, - Services.FileService.GetPartialView, Services.FileService.SavePartialView, Services.FileService.CreatePartialView); + _fileService.GetPartialView, _fileService.SavePartialView, _fileService.CreatePartialView); } private Attempt CreateOrUpdatePartialViewMacro(CodeFileDisplay display) { return CreateOrUpdatePartialView(display, Core.Constants.SystemDirectories.MacroPartials, - Services.FileService.GetPartialViewMacro, Services.FileService.SavePartialViewMacro, Services.FileService.CreatePartialViewMacro); + _fileService.GetPartialViewMacro, _fileService.SavePartialViewMacro, _fileService.CreatePartialViewMacro); } /// @@ -608,6 +612,8 @@ namespace Umbraco.Web.Editors display.Name = EnsureCorrectFileExtension(display.Name, ".cshtml"); Attempt partialViewResult; + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + var virtualPath = NormalizeVirtualPath(display.VirtualPath, systemDirectory); var view = getView(virtualPath); if (view != null) @@ -617,13 +623,13 @@ namespace Umbraco.Web.Editors view.Path = orgPath + display.Name; view.Content = display.Content; - partialViewResult = saveView(view, Security.CurrentUser.Id); + partialViewResult = saveView(view, currentUser.Id); } else { view = new PartialView(PartialViewType.PartialView, virtualPath + display.Name); view.Content = display.Content; - partialViewResult = createView(view, display.Snippet, Security.CurrentUser.Id); + partialViewResult = createView(view, display.Snippet, currentUser.Id); } return partialViewResult; diff --git a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs new file mode 100644 index 0000000000..3c94e3f9a0 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq; +using System.Text; +using System.Web; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; + +namespace Umbraco.Extensions +{ + public static class UrlHelperExtensions + { + internal static string GetTreePathFromFilePath(this IUrlHelper urlHelper, string virtualPath, string basePath = "") + { + //This reuses the Logic from umbraco.cms.helpers.DeepLink class + //to convert a filepath to a tree syncing path string. + + //removes the basepath from the path + //and normalizes paths - / is used consistently between trees and editors + basePath = basePath.TrimStart("~"); + virtualPath = virtualPath.TrimStart("~"); + virtualPath = virtualPath.Substring(basePath.Length); + virtualPath = virtualPath.Replace('\\', '/'); + + //-1 is the default root id for trees + var sb = new StringBuilder("-1"); + + //split the virtual path and iterate through it + var pathPaths = virtualPath.Split('/'); + + for (var p = 0; p < pathPaths.Length; p++) + { + var path = HttpUtility.UrlEncode(string.Join("/", pathPaths.Take(p + 1))); + if (string.IsNullOrEmpty(path) == false) + { + sb.Append(","); + sb.Append(path); + } + } + return sb.ToString().TrimEnd(","); + } + } +} diff --git a/src/Umbraco.Web.Common/ActionsResults/UmbracoProblemResult.cs b/src/Umbraco.Web.Common/ActionsResults/UmbracoProblemResult.cs new file mode 100644 index 0000000000..235ef0c037 --- /dev/null +++ b/src/Umbraco.Web.Common/ActionsResults/UmbracoProblemResult.cs @@ -0,0 +1,13 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; + +namespace Umbraco.Web.Common.ActionsResults +{ + public class UmbracoProblemResult : ObjectResult + { + public UmbracoProblemResult(string message, HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError) : base(new {Message = message}) + { + StatusCode = (int) httpStatusCode; + } + } +} diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs index d5268a884f..e332c89536 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.Common.ApplicationModels ActionModelConventions = new List() { - new ClientErrorResultFilterConvention(), // TODO: Need to determine exactly how this affects errors + new ClientErrorResultFilterConvention(), // Ensures the responses without any body is converted into a simple json object with info instead of a string like "Status Code: 404; Not Found" new InvalidModelStateFilterConvention(), // automatically 400 responses if ModelState is invalid before hitting the controller new ConsumesConstraintForFormFileParameterConvention(), // If an controller accepts files, it must accept multipart/form-data. new InferParameterBindingInfoConvention(modelMetadataProvider), // no need for [FromBody] everywhere, A complex type parameter is assigned to FromBody @@ -74,9 +74,9 @@ namespace Umbraco.Web.Common.ApplicationModels foreach (var convention in ActionModelConventions) { convention.Apply(action); - } + } } - + } } diff --git a/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs b/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs index 750417fab6..cb14a5a546 100644 --- a/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs +++ b/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs @@ -4,6 +4,7 @@ using System.Net; using System.Runtime.Serialization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Common.Exceptions { @@ -68,5 +69,16 @@ namespace Umbraco.Web.Common.Exceptions } }; } + + public static HttpResponseException CreateNotificationValidationErrorResponse(string errorMessage) + { + var notificationModel = new SimpleNotificationModel + { + Message = errorMessage + }; + notificationModel.AddErrorNotification(errorMessage, string.Empty); + return CreateValidationErrorResponse(notificationModel); + } + } } diff --git a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs index 3221023ca2..f4b516a764 100644 --- a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs @@ -33,34 +33,5 @@ namespace Umbraco.Web.Trees return actionUrl; } - internal static string GetTreePathFromFilePath(this UrlHelper urlHelper, string virtualPath, string basePath = "") - { - //This reuses the Logic from umbraco.cms.helpers.DeepLink class - //to convert a filepath to a tree syncing path string. - - //removes the basepath from the path - //and normalizes paths - / is used consistently between trees and editors - basePath = basePath.TrimStart("~"); - virtualPath = virtualPath.TrimStart("~"); - virtualPath = virtualPath.Substring(basePath.Length); - virtualPath = virtualPath.Replace('\\', '/'); - - //-1 is the default root id for trees - var sb = new StringBuilder("-1"); - - //split the virtual path and iterate through it - var pathPaths = virtualPath.Split('/'); - - for (var p = 0; p < pathPaths.Length; p++) - { - var path = HttpUtility.UrlEncode(string.Join("/", pathPaths.Take(p + 1))); - if (string.IsNullOrEmpty(path) == false) - { - sb.Append(","); - sb.Append(path); - } - } - return sb.ToString().TrimEnd(","); - } } } From c3c98e2621d31dc095f012012ff2103db230f5b8 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 13:04:06 +0100 Subject: [PATCH 24/55] v8: Fix for login screen title display (#7412) --- src/Umbraco.Web.UI.Client/package-lock.json | 6 +++--- .../directives/components/application/umblogin.directive.js | 2 ++ src/Umbraco.Web.UI.Client/src/main.controller.js | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index b8087066c9..12e7b115e7 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -15313,9 +15313,9 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "tinymce": { - "version": "4.9.9", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.9.tgz", - "integrity": "sha512-7Wqh4PGSAWm6FyNwyI1uFAaZyzeQeiwd9Gg2R89SpFIqoMrSzNHIYBqnZnlDm4Bd2DJ0wcC6uJhwFrabIE8puw==" + "version": "4.9.10", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.10.tgz", + "integrity": "sha512-vyzGG04Q44Y7zWIKA4c+G7MxMCsed6JkrhU+k0TaDs9XKAiS+e+D3Fzz5OIJ7p5keF7lbRK5czgI8T1JtouZqw==" }, "tmp": { "version": "0.0.33", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index 541cc647fb..ec8da898ad 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -209,6 +209,7 @@ } if (vm.loginForm.$invalid) { + SetTitle(); return; } @@ -257,6 +258,7 @@ vm.loginForm.password.$setValidity('auth', true); } }); + SetTitle(); } function requestPasswordResetSubmit(email) { diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 81eadf150f..297d93f4bc 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -56,12 +56,14 @@ function MainController($scope, $location, appState, treeService, notificationsS appState.setSearchState("show", false); }; - $scope.showLoginScreen = function(isTimedOut) { + $scope.showLoginScreen = function (isTimedOut) { + $scope.login.pageTitle = $scope.$root.locationTitle; $scope.login.isTimedOut = isTimedOut; $scope.login.show = true; }; - $scope.hideLoginScreen = function() { + $scope.hideLoginScreen = function () { + $scope.$root.locationTitle = $scope.login.pageTitle; $scope.login.show = false; }; From 71a8cb6d3525edc33c05989478a79d12c97f4337 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 26 May 2020 14:42:19 +0200 Subject: [PATCH 25/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Migrated DataTypeController + Fixed injection of services to UmbracoApplicationAuthorizeAttribute --- .../Controllers/CodeFileController.cs | 2 +- .../Controllers}/DataTypeController.cs | 228 +++++++++--------- .../Filters/DataTypeValidateAttribute.cs | 53 ++-- .../UmbracoApplicationAuthorizeAttribute.cs | 11 +- .../Filters/UmbracoTreeAuthorizeAttribute.cs | 71 ++++++ .../HealthCheck/HealthCheckController.cs | 3 +- .../Profiling/WebProfilingController.cs | 6 +- .../Runtime/AspNetCoreComposer.cs | 14 ++ .../Umbraco.Web.UI.NetCore.csproj | 1 + .../Editors/BackOfficeServerVariables.cs | 8 +- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 4 +- .../Filters/UmbracoTreeAuthorizeAttribute.cs | 1 + 13 files changed, 242 insertions(+), 168 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/DataTypeController.cs (63%) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice}/Filters/DataTypeValidateAttribute.cs (60%) create mode 100644 src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index cfb4d94ad4..63a7330c29 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.BackOffice.Controllers // ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/ [PluginController("UmbracoApi")] //[PrefixlessBodyModelValidator] - [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] public class CodeFileController : BackOfficeNotificationsController { private readonly IIOHelper _ioHelper; diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs similarity index 63% rename from src/Umbraco.Web/Editors/DataTypeController.cs rename to src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 19ad546b2d..8cc5d884bd 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -3,28 +3,21 @@ using System.Collections.Generic; using System.Data; using System.Linq; using System.Net; -using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using System.Net.Http; using System.Text; -using Umbraco.Core.Cache; -using Umbraco.Web.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Mapping; -using System.Web.Http.Controllers; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Web.Routing; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; namespace Umbraco.Web.Editors { @@ -37,45 +30,44 @@ namespace Umbraco.Web.Editors /// Content Types, Member Types or Media Types ... and of course to Data Types /// [PluginController("UmbracoApi")] - [UmbracoTreeAuthorize(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)] - [EnableOverrideAuthorization] - [DataTypeControllerConfiguration] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes}})] public class DataTypeController : BackOfficeNotificationsController { private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; private readonly IContentSettings _contentSettings; + private readonly UmbracoMapper _umbracoMapper; + private readonly PropertyEditorCollection _propertyEditorCollection; + private readonly IContentTypeService _contentTypeService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; public DataTypeController( PropertyEditorCollection propertyEditors, - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, - UmbracoMapper umbracoMapper, + IDataTypeService dataTypeService, IContentSettings contentSettings, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) - { - _propertyEditors = propertyEditors; + UmbracoMapper umbracoMapper, + PropertyEditorCollection propertyEditorCollection, + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + ILocalizedTextService localizedTextService, + IUmbracoContextAccessor umbracoContextAccessor) + { + _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); - } + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); + _propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection)); + _contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); + _mediaTypeService = mediaTypeService ?? throw new ArgumentNullException(nameof(mediaTypeService)); + _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + } - /// - /// Configures this controller with a custom action selector - /// - private class DataTypeControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) - )); - } - } /// /// Gets data type by name @@ -84,8 +76,8 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetByName(string name) { - var dataType = Services.DataTypeService.GetDataType(name); - return dataType == null ? null : Mapper.Map(dataType); + var dataType = _dataTypeService.GetDataType(name); + return dataType == null ? null : _umbracoMapper.Map(dataType); } /// @@ -95,12 +87,12 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetById(int id) { - var dataType = Services.DataTypeService.GetDataType(id); + var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dataType); + return _umbracoMapper.Map(dataType); } /// @@ -110,12 +102,12 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetById(Guid id) { - var dataType = Services.DataTypeService.GetDataType(id); + var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dataType); + return _umbracoMapper.Map(dataType); } /// @@ -129,12 +121,12 @@ namespace Umbraco.Web.Editors if (guidUdi == null) throw new HttpResponseException(HttpStatusCode.NotFound); - var dataType = Services.DataTypeService.GetDataType(guidUdi.Guid); + var dataType = _dataTypeService.GetDataType(guidUdi.Guid); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dataType); + return _umbracoMapper.Map(dataType); } /// @@ -144,17 +136,17 @@ namespace Umbraco.Web.Editors /// [HttpDelete] [HttpPost] - public HttpResponseMessage DeleteById(int id) + public IActionResult DeleteById(int id) { - var foundType = Services.DataTypeService.GetDataType(id); + var foundType = _dataTypeService.GetDataType(id); if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + _dataTypeService.Delete(foundType, currentUser.Id); - Services.DataTypeService.Delete(foundType, Security.CurrentUser.Id); - - return Request.CreateResponse(HttpStatusCode.OK); + return Ok(); } public DataTypeDisplay GetEmpty(int parentId) @@ -162,7 +154,7 @@ namespace Umbraco.Web.Editors // cannot create an "empty" data type, so use something by default. var editor = _propertyEditors[Constants.PropertyEditors.Aliases.Label]; var dt = new DataType(editor, parentId); - return Mapper.Map(dt); + return _umbracoMapper.Map(dt); } /// @@ -172,13 +164,13 @@ namespace Umbraco.Web.Editors /// a DataTypeDisplay public DataTypeDisplay GetCustomListView(string contentTypeAlias) { - var dt = Services.DataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + var dt = _dataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); if (dt == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dt); + return _umbracoMapper.Map(dt); } /// @@ -188,17 +180,17 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay PostCreateCustomListView(string contentTypeAlias) { - var dt = Services.DataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + var dt = _dataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); //if it doesn't exist yet, we will create it. if (dt == null) { var editor = _propertyEditors[Constants.PropertyEditors.Aliases.ListView]; dt = new DataType(editor) { Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias }; - Services.DataTypeService.Save(dt); + _dataTypeService.Save(dt); } - return Mapper.Map(dt); + return _umbracoMapper.Map(dt); } /// @@ -218,11 +210,11 @@ namespace Umbraco.Web.Editors if (dataTypeId == -1) { //this is a new data type, so just return the field editors with default values - return Mapper.Map>(propEd); + return _umbracoMapper.Map>(propEd); } //we have a data type associated - var dataType = Services.DataTypeService.GetDataType(dataTypeId); + var dataType = _dataTypeService.GetDataType(dataTypeId); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -234,11 +226,11 @@ namespace Umbraco.Web.Editors if (dataType.EditorAlias == editorAlias) { //this is the currently assigned pre-value editor, return with values. - return Mapper.Map>(dataType); + return _umbracoMapper.Map>(dataType); } //these are new pre-values, so just return the field editors with default values - return Mapper.Map>(propEd); + return _umbracoMapper.Map>(propEd); } /// @@ -248,20 +240,23 @@ namespace Umbraco.Web.Editors /// [HttpDelete] [HttpPost] - public HttpResponseMessage DeleteContainer(int id) + public IActionResult DeleteContainer(int id) { - Services.DataTypeService.DeleteContainer(id, Security.CurrentUser.Id); - return Request.CreateResponse(HttpStatusCode.OK); + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + _dataTypeService.DeleteContainer(id, currentUser.Id); + + return Ok(); } - public HttpResponseMessage PostCreateContainer(int parentId, string name) + public IActionResult PostCreateContainer(int parentId, string name) { - var result = Services.DataTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + var result = _dataTypeService.CreateContainer(parentId, name, currentUser.Id); return result - ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id - : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + ? Ok(result.Result) //return the id + : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } /// @@ -269,8 +264,8 @@ namespace Umbraco.Web.Editors /// /// /// - [DataTypeValidate] - public DataTypeDisplay PostSave(DataTypeSave dataType) + [TypeFilter(typeof(DataTypeValidateAttribute))] + public IActionResult PostSave(DataTypeSave dataType) { //If we've made it here, then everything has been wired up and validated by the attribute @@ -286,21 +281,23 @@ namespace Umbraco.Web.Editors dataType.PersistedDataType.Configuration = configuration; + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; // save the data type try { - Services.DataTypeService.Save(dataType.PersistedDataType, Security.CurrentUser.Id); + + _dataTypeService.Save(dataType.PersistedDataType, currentUser.Id); } catch (DuplicateNameException ex) { ModelState.AddModelError("Name", ex.Message); - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + throw HttpResponseException.CreateValidationErrorResponse(ModelState); } // map back to display model, and return - var display = Mapper.Map(dataType.PersistedDataType); - display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/dataTypeSaved"), ""); - return display; + var display = _umbracoMapper.Map(dataType.PersistedDataType); + display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/dataTypeSaved"), ""); + return Ok(display); } /// @@ -308,46 +305,45 @@ namespace Umbraco.Web.Editors /// /// /// - public HttpResponseMessage PostMove(MoveOrCopy move) + public IActionResult PostMove(MoveOrCopy move) { - var toMove = Services.DataTypeService.GetDataType(move.Id); + var toMove = _dataTypeService.GetDataType(move.Id); if (toMove == null) { - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); } - var result = Services.DataTypeService.Move(toMove, move.ParentId); + var result = _dataTypeService.Move(toMove, move.ParentId); if (result.Success) { - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "text/plain"); - return response; + return Content(toMove.Path,"text/plain", Encoding.UTF8); } switch (result.Result.Result) { case MoveOperationStatusType.FailedParentNotFound: - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); case MoveOperationStatusType.FailedCancelledByEvent: //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); case MoveOperationStatusType.FailedNotAllowedByPath: var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); - return Request.CreateValidationErrorResponse(notificationModel); + notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByPath"), ""); + throw HttpResponseException.CreateValidationErrorResponse(notificationModel); default: throw new ArgumentOutOfRangeException(); } } - public HttpResponseMessage PostRenameContainer(int id, string name) + public IActionResult PostRenameContainer(int id, string name) { - var result = Services.DataTypeService.RenameContainer(id, name, Security.CurrentUser.Id); + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + var result = _dataTypeService.RenameContainer(id, name, currentUser.Id); return result - ? Request.CreateResponse(HttpStatusCode.OK, result.Result) - : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + ? Ok(result.Result) + : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } /// @@ -358,7 +354,7 @@ namespace Umbraco.Web.Editors public DataTypeReferences GetReferences(int id) { var result = new DataTypeReferences(); - var usages = Services.DataTypeService.GetReferences(id); + var usages = _dataTypeService.GetReferences(id); foreach(var groupOfEntityType in usages.GroupBy(x => x.Key.EntityType)) { @@ -366,11 +362,11 @@ namespace Umbraco.Web.Editors var guidsAndPropertyAliases = groupOfEntityType.ToDictionary(i => ((GuidUdi)i.Key).Guid, i => i.Value); if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.DocumentType)) - result.DocumentTypes = GetContentTypeUsages(Services.ContentTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); + result.DocumentTypes = GetContentTypeUsages(_contentTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); else if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.MediaType)) - result.MediaTypes = GetContentTypeUsages(Services.MediaTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); + result.MediaTypes = GetContentTypeUsages(_mediaTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); else if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.MemberType)) - result.MemberTypes = GetContentTypeUsages(Services.MemberTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); + result.MemberTypes = GetContentTypeUsages(_memberTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); } return result; @@ -412,14 +408,13 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoApplicationAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] public IEnumerable GetAll() { - return Services.DataTypeService + return _dataTypeService .GetAll() - .Select(Mapper.Map).Where(x => x.IsSystemDataType == false); + .Select(_umbracoMapper.Map).Where(x => x.IsSystemDataType == false); } /// @@ -429,17 +424,16 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoTreeAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] public IDictionary> GetGroupedDataTypes() { - var dataTypes = Services.DataTypeService + var dataTypes = _dataTypeService .GetAll() - .Select(Mapper.Map) + .Select(_umbracoMapper.Map) .ToArray(); - var propertyEditors = Current.PropertyEditors.ToArray(); + var propertyEditors =_propertyEditorCollection.ToArray(); foreach (var dataType in dataTypes) { @@ -462,20 +456,20 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoTreeAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] + public IDictionary> GetGroupedPropertyEditors() { var datatypes = new List(); var showDeprecatedPropertyEditors = _contentSettings.ShowDeprecatedPropertyEditors; - var propertyEditors = Current.PropertyEditors + var propertyEditors =_propertyEditorCollection .Where(x=>x.IsDeprecated == false || showDeprecatedPropertyEditors); foreach (var propertyEditor in propertyEditors) { var hasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); - var basic = Mapper.Map(propertyEditor); + var basic = _umbracoMapper.Map(propertyEditor); basic.HasPrevalues = hasPrevalues; datatypes.Add(basic); } @@ -495,14 +489,14 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoTreeAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] + public IEnumerable GetAllPropertyEditors() { - return Current.PropertyEditors + return _propertyEditorCollection .OrderBy(x => x.Name) - .Select(Mapper.Map); + .Select(_umbracoMapper.Map); } #endregion } diff --git a/src/Umbraco.Web/Editors/Filters/DataTypeValidateAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs similarity index 60% rename from src/Umbraco.Web/Editors/Filters/DataTypeValidateAttribute.cs rename to src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs index efdcf93fff..efd45f3a6e 100644 --- a/src/Umbraco.Web/Editors/Filters/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs @@ -1,16 +1,15 @@ using System; using System.Linq; using System.Net; -using System.Net.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; +using Microsoft.AspNetCore.Mvc.Filters; using Umbraco.Core; -using Umbraco.Web.Composing; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Common.ActionsResults; +using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.WebApi; namespace Umbraco.Web.Editors { @@ -19,38 +18,36 @@ namespace Umbraco.Web.Editors /// internal sealed class DataTypeValidateAttribute : ActionFilterAttribute { - public IDataTypeService DataTypeService { get; } + private readonly IDataTypeService _dataTypeService; + private readonly PropertyEditorCollection _propertyEditorCollection; + private readonly UmbracoMapper _umbracoMapper; - public PropertyEditorCollection PropertyEditors { get; } - - public DataTypeValidateAttribute() - : this(Current.Factory.GetInstance(), Current.Factory.GetInstance()) - { - } /// /// For use in unit tests. Not possible to use as attribute ctor. /// /// - /// - public DataTypeValidateAttribute(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) + /// + /// + public DataTypeValidateAttribute(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection, UmbracoMapper umbracoMapper) { - DataTypeService = dataTypeService; - PropertyEditors = propertyEditors; + _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); + _propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection)); + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); } - public override void OnActionExecuting(HttpActionContext actionContext) + public override void OnActionExecuting(ActionExecutingContext context) { - var dataType = (DataTypeSave) actionContext.ActionArguments["dataType"]; + var dataType = (DataTypeSave) context.ActionArguments["dataType"]; dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')', ':'); dataType.Alias = dataType.Alias == null ? dataType.Name : dataType.Alias.CleanForXss('[', ']', '(', ')', ':'); // get the property editor, ensuring that it exits - if (!PropertyEditors.TryGet(dataType.EditorAlias, out var propertyEditor)) + if (!_propertyEditorCollection.TryGet(dataType.EditorAlias, out var propertyEditor)) { var message = $"Property editor \"{dataType.EditorAlias}\" was not found."; - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound); return; } @@ -62,25 +59,25 @@ namespace Umbraco.Web.Editors switch (dataType.Action) { case ContentSaveAction.Save: - persisted = DataTypeService.GetDataType(Convert.ToInt32(dataType.Id)); + persisted = _dataTypeService.GetDataType(Convert.ToInt32(dataType.Id)); if (persisted == null) { var message = $"Data type with id {dataType.Id} was not found."; - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound); return; } // map the model to the persisted instance - Current.Mapper.Map(dataType, persisted); + _umbracoMapper.Map(dataType, persisted); break; case ContentSaveAction.SaveNew: // create the persisted model from mapping the saved model - persisted = Current.Mapper.Map(dataType); + persisted = _umbracoMapper.Map(dataType); ((DataType) persisted).ResetIdentity(); break; default: - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new ArgumentOutOfRangeException()); + context.Result = new UmbracoProblemResult($"Data type action {dataType.Action} was not found.", HttpStatusCode.NotFound); return; } @@ -98,13 +95,13 @@ namespace Umbraco.Web.Editors // run each IValueValidator (with null valueType and dataTypeConfiguration: not relevant here) foreach (var validator in editorField.Validators) foreach (var result in validator.Validate(field.Value, null, null)) - actionContext.ModelState.AddValidationError(result, "Properties", field.Key); + context.ModelState.AddModelError(field.Key,result.ErrorMessage); } - if (actionContext.ModelState.IsValid == false) + if (context.ModelState.IsValid == false) { // if it is not valid, do not continue and return the model state - actionContext.Response = actionContext.Request.CreateValidationErrorResponse(actionContext.ModelState); + throw HttpResponseException.CreateValidationErrorResponse(context.ModelState); } } } diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs index 88a4c4f8ff..2a9f88c0be 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.BackOffice.Filters /// internal static bool Enable = true; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly string[] _appNames; /// @@ -22,29 +23,29 @@ namespace Umbraco.Web.BackOffice.Filters /// /// If the user has access to any of the specified apps, they will be authorized. /// - public UmbracoApplicationAuthorizeAttribute(params string[] appName) + public UmbracoApplicationAuthorizeAttribute(IUmbracoContextAccessor umbracoContextAccessor, params string[] appName) { + _umbracoContextAccessor = umbracoContextAccessor; _appNames = appName; } public void OnAuthorization(AuthorizationFilterContext context) { - var umbracoContextAccessor = context.HttpContext.RequestServices.GetRequiredService(); - if (!IsAuthorized(umbracoContextAccessor)) + if (!IsAuthorized()) { context.Result = new ForbidResult(); } } - private bool IsAuthorized(IUmbracoContextAccessor umbracoContextAccessor) + private bool IsAuthorized() { if (Enable == false) { return true; } - var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); var authorized = umbracoContext.Security.CurrentUser != null && _appNames.Any(app => umbracoContext.Security.UserHasSectionAccess( app, umbracoContext.Security.CurrentUser)); diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs new file mode 100644 index 0000000000..9a8239b637 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -0,0 +1,71 @@ +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core; +using Umbraco.Web.Services; + +namespace Umbraco.Web.BackOffice.Filters +{ + /// + /// Ensures that the current user has access to the application for which the specified tree(s) belongs + /// + /// + /// This would allow a tree to be moved between sections + /// + public sealed class UmbracoTreeAuthorizeAttribute : IAuthorizationFilter + { + /// + /// Can be used by unit tests to enable/disable this filter + /// + internal static bool Enable = true; + + private readonly ITreeService _treeService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly string[] _treeAliases; + + /// + /// Constructor to set authorization to be based on a tree alias for which application security will be applied + /// + /// + /// + /// If the user has access to the application that the treeAlias is specified in, they will be authorized. + /// Multiple trees may be specified. + /// + /// + public UmbracoTreeAuthorizeAttribute(ITreeService treeService, IUmbracoContextAccessor umbracoContextAccessor, params string[] treeAliases) + { + _treeService = treeService; + _umbracoContextAccessor = umbracoContextAccessor; + _treeAliases = treeAliases; + } + + private bool IsAuthorized() + { + if (Enable == false) + { + return true; + } + + var apps = _treeAliases.Select(x => _treeService + .GetByAlias(x)) + .WhereNotNull() + .Select(x => x.SectionAlias) + .Distinct() + .ToArray(); + + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return umbracoContext.Security.CurrentUser != null + && apps.Any(app => umbracoContext.Security.UserHasSectionAccess( + app, umbracoContext.Security.CurrentUser)); + } + + public void OnAuthorization(AuthorizationFilterContext context) + { + if (!IsAuthorized()) + { + + context.Result = new ForbidResult(); + } + } + } +} diff --git a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs index db2b72f989..00de2db0b6 100644 --- a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Web.Editors; using Umbraco.Core.Configuration.HealthChecks; @@ -12,7 +13,7 @@ namespace Umbraco.Web.HealthCheck /// /// The API controller used to display the health check info and execute any actions /// - [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] public class HealthCheckController : UmbracoAuthorizedJsonController { private readonly HealthCheckCollection _checks; diff --git a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs index a053c28d42..ba55dcb51c 100644 --- a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs +++ b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Hosting; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Editors; @@ -8,7 +10,7 @@ namespace Umbraco.Web.BackOffice.Profiling /// /// The API controller used to display the state of the web profiler /// - [UmbracoApplicationAuthorizeAttribute(Core.Constants.Applications.Settings)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] public class WebProfilingController : UmbracoAuthorizedJsonController { private readonly IHostingEnvironment _hosting; diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index babf6cb80d..5ea29cef1d 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -21,6 +21,8 @@ using Umbraco.Web.Common.Controllers; using System; using Umbraco.Web.Common.Middleware; using Umbraco.Web.Common.ModelBinding; +using Umbraco.Web.Search; +using Umbraco.Web.Trees; namespace Umbraco.Web.Common.Runtime { @@ -79,6 +81,14 @@ namespace Umbraco.Web.Common.Runtime composition.WithCollectionBuilder() .Add(umbracoApiControllerTypes); + // register back office trees + // the collection builder only accepts types inheriting from TreeControllerBase + // and will filter out those that are not attributed with TreeAttribute + // composition.Trees() + // .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); + composition.RegisterUnique(); //TODO replace with collection builder above + + composition.RegisterUnique(); composition.RegisterUnique(); @@ -86,6 +96,10 @@ namespace Umbraco.Web.Common.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + + + + } } } diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index 483e38780e..8f7b78b128 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -60,6 +60,7 @@ Designer + diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index f79865c881..a509ecfd29 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -208,10 +208,10 @@ namespace Umbraco.Web.Editors "entityApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(0, UmbracoEntityTypes.Media)) }, - { - "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, + // { + // "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetById(0)) + // }, //TODO Reintroduce // { // "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index a427669ed9..7f437c9378 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -70,7 +70,7 @@ namespace Umbraco.Web.Runtime //we need to eagerly scan controller types since they will need to be routed composition.WithCollectionBuilder() .Add(composition.TypeLoader.GetSurfaceControllers()); - + // add all known factories, devs can then modify this list on application // startup either by binding to events or in their own global.asax @@ -83,13 +83,7 @@ namespace Umbraco.Web.Runtime // register preview SignalR hub composition.RegisterUnique(_ => GlobalHost.ConnectionManager.GetHubContext()); - var umbracoApiControllerTypes = composition.TypeLoader.GetUmbracoApiControllers().ToList(); - // register back office trees - // the collection builder only accepts types inheriting from TreeControllerBase - // and will filter out those that are not attributed with TreeAttribute - composition.Trees() - .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f8e73bb361..6687660dc6 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -152,11 +152,11 @@ + - @@ -339,7 +339,6 @@ - @@ -389,7 +388,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs index 1bea963ee1..07829648c3 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -5,6 +5,7 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Web.Composing; +//MOVED to netcore namespace Umbraco.Web.WebApi.Filters { /// From 5541d130207b8a32dfb361bc4d7143c85143c645 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 13:59:33 +0100 Subject: [PATCH 26/55] V8: Accessibility Changes For umbEditorHeader Directive (edit user) (#7102) --- .../editor/umbeditorheader.directive.js | 140 ++++++++++-------- .../src/views/users/user.controller.js | 1 + 2 files changed, 78 insertions(+), 63 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 87053c083c..58f799e5af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -206,7 +206,7 @@ Use this directive to construct a header inside the main editor window. (function () { 'use strict'; - function EditorHeaderDirective(editorService, localizationService, editorState) { + function EditorHeaderDirective(editorService, localizationService, editorState, $rootScope) { function link(scope, $injector) { @@ -224,27 +224,9 @@ Use this directive to construct a header inside the main editor window. if (editorState.current) { //to do make work for user create/edit // to do make it work for user group create/ edit - // to do make it work for language edit/create - // to do make it work for log viewer - scope.isNew = editorState.current.id === 0 || - editorState.current.id === "0" || - editorState.current.id === -1 || - editorState.current.id === 0 || - editorState.current.id === "-1"; - - var localizeVars = [ - scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit", - "visuallyHiddenTexts_name", - scope.isNew ? "general_new" : "general_edit" - ]; - - if (scope.editorfor) { - localizeVars.push(scope.editorfor); - } - localizationService.localizeMany(localizeVars).then(function(data) { - setAccessibilityForEditor(data); - scope.loading = false; - }); + // to make it work for language edit/create + setAccessibilityForEditorState(); + scope.loading = false; } else { scope.loading = false; } @@ -283,59 +265,91 @@ Use this directive to construct a header inside the main editor window. editorService.iconPicker(iconPicker); }; - function setAccessibilityForEditor(data) { - - if (editorState.current) { - if (scope.nameLocked) { - scope.accessibility.a11yName = scope.name; - SetPageTitle(scope.name); - } else { - - scope.accessibility.a11yMessage = data[0]; - scope.accessibility.a11yName = data[1]; - var title = data[2] + ":"; - if (!scope.isNew) { - scope.accessibility.a11yMessage += " " + scope.name; - title += " " + scope.name; - } else { - var name = ""; - if (editorState.current.contentTypeName) { - name = editorState.current.contentTypeName; - } else if (scope.editorfor) { - name = data[3]; - } - if (name !== "") { - scope.accessibility.a11yMessage += " " + name; - scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName; - title += " " + name; - } - } - if (title !== data[2] + ":") { - SetPageTitle(title); - } - - } - scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage); - scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName); + function setAccessibilityForEditorState() { + var isNew = editorState.current.id === 0 || + editorState.current.id === "0" || + editorState.current.id === -1 || + editorState.current.id === 0 || + editorState.current.id === "-1"; + + var contentTypeName = ""; + if (editorState.current.contentTypeName) { + contentTypeName = editorState.current.contentTypeName; } - + + var setTitle = false; + if (scope.setpagetitle !== undefined) { + setTitle = scope.setpagetitle; + } + setAccessibilityHeaderDirective(isNew, scope.editorfor, scope.nameLocked, scope.name, contentTypeName, setTitle); } + function setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, entityName, contentTypeName, setTitle) { + + var localizeVars = [ + isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit", + "visuallyHiddenTexts_name", + isNew ? "general_new" : "general_edit" + ]; + + if (editorFor) { + localizeVars.push(editorFor); + } + localizationService.localizeMany(localizeVars).then(function(data) { + if (nameLocked) { + scope.accessibility.a11yName = entityName; + if (setTitle) { + SetPageTitle(entityName); + } + } else { + + scope.accessibility.a11yMessage = data[0]; + scope.accessibility.a11yName = data[1]; + var title = data[2] + ":"; + if (!isNew) { + scope.accessibility.a11yMessage += " " + entityName; + title += " " + entityName; + } else { + var name = ""; + if (contentTypeName) { + name = editorState.current.contentTypeName; + } else if (editorFor) { + name = data[3]; + } + if (name !== "") { + scope.accessibility.a11yMessage += " " + name; + scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName; + title += " " + name; + } + } + if (setTitle && title !== data[2] + ":") { + SetPageTitle(title); + } + + } + scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage); + scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName); + + }); + } + + + function isEmptyOrSpaces(str) { return str === null || str===undefined || str.trim ===''; } function SetPageTitle(title) { - var setTitle = false; - if (scope.setpagetitle !== undefined) { - setTitle = scope.setpagetitle; - } - if (setTitle) { scope.$emit("$changeTitle", title); - } } + + $rootScope.$on('$setAccessibleHeader', function (event, isNew, editorFor, nameLocked, name, contentTypeName, setTitle) { + setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, name, contentTypeName, setTitle); + }); } + + var directive = { transclude: true, restrict: 'E', diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index ecea3b1dba..19218d3d08 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -103,6 +103,7 @@ vm.changePasswordModel.config.allowManuallyChangingPassword = true; } + $scope.$emit("$setAccessibleHeader", false, "general_user", false, vm.user.name, "", true); vm.loading = false; }); }); From b80dfbf5fce0f03f299c105f2ea167a5c55aa7b0 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 14:01:50 +0100 Subject: [PATCH 27/55] Fix for tiny MCE when language is en-US (#8002) --- .../lib/tinymce/langs/en_US.js | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js new file mode 100644 index 0000000000..90eae85800 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js @@ -0,0 +1,261 @@ +tinymce.addI18n('en_US',{ +"Redo": "Redo", +"Undo": "Undo", +"Cut": "Cut", +"Copy": "Copy", +"Paste": "Paste", +"Select all": "Select all", +"New document": "New document", +"Ok": "Ok", +"Cancel": "Cancel", +"Visual aids": "Visual aids", +"Bold": "Bold", +"Italic": "Italic", +"Underline": "Underline", +"Strikethrough": "Strikethrough", +"Superscript": "Superscript", +"Subscript": "Subscript", +"Clear formatting": "Clear formatting", +"Align left": "Align left", +"Align center": "Align center", +"Align right": "Align right", +"Justify": "Justify", +"Bullet list": "Bullet list", +"Numbered list": "Numbered list", +"Decrease indent": "Decrease indent", +"Increase indent": "Increase indent", +"Close": "Close", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.", +"Headers": "Headers", +"Header 1": "Header 1", +"Header 2": "Header 2", +"Header 3": "Header 3", +"Header 4": "Header 4", +"Header 5": "Header 5", +"Header 6": "Header 6", +"Headings": "Headings", +"Heading 1": "Heading 1", +"Heading 2": "Heading 2", +"Heading 3": "Heading 3", +"Heading 4": "Heading 4", +"Heading 5": "Heading 5", +"Heading 6": "Heading 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraph", +"Blockquote": "Blockquote", +"Inline": "Inline", +"Blocks": "Blocks", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", +"Font Family": "Font Family", +"Font Sizes": "Font Sizes", +"Class": "Class", +"Browse for an image": "Browse for an image", +"OR": "OR", +"Drop an image here": "Drop an image here", +"Upload": "Upload", +"Block": "Blocks", +"Align": "Align", +"Default": "Default", +"Circle": "Circle", +"Disc": "Disc", +"Square": "Square", +"Lower Alpha": "Lower Alpha", +"Lower Greek": "Lower Greek", +"Lower Roman": "Lower Roman", +"Upper Alpha": "Upper Alpha", +"Upper Roman": "Upper Roman", +"Anchor": "Anchor", +"Name": "Name", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons, or underscores.", +"You have unsaved changes are you sure you want to navigate away?": "You have unsaved changes are you sure you want to navigate away?", +"Restore last draft": "Restore last draft", +"Special character": "Special character", +"Source code": "Source code", +"Insert\/Edit code sample": "Insert\/Edit code sample", +"Language": "Language", +"Code sample": "Code sample", +"Color": "color", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Left to right", +"Right to left": "Right to left", +"Emoticons": "Emoticons", +"Document properties": "Document properties", +"Title": "Title", +"Keywords": "Keywords", +"Description": "Description", +"Robots": "Robots", +"Author": "Author", +"Encoding": "Encoding", +"Fullscreen": "Fullscreen", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Handy Shortcuts", +"Horizontal line": "Horizontal line", +"Insert\/edit image": "Insert\/edit image", +"Image description": "Image description", +"Source": "Source", +"Dimensions": "Dimensions", +"Constrain proportions": "Constrain proportions", +"General": "General", +"Advanced": "Advanced", +"Style": "Style", +"Vertical space": "Vertical space", +"Horizontal space": "Horizontal space", +"Border": "Border", +"Insert image": "Insert image", +"Image": "Image", +"Image list": "Image list", +"Rotate counterclockwise": "Rotate counterclockwise", +"Rotate clockwise": "Rotate clockwise", +"Flip vertically": "Flip vertically", +"Flip horizontally": "Flip horizontally", +"Edit image": "Edit image", +"Image options": "Image options", +"Zoom in": "Zoom in", +"Zoom out": "Zoom out", +"Crop": "Crop", +"Resize": "Resize", +"Orientation": "Orientation", +"Brightness": "Brightness", +"Sharpen": "Sharpen", +"Contrast": "Contrast", +"Color levels": "color levels", +"Gamma": "Gamma", +"Invert": "Invert", +"Apply": "Apply", +"Back": "Back", +"Insert date\/time": "Insert date\/time", +"Date\/time": "Date\/time", +"Insert link": "Insert link", +"Insert\/edit link": "Insert\/edit link", +"Text to display": "Text to display", +"Url": "Url", +"Target": "Target", +"None": "None", +"New window": "New window", +"Remove link": "Remove link", +"Anchors": "Anchors", +"Link": "Link", +"Paste or type a link": "Paste or type a link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?", +"Link list": "Link list", +"Insert video": "Insert video", +"Insert\/edit video": "Insert\/edit video", +"Insert\/edit media": "Insert\/edit media", +"Alternative source": "Alternative source", +"Poster": "Poster", +"Paste your embed code below:": "Paste your embed code below:", +"Embed": "Embed", +"Media": "Media", +"Nonbreaking space": "Nonbreaking space", +"Page break": "Page break", +"Paste as text": "Paste as text", +"Preview": "Preview", +"Print": "Print", +"Save": "Save", +"Find": "Find", +"Replace with": "Replace with", +"Replace": "Replace", +"Replace all": "Replace all", +"Prev": "Prev", +"Next": "Next", +"Find and replace": "Find and replace", +"Could not find the specified string.": "Could not find the specified string.", +"Match case": "Match case", +"Whole words": "Whole words", +"Spellcheck": "Spellcheck", +"Ignore": "Ignore", +"Ignore all": "Ignore all", +"Finish": "Finish", +"Add to Dictionary": "Add to Dictionary", +"Insert table": "Insert table", +"Table properties": "Table properties", +"Delete table": "Delete table", +"Cell": "Cell", +"Row": "Row", +"Column": "Column", +"Cell properties": "Cell properties", +"Merge cells": "Merge cells", +"Split cell": "Split cell", +"Insert row before": "Insert row before", +"Insert row after": "Insert row after", +"Delete row": "Delete row", +"Row properties": "Row properties", +"Cut row": "Cut row", +"Copy row": "Copy row", +"Paste row before": "Paste row before", +"Paste row after": "Paste row after", +"Insert column before": "Insert column before", +"Insert column after": "Insert column after", +"Delete column": "Delete column", +"Cols": "Cols", +"Rows": "Rows", +"Width": "Width", +"Height": "Height", +"Cell spacing": "Cell spacing", +"Cell padding": "Cell padding", +"Caption": "Caption", +"Left": "Left", +"Center": "Center", +"Right": "Right", +"Cell type": "Cell type", +"Scope": "Scope", +"Alignment": "Alignment", +"H Align": "H Align", +"V Align": "V Align", +"Top": "Top", +"Middle": "Middle", +"Bottom": "Bottom", +"Header cell": "Header cell", +"Row group": "Row group", +"Column group": "Column group", +"Row type": "Row type", +"Header": "Header", +"Body": "Body", +"Footer": "Footer", +"Border color": "Border color", +"Insert template": "Insert template", +"Templates": "Templates", +"Template": "Template", +"Text color": "Text color", +"Background color": "Background color", +"Custom...": "Custom...", +"Custom color": "Custom color", +"No color": "No color", +"Table of Contents": "Table of Contents", +"Show blocks": "Show blocks", +"Show invisible characters": "Show invisible characters", +"Words: {0}": "Words: {0}", +"{0} words": "{0} words", +"File": "File", +"Edit": "Edit", +"Insert": "Insert", +"View": "View", +"Format": "Format", +"Table": "Table", +"Tools": "Tools", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" +}); \ No newline at end of file From e2604103a32ec01ffd722a3b51ceb23a855a2f83 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 14:10:16 +0100 Subject: [PATCH 28/55] v8: Accessibility Create User Page Title (#7104) --- .../src/views/users/views/users/users.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 102efae702..2217628872 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -814,6 +814,7 @@ vm.newUser.message = ""; // clear button state vm.page.createButtonState = "init"; + $scope.$emit("$setAccessibleHeader", true, "general_user", false, "", "", true); } init(); From 6da69f1e429e4666592a81c615e9e95aa5ad092e Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 14:34:30 +0100 Subject: [PATCH 29/55] V8: Accessibility changes for login screen (#5800) --- .../application/umblogin.directive.js | 124 +++++++++--------- .../src/less/pages/login.less | 5 +- .../components/application/umb-login.html | 6 +- 3 files changed, 69 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index ec8da898ad..6dd740e08b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -63,10 +63,14 @@ vm.labels = {}; localizationService.localizeMany([ vm.usernameIsEmail ? "general_email" : "general_username", - vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint"] + vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint", + vm.usernameIsEmail ? "placeholders_emptyEmail" : "placeholders_emptyUsername", + "placeholders_emptyPassword"] ).then(function (data) { vm.labels.usernameLabel = data[0]; vm.labels.usernamePlaceholder = data[1]; + vm.labels.usernameError = data[2]; + vm.labels.passwordError = data[3]; }); vm.twoFactor = {}; @@ -193,72 +197,70 @@ } function loginSubmit() { - - // make sure that we are returning to the login view. - vm.view = "login"; - - // TODO: Do validation properly like in the invite password update + + if (formHelper.submitForm({ scope: $scope })) { + //if the login and password are not empty we need to automatically + // validate them - this is because if there are validation errors on the server + // then the user has to change both username & password to resubmit which isn't ideal, + // so if they're not empty, we'll just make sure to set them to valid. + if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); + } + + if (vm.loginForm.$invalid) { + SetTitle(); + return; + } + + // make sure that we are returning to the login view. + vm.view = "login"; - //if the login and password are not empty we need to automatically - // validate them - this is because if there are validation errors on the server - // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty, we'll just make sure to set them to valid. - if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } + vm.loginStates.submitButton = "busy"; - if (vm.loginForm.$invalid) { - SetTitle(); - return; - } + userService.authenticate(vm.login, vm.password) + .then(function(data) { + vm.loginStates.submitButton = "success"; + userService._retryRequestQueue(true); + if (vm.onLogin) { + vm.onLogin(); + } + }, + function(reason) { - vm.loginStates.submitButton = "busy"; + //is Two Factor required? + if (reason.status === 402) { + vm.errorMsg = "Additional authentication required"; + show2FALoginDialog(reason.data.twoFactorView); + } else { + vm.loginStates.submitButton = "error"; + vm.errorMsg = reason.errorMsg; - userService.authenticate(vm.login, vm.password) - .then(function (data) { - vm.loginStates.submitButton = "success"; - userService._retryRequestQueue(true); - if(vm.onLogin) { - vm.onLogin(); + //set the form inputs to invalid + vm.loginForm.username.$setValidity("auth", false); + vm.loginForm.password.$setValidity("auth", false); + } + + userService._retryRequestQueue(); + + }); + + //setup a watch for both of the model values changing, if they change + // while the form is invalid, then revalidate them so that the form can + // be submitted again. + vm.loginForm.username.$viewChangeListeners.push(function() { + if (vm.loginForm.$invalid) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); } - }, - function (reason) { - - //is Two Factor required? - if (reason.status === 402) { - vm.errorMsg = "Additional authentication required"; - show2FALoginDialog(reason.data.twoFactorView); - } - else { - vm.loginStates.submitButton = "error"; - vm.errorMsg = reason.errorMsg; - - //set the form inputs to invalid - vm.loginForm.username.$setValidity("auth", false); - vm.loginForm.password.$setValidity("auth", false); - } - - userService._retryRequestQueue(); - }); - - //setup a watch for both of the model values changing, if they change - // while the form is invalid, then revalidate them so that the form can - // be submitted again. - vm.loginForm.username.$viewChangeListeners.push(function () { - if (vm.loginForm.$invalid) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } - }); - vm.loginForm.password.$viewChangeListeners.push(function () { - if (vm.loginForm.$invalid) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } - }); - SetTitle(); + vm.loginForm.password.$viewChangeListeners.push(function() { + if (vm.loginForm.$invalid) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); + } + }); + } } function requestPasswordResetSubmit(email) { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index e36acdc273..818b1d84d1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -123,6 +123,7 @@ position: relative; text-align: right; user-select: none; + margin-left: auto; a { opacity: .5; @@ -134,8 +135,8 @@ .password-text { background-repeat: no-repeat; background-size: 18px; - background-position: left center; - padding-left: 26px; + background-position: 0px 1px; + padding-left: 24px; &.show { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E"); 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 2e81395643..098d69960d 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 @@ -149,16 +149,16 @@
- +
- +
- +
Show password From 32a0c3b075f8230dd00142aa7ebc161a67faffaf Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 17:45:39 +0200 Subject: [PATCH 30/55] "Actions" drop down options do not give context to the user (#6301) --- .../views/components/editor/umb-editor-menu.html | 9 ++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 10 +++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 10 +++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 14 +++++++++++++- src/Umbraco.Web/Models/Trees/MenuItem.cs | 9 ++++++++- src/Umbraco.Web/Models/Trees/MenuItemList.cs | 11 ++++++++--- src/Umbraco.Web/Trees/ContentTreeController.cs | 2 +- 7 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html index fe90fef07a..ceae7c3bb9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html @@ -15,7 +15,14 @@ diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 7cead86114..a876b1a6b7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -247,7 +247,7 @@ Dette dokument er udgivet, men dets URL kan ikke dirigeres Udgiv Udgivet - Udgivet (Afventende ændringer) + 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 de valgte sprog og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]> @@ -426,6 +426,7 @@ Internt link: Ved lokalt link, indsæt da en "#" foran linket Åben i nyt vindue? + Makroindstillinger Denne makro har ingen egenskaber du kan redigere Indsæt tekst Rediger rettigheder for @@ -663,6 +664,7 @@ Ikon Id Importer + Inkludér undermapper i søgning Søg kun i denne mappe Info Indre margen @@ -1749,6 +1751,12 @@ Mange hilsner fra Umbraco robotten Åben backoffice søgning Åben/Luk backoffice hjælp Åben/Luk dine profil indstillinger + 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% Aktivt sprog Skift sprog til Opret ny mappe diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index e764b5591e..e4f9ed7b91 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -141,6 +141,7 @@ Save and send for approval Save list view Schedule + Preview Save and preview Preview is disabled because there's no template assigned Choose style @@ -686,6 +687,7 @@ Icon Id Import + Include subfolders in search Search only this folder Info Inner margin @@ -2228,6 +2230,12 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Setup Culture and Hostnames for %0% + Create new node under %0% + Setup Public access on %0% + Setup Permissions on %0% + Change sort order for %0% + Create Content Template based on %0% Open context menu for Current language Switch language to @@ -2243,7 +2251,7 @@ To manage your website, simply open the Umbraco back office and start adding con Create Edit Name - Add new row + Add new row View more options diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index e1b1df2c34..5fc037d8b3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -140,6 +140,7 @@ Send for approval Save list view Schedule + Preview Save and preview Preview is disabled because there's no template assigned Choose style @@ -369,7 +370,8 @@ Document Types within the Settings section, by changing the Allow as root option under Permissions.]]> Media Types Types within the Settings section, by editing the Allowed child node types under Permissions.]]> The selected media in the tree doesn't allow for any other media to be created below it. - Edit permissions for this media type Document Type without a template + Edit permissions for this media type + Document Type without a template New folder New data type New JavaScript file @@ -570,6 +572,9 @@ #value or ?key=value Enter alias... Generating alias... + Create item + Edit + Name Create custom list view @@ -690,6 +695,7 @@ Icon Id Import + Include subfolders in search Search only this folder Info Inner margin @@ -2244,6 +2250,12 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Setup Culture and Hostnames for %0% + Create new node under %0% + Setup Public access on %0% + Setup Permissions on %0% + Change sort order for %0% + Create Content Template based on %0% Open context menu for Current language Switch language to diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index 9d4c76eea1..094c6b24ff 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Actions; +using System.Threading; namespace Umbraco.Web.Models.Trees { @@ -28,12 +29,15 @@ namespace Umbraco.Web.Models.Trees Name = name; } - public MenuItem(string alias, ILocalizedTextService textService) : this() { + var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); + values.TryGetValue($"visuallyHiddenTexts/{alias}_description", out var textDescription); + Alias = alias; Name = textService.Localize($"actions/{Alias}"); + TextDescription = textDescription; } /// @@ -74,6 +78,9 @@ namespace Umbraco.Web.Models.Trees [Required] public string Alias { get; set; } + [DataMember(Name = "textDescription")] + public string TextDescription { get; set; } + /// /// Ensures a menu separator will exist before this menu item /// diff --git a/src/Umbraco.Web/Models/Trees/MenuItemList.cs b/src/Umbraco.Web/Models/Trees/MenuItemList.cs index 1df486ebdf..4aaf0632ab 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemList.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemList.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Actions; @@ -75,7 +76,7 @@ namespace Umbraco.Web.Models.Trees } return null; } - + internal MenuItem CreateMenuItem(string name, bool hasSeparator = false, bool opensDialog = false) where T : IAction { @@ -96,14 +97,18 @@ namespace Umbraco.Web.Models.Trees var item = Current.Actions.GetAction(); if (item == null) return null; + var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); + values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription); + var menuItem = new MenuItem(item, textService.Localize($"actions/{item.Alias}")) { SeparatorBefore = hasSeparator, - OpensDialog = opensDialog + OpensDialog = opensDialog, + TextDescription = textDescription, }; return menuItem; } - + } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 85ff30487d..663af43643 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -323,7 +323,7 @@ namespace Umbraco.Web.Trees private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool opensDialog = false) where TAction : IAction { - var menuItem = menu.Items.Add(Services.TextService.Localize("actions", _actions.GetAction().Alias), hasSeparator, opensDialog); + var menuItem = menu.Items.Add(Services.TextService, hasSeparator, opensDialog); } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) From cc04a1c8fe9d7d3c639d744a618bfd9cd631a41a Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 18:01:17 +0200 Subject: [PATCH 31/55] Content templates dashboard: Add translations and move styles external (#6888) --- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../less/dashboards/content-templates.less | 22 +++++++++++ .../src/views/contentblueprints/intro.html | 37 ++++++++++++------- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 17 +++++++++ .../Umbraco/config/lang/en_us.xml | 17 +++++++++ 5 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 174f9f41d7..f0d7c6f1e1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -227,6 +227,7 @@ @import "dashboards/umbraco-forms.less"; @import "dashboards/examine-management.less"; @import "dashboards/healthcheck.less"; +@import "dashboards/content-templates.less"; @import "dashboards/nucache.less"; @import "typeahead.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less new file mode 100644 index 0000000000..9966fc97e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less @@ -0,0 +1,22 @@ +.content-templates-dashboard{ + p{ + line-height: 1.6em; + margin-bottom: 30px; + + &:last-child{ + margin-bottom: 0; + } + } + + ul{ + margin-bottom: 15px; + } + + li{ + margin-bottom: 5px; + + &:last-child{ + margin-bottom: 0; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html index ce423225f6..66695ace91 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html @@ -12,21 +12,32 @@ - -

What are Content Templates?

-

Content Templates are pre-defined content that can be selected when creating a new content node.

+ +

+ What are Content Templates? +

+

+ Content Templates are pre-defined content that can be selected when creating a new content node. +

-

How do I create a Content Template?

-

There are two ways to create a Content Template:

-
    -
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • -
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • -
-

Once given a name, editors can start using the Content Template as a foundation for their new page.

+

+ How do I create a Content Template? +

+ +

There are two ways to create a Content Template:

+
    +
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • +
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • +
+

Once given a name, editors can start using the Content Template as a foundation for their new page.

+
-

How do I manage Content Templates

-

You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the - Content Template is based on and click it to edit or delete it.

+

+ How do I manage Content Templates? +

+

+ You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the Content Template is based on and click it to edit or delete it. +

diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index e4f9ed7b91..e96ef754c4 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2423,4 +2423,21 @@ To manage your website, simply open the Umbraco back office and start adding con Umbraco Forms Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! + + What are Content Templates? + Content Templates are pre-defined content that can be selected when creating a new content node. + How do I create a Content Template? + + There are two ways to create a Content Template:

+
    +
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • +
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • +
+

Once given a name, editors can start using the Content Template as a foundation for their new page.

+ ]]> +
+ How do I manage Content Templates? + You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete it. + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 5fc037d8b3..2bb6495977 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2443,4 +2443,21 @@ To manage your website, simply open the Umbraco back office and start adding con Umbraco Forms Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! + + What are Content Templates? + Content Templates are pre-defined content that can be selected when creating a new content node. + How do I create a Content Template? + + There are two ways to create a Content Template:

+
    +
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • +
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • +
+

Once given a name, editors can start using the Content Template as a foundation for their new page.

+ ]]> +
+ How do I manage Content Templates? + You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete it. + From 19f16c3b1fefadfd71d2de523fd13561ffeb69aa Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 18:02:21 +0200 Subject: [PATCH 32/55] Theme backoffice docs for v8 to clearly differentiate it from the v7 docs (#8010) --- src/Umbraco.Web.UI.Docs/gulpfile.js | 4 +-- src/Umbraco.Web.UI.Docs/umb-docs.css | 39 +++++++++++++++++----------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Docs/gulpfile.js b/src/Umbraco.Web.UI.Docs/gulpfile.js index 789e2b7845..a3e596ecad 100644 --- a/src/Umbraco.Web.UI.Docs/gulpfile.js +++ b/src/Umbraco.Web.UI.Docs/gulpfile.js @@ -16,7 +16,7 @@ gulp.task('docs', [], function (cb) { var options = { html5Mode: false, startPage: '/api', - title: "Umbraco Backoffice UI API Documentation", + title: "Umbraco 8 Backoffice UI API Documentation", dest: './api', styles: ['./umb-docs.css'], image: "https://our.umbraco.com/assets/images/logo.svg" @@ -53,4 +53,4 @@ gulp.task('open:docs', function (cb) { gulp.src(__filename) .pipe(open(options)); cb(); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Docs/umb-docs.css b/src/Umbraco.Web.UI.Docs/umb-docs.css index 0f2e3e7f74..b748a0a609 100644 --- a/src/Umbraco.Web.UI.Docs/umb-docs.css +++ b/src/Umbraco.Web.UI.Docs/umb-docs.css @@ -4,7 +4,7 @@ html { } body { font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif; - + } .container, .navbar-static-top .container, .navbar-fixed-top .container, .navbar-fixed-bottom .container { @@ -33,37 +33,43 @@ body { font-family: inherit; } +.navbar .container{ + min-height: inherit; + display: flex; + align-items: center; +} + .navbar .brand { display: block; - float: left; - padding: 10px 20px 10px; - margin-left: -20px; - font-size: 20px; - font-weight: 200; - color: rgba(0,0,0,.8); + color: white; text-shadow: none; } .navbar-fixed-top .navbar-inner { min-height: 50px; - background: #a3db78; + background: #3544b1; } .form-search .well { - background-color: #f5fbf1; + background-color: #f7f7f7; } .form-search > ul.nav > li.module { - background-color: #daf0c9; + background-color: #3544b1; } -.breadcrumb { - background-color: #f5fbf1; +.form-search > ul.nav > li.module a { + color: white; } -a { - color: #f36f21; +.form-search > ul.nav > li.section { + background-color: #ccc; } + +.breadcrumb { + background-color: #f7f7f7; +} + a:hover { text-decoration: none; color: rgba(0,0,0,.8); @@ -87,9 +93,12 @@ a:hover { color: #000; } +.form-search > ul.nav > li.module > a:hover{ + color: #fff; +} + .header img { width: 50px; - margin-top: 5px; } .content .methods code { From 14ad2bd27cae73f2dcf41d8f810ec1c50975b032 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 18:15:18 +0200 Subject: [PATCH 33/55] Convert span to button, add missing label, hide icons for screen readers (#8011) --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 2 +- .../src/views/propertyeditors/datepicker/datepicker.html | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 764b73c593..b5870b8dce 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -863,11 +863,11 @@ .bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;} .umb-datepicker .input-append .add-on{cursor: pointer;} .umb-datepicker .input-append .on-top { + border: 0 none; position: absolute; margin-left: -31px; margin-top: 1px; display: inline-block; - height: 22px; padding: 5px 6px 3px 6px; font-size: @baseFontSize; font-weight: normal; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index e697dc56a5..b35663c3df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -20,10 +20,11 @@ ng-required="model.validation.mandatory" val-server="value" class="datepickerinput"> - - - - + +
From 62b0886afe8b6bbc394709be51376c446b3961be Mon Sep 17 00:00:00 2001 From: BatJan Date: Sat, 16 May 2020 20:23:44 +0200 Subject: [PATCH 34/55] Fix label alignment --- .../src/less/components/umb-form-check.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index a52f81b92a..9a3760444d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -18,6 +18,8 @@ label.umb-form-check--checkbox{ } .umb-form-check__info { margin-left:20px; + position: relative; + top: 3px; } From 30900c59aef9d160136f559ac230ef060572b40b Mon Sep 17 00:00:00 2001 From: BatJan Date: Sat, 16 May 2020 21:00:41 +0200 Subject: [PATCH 35/55] Add disable-dirty-check option --- .../components/forms/umbcheckbox.directive.js | 4 +++- .../views/components/forms/umb-checkbox.html | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js index 9a9d6d4a76..389aec2044 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -32,6 +32,7 @@ @param {boolean} required Set the checkbox to be required. @param {callback} onChange Callback when the value of the checkbox change by interaction. @param {string} cssClass Set a css class modifier +@param {boolean} disableDirtyCheck Disable checking if the model is dirty **/ @@ -84,7 +85,8 @@ required: "<", onChange: "&?", cssClass: "@?", - iconClass: "@?" + iconClass: "@?", + disableDirtyCheck: "=?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html index ba5adf199a..5f83d9ae53 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html @@ -1,7 +1,7 @@
- + ng-change="vm.change()" + no-dirty-check /> + + /// The API controller used for getting log history @@ -27,27 +31,34 @@ namespace Umbraco.Web.Editors { private readonly IMediaFileSystem _mediaFileSystem; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IAuditService _auditService; + private readonly UmbracoMapper _umbracoMapper; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IUserService _userService; + private readonly AppCaches _appCaches; + private readonly ISqlContext _sqlContext; public LogController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, IMediaFileSystem mediaFileSystem, - IShortStringHelper shortStringHelper, - UmbracoMapper umbracoMapper, IImageUrlGenerator imageUrlGenerator, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) - { - _mediaFileSystem = mediaFileSystem; - _imageUrlGenerator = imageUrlGenerator; - } + IAuditService auditService, + UmbracoMapper umbracoMapper, + IUmbracoContextAccessor umbracoContextAccessor, + IUserService userService, + AppCaches appCaches, + ISqlContext sqlContext) + { + _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); + _imageUrlGenerator = imageUrlGenerator ?? throw new ArgumentNullException(nameof(imageUrlGenerator)); + _auditService = auditService ?? throw new ArgumentNullException(nameof(auditService)); + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _appCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches)); + _sqlContext = sqlContext ?? throw new ArgumentNullException(nameof(sqlContext)); + } - [UmbracoApplicationAuthorize(Core.Constants.Applications.Content, Core.Constants.Applications.Media)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{ Constants.Applications.Content, Constants.Applications.Media } })] public PagedResult GetPagedEntityLog(int id, int pageNumber = 1, int pageSize = 10, @@ -60,9 +71,9 @@ namespace Umbraco.Web.Editors } long totalRecords; - var dateQuery = sinceDate.HasValue ? SqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null; - var result = Services.AuditService.GetPagedItemsByEntity(id, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter: dateQuery); - var mapped = result.Select(item => Mapper.Map(item)); + var dateQuery = sinceDate.HasValue ? _sqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null; + var result = _auditService.GetPagedItemsByEntity(id, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter: dateQuery); + var mapped = result.Select(item => _umbracoMapper.Map(item)); var page = new PagedResult(totalRecords, pageNumber, pageSize) { @@ -84,10 +95,11 @@ namespace Umbraco.Web.Editors } long totalRecords; - var dateQuery = sinceDate.HasValue ? SqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null; - var userId = Security.GetUserId().ResultOr(0); - var result = Services.AuditService.GetPagedItemsByUser(userId, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter:dateQuery); - var mapped = Mapper.MapEnumerable(result); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var dateQuery = sinceDate.HasValue ? _sqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null; + var userId = umbracoContext.Security.GetUserId().ResultOr(0); + var result = _auditService.GetPagedItemsByUser(userId, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter:dateQuery); + var mapped = _umbracoMapper.MapEnumerable(result); return new PagedResult(totalRecords, pageNumber, pageSize) { Items = MapAvatarsAndNames(mapped) @@ -98,9 +110,9 @@ namespace Umbraco.Web.Editors { var mappedItems = items.ToList(); var userIds = mappedItems.Select(x => x.UserId).ToArray(); - var userAvatars = Services.UserService.GetUsersById(userIds) - .ToDictionary(x => x.Id, x => x.GetUserAvatarUrls(AppCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator)); - var userNames = Services.UserService.GetUsersById(userIds).ToDictionary(x => x.Id, x => x.Name); + var userAvatars = _userService.GetUsersById(userIds) + .ToDictionary(x => x.Id, x => x.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator)); + var userNames = _userService.GetUsersById(userIds).ToDictionary(x => x.Id, x => x.Name); foreach (var item in mappedItems) { if (userAvatars.TryGetValue(item.UserId, out var avatars)) From 99c2fd7f8149ce4bddeb1f44c936a8eb3ff6c4d5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 26 May 2020 19:05:15 +0200 Subject: [PATCH 38/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - LogViewerController --- .../Controllers}/LogViewerController.cs | 27 ++++++++++--------- .../Editors/BackOfficeServerVariables.cs | 24 ++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/LogViewerController.cs (76%) diff --git a/src/Umbraco.Web/Editors/LogViewerController.cs b/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs similarity index 76% rename from src/Umbraco.Web/Editors/LogViewerController.cs rename to src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs index 31b29fd426..444ebbe988 100644 --- a/src/Umbraco.Web/Editors/LogViewerController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; -using System.Web.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Logging.Viewer; using Umbraco.Core.Models; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Editors; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { /// /// Backoffice controller supporting the dashboard for viewing logs with some simple graphs & filtering @@ -33,60 +34,60 @@ namespace Umbraco.Web.Editors } [HttpGet] - public bool GetCanViewLogs([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) + public bool GetCanViewLogs([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); return CanViewLogs(logTimePeriod); } [HttpGet] - public int GetNumberOfErrors([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) + public int GetNumberOfErrors([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); + throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); } return _logViewer.GetNumberOfErrors(logTimePeriod); } [HttpGet] - public LogLevelCounts GetLogLevelCounts([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) + public LogLevelCounts GetLogLevelCounts([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); + throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); } return _logViewer.GetLogLevelCounts(logTimePeriod); } [HttpGet] - public IEnumerable GetMessageTemplates([FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) + public IEnumerable GetMessageTemplates([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); + throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); } return _logViewer.GetMessageTemplates(logTimePeriod); } [HttpGet] - public PagedResult GetLogs(string orderDirection = "Descending", int pageNumber = 1, string filterExpression = null, [FromUri]string[] logLevels = null, [FromUri] DateTime? startDate = null,[FromUri] DateTime? endDate = null) + public PagedResult GetLogs(string orderDirection = "Descending", int pageNumber = 1, string filterExpression = null, [FromQuery]string[] logLevels = null, [FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size")); + throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); } var direction = orderDirection == "Descending" ? Direction.Descending : Direction.Ascending; diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index a509ecfd29..404bf88c89 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -217,10 +217,10 @@ namespace Umbraco.Web.Editors // "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetDashboard(null)) // }, - { - "logApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null)) - }, + // { + // "logApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null)) + // }, { "memberApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetByKey(Guid.Empty)) @@ -317,18 +317,18 @@ namespace Umbraco.Web.Editors // "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetSupportedLocales()) // }, - { - "languageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllLanguages()) - }, + // { + // "languageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetAllLanguages()) + // }, { "relationTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(1)) }, - { - "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNumberOfErrors(null, null)) - }, + // { + // "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetNumberOfErrors(null, null)) + // }, // { // "webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetStatus()) From 8de3551166220a47ad93c4cab632668acd0d4954 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 27 May 2020 08:19:48 +0200 Subject: [PATCH 39/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved KeepAliveController to backoffice url --- src/Umbraco.Web.BackOffice/Controllers/KeepAliveController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.BackOffice/Controllers/KeepAliveController.cs b/src/Umbraco.Web.BackOffice/Controllers/KeepAliveController.cs index 07393db1b6..ed8d02b7b2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/KeepAliveController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/KeepAliveController.cs @@ -1,11 +1,15 @@ using System; using System.Runtime.Serialization; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Controllers; namespace Umbraco.Web.BackOffice.Controllers { + [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] + [IsBackOffice] public class KeepAliveController : UmbracoApiController { [OnlyLocalRequests] From 31dc9b079b5804370fdb67a37582b9e771831080 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 27 May 2020 08:20:41 +0200 Subject: [PATCH 40/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved PackageController and added VersionConverter on AngularJsonMediaTypeFormatter, instead of having single attribute only used for PackageController --- .../Controllers}/PackageController.cs | 115 ++++++++---------- .../AngularJsonMediaTypeFormatter.cs | 2 + 2 files changed, 50 insertions(+), 67 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/PackageController.cs (50%) diff --git a/src/Umbraco.Web/Editors/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs similarity index 50% rename from src/Umbraco.Web/Editors/PackageController.cs rename to src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 6dc913237d..220d67f794 100644 --- a/src/Umbraco.Web/Editors/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -1,65 +1,54 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Web; -using System.Web.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; using Semver; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Mapping; using Umbraco.Core.Models.Packaging; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Editors; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { /// /// A controller used for managing packages in the back office /// [PluginController("UmbracoApi")] - [SerializeVersion] - [UmbracoApplicationAuthorize(Core.Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Packages}})] public class PackageController : UmbracoAuthorizedJsonController { - private readonly IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IPackagingService _packagingService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; public PackageController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, - UmbracoMapper umbracoMapper, - IIOHelper ioHelper, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) + IHostingEnvironment hostingEnvironment, + IPackagingService packagingService, + IUmbracoContextAccessor umbracoContextAccessor) { - _ioHelper = ioHelper; + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + _packagingService = packagingService ?? throw new ArgumentNullException(nameof(packagingService)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); } public IEnumerable GetCreatedPackages() { - return Services.PackagingService.GetAllCreatedPackages(); + return _packagingService.GetAllCreatedPackages(); } public PackageDefinition GetCreatedPackageById(int id) { - var package = Services.PackagingService.GetCreatedPackageById(id); + var package = _packagingService.GetCreatedPackageById(id); if (package == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -79,17 +68,16 @@ namespace Umbraco.Web.Editors public PackageDefinition PostSavePackage(PackageDefinition model) { if (ModelState.IsValid == false) - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + throw HttpResponseException.CreateValidationErrorResponse(ModelState); //save it - if (!Services.PackagingService.SaveCreatedPackage(model)) - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( + if (!_packagingService.SaveCreatedPackage(model)) + throw HttpResponseException.CreateNotificationValidationErrorResponse( model.Id == default ? $"A package with the name {model.Name} already exists" - : $"The package with id {model.Id} was not found")); + : $"The package with id {model.Id} was not found"); - Services.PackagingService.ExportCreatedPackage(model); + _packagingService.ExportCreatedPackage(model); //the packagePath will be on the model return model; @@ -102,55 +90,48 @@ namespace Umbraco.Web.Editors /// [HttpPost] [HttpDelete] - public IHttpActionResult DeleteCreatedPackage(int packageId) + public IActionResult DeleteCreatedPackage(int packageId) { - Services.PackagingService.DeleteCreatedPackage(packageId, Security.GetUserId().ResultOr(0)); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + _packagingService.DeleteCreatedPackage(packageId, umbracoContext.Security.GetUserId().ResultOr(0)); return Ok(); } [HttpGet] - public HttpResponseMessage DownloadCreatedPackage(int id) + public IActionResult DownloadCreatedPackage(int id) { - var package = Services.PackagingService.GetCreatedPackageById(id); + var package = _packagingService.GetCreatedPackageById(id); if (package == null) - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); - var fullPath = _ioHelper.MapPath(package.PackagePath); - if (!File.Exists(fullPath)) - return Request.CreateNotificationValidationErrorResponse("No file found for path " + package.PackagePath); + var fullPath = _hostingEnvironment.MapPathContentRoot(package.PackagePath); + if (!System.IO.File.Exists(fullPath)) + throw HttpResponseException.CreateNotificationValidationErrorResponse("No file found for path " + package.PackagePath); var fileName = Path.GetFileName(package.PackagePath); var encoding = Encoding.UTF8; - var response = new HttpResponseMessage + var cd = new System.Net.Mime.ContentDisposition { - Content = new StreamContent(File.OpenRead(fullPath)) - { - Headers = - { - ContentDisposition = new ContentDispositionHeaderValue("attachment") - { - FileName = HttpUtility.UrlEncode(fileName, encoding) - }, - ContentType = new MediaTypeHeaderValue("application/octet-stream") - { - CharSet = encoding.WebName - } - } - } + FileName = HttpUtility.UrlEncode(fileName, encoding), + Inline = false // false = prompt the user for downloading; true = browser to try to show the file inline }; - + Response.Headers.Add("Content-Disposition", cd.ToString()); // Set custom header so umbRequestHelper.downloadFile can save the correct filename - response.Headers.Add("x-filename", HttpUtility.UrlEncode(fileName, encoding)); + Response.Headers.Add("x-filename", HttpUtility.UrlEncode(fileName, encoding)); + return new FileStreamResult(System.IO.File.OpenRead(fullPath), new MediaTypeHeaderValue("application/octet-stream") + { + Charset = encoding.WebName, + + }); - return response; } public PackageDefinition GetInstalledPackageById(int id) { - var pack = Services.PackagingService.GetInstalledPackageById(id); + var pack = _packagingService.GetInstalledPackageById(id); if (pack == null) throw new HttpResponseException(HttpStatusCode.NotFound); return pack; } @@ -161,7 +142,7 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetInstalled() { - return Services.PackagingService.GetAllInstalledPackages() + return _packagingService.GetAllInstalledPackages() .GroupBy( //group by name x => x.Name, diff --git a/src/Umbraco.Web.Common/Formatters/AngularJsonMediaTypeFormatter.cs b/src/Umbraco.Web.Common/Formatters/AngularJsonMediaTypeFormatter.cs index 9a10269398..5e400c78eb 100644 --- a/src/Umbraco.Web.Common/Formatters/AngularJsonMediaTypeFormatter.cs +++ b/src/Umbraco.Web.Common/Formatters/AngularJsonMediaTypeFormatter.cs @@ -3,6 +3,7 @@ using System.IO; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace Umbraco.Web.Common.Formatters { @@ -19,6 +20,7 @@ namespace Umbraco.Web.Common.Formatters public AngularJsonMediaTypeFormatter(JsonSerializerSettings serializerSettings, ArrayPool charPool, MvcOptions mvcOptions) : base(serializerSettings, charPool, mvcOptions) { + serializerSettings.Converters.Add(new VersionConverter()); } protected override JsonWriter CreateJsonWriter(TextWriter writer) From 10e66f04f2b14628bfce4bef9264469dd65957f3 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 27 May 2020 12:17:46 +0200 Subject: [PATCH 41/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved PackageInstallController + Handled the fileupload in a new (aspnet core) way. So no need to clean up from a temp location anymore as we read from the stream. --- .../Models/LocalPackageInstallModel.cs | 4 +- .../Controllers}/PackageInstallController.cs | 171 ++++++++---------- .../Editors/BackOfficeServerVariables.cs | 8 +- 3 files changed, 84 insertions(+), 99 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/PackageInstallController.cs (62%) diff --git a/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs b/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs index 4eab8ed2c2..97d27ffdcb 100644 --- a/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs +++ b/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs @@ -9,10 +9,8 @@ namespace Umbraco.Web.Models /// A model that represents uploading a local package /// [DataContract(Name = "localPackageInstallModel")] - public class LocalPackageInstallModel : PackageInstallModel, IHaveUploadedFiles, INotificationModel + public class LocalPackageInstallModel : PackageInstallModel, INotificationModel { - public List UploadedFiles { get; } = new List(); - [DataMember(Name = "notifications")] public List Notifications { get; } = new List(); diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs similarity index 62% rename from src/Umbraco.Web/Editors/PackageInstallController.cs rename to src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs index 650050ff66..9f86d3e5cc 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs @@ -1,69 +1,66 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Tasks; -using System.Web.Http; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Semver; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; -using Umbraco.Core.Mapping; using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Packaging; using Umbraco.Net; using Umbraco.Core.Packaging; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Core.WebAssets; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Editors; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using File = System.IO.File; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { /// /// A controller used for installing packages and managing all of the data in the packages section in the back office /// [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Core.Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Packages} })] public class PackageInstallController : UmbracoAuthorizedJsonController { private readonly IUmbracoVersion _umbracoVersion; - private readonly IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IRuntimeMinifier _runtimeMinifier; + private readonly IPackagingService _packagingService; + private readonly ILogger _logger; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILocalizedTextService _localizedTextService; public PackageInstallController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, IUmbracoVersion umbracoVersion, - UmbracoMapper umbracoMapper, - IIOHelper ioHelper, - IPublishedUrlProvider publishedUrlProvider, + IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime, - IRuntimeMinifier runtimeMinifier) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) + IRuntimeMinifier runtimeMinifier, + IPackagingService packagingService, + ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + ILocalizedTextService localizedTextService) { - _umbracoVersion = umbracoVersion; - _ioHelper = ioHelper; - _umbracoApplicationLifetime = umbracoApplicationLifetime; - _runtimeMinifier = runtimeMinifier; + _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + _umbracoApplicationLifetime = umbracoApplicationLifetime ?? throw new ArgumentNullException(nameof(umbracoApplicationLifetime)); + _runtimeMinifier = runtimeMinifier ?? throw new ArgumentNullException(nameof(runtimeMinifier)); + _packagingService = packagingService ?? throw new ArgumentNullException(nameof(packagingService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); } /// @@ -73,9 +70,9 @@ namespace Umbraco.Web.Editors /// /// [HttpPost] - public IHttpActionResult ValidateInstalled(string name, string version) + public IActionResult ValidateInstalled(string name, string version) { - var installType = Services.PackagingService.GetPackageInstallType(name, SemVersion.Parse(version), out _); + var installType = _packagingService.GetPackageInstallType(name, SemVersion.Parse(version), out _); if (installType == PackageInstallType.AlreadyInstalled) return BadRequest(); @@ -84,26 +81,28 @@ namespace Umbraco.Web.Editors } [HttpPost] - public IHttpActionResult Uninstall(int packageId) + public IActionResult Uninstall(int packageId) { try { - var package = Services.PackagingService.GetInstalledPackageById(packageId); + + var package = _packagingService.GetInstalledPackageById(packageId); if (package == null) return NotFound(); - var summary = Services.PackagingService.UninstallPackage(package.Name, Security.GetUserId().ResultOr(0)); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var summary = _packagingService.UninstallPackage(package.Name, umbracoContext.Security.GetUserId().ResultOr(0)); //now get all other packages by this name since we'll uninstall all versions - foreach (var installed in Services.PackagingService.GetAllInstalledPackages() + foreach (var installed in _packagingService.GetAllInstalledPackages() .Where(x => x.Name == package.Name && x.Id != package.Id)) { //remove from the xml - Services.PackagingService.DeleteInstalledPackage(installed.Id, Security.GetUserId().ResultOr(0)); + _packagingService.DeleteInstalledPackage(installed.Id, umbracoContext.Security.GetUserId().ResultOr(0)); } } catch (Exception ex) { - Logger.Error(ex, "Failed to uninstall."); + _logger.Error(ex, "Failed to uninstall."); throw; } @@ -114,9 +113,9 @@ namespace Umbraco.Web.Editors private void PopulateFromPackageData(LocalPackageInstallModel model) { - var zipFile = new FileInfo(Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Packages), model.ZipFileName)); + var zipFile = new FileInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages), model.ZipFileName)); - var ins = Services.PackagingService.GetCompiledPackageInfo(zipFile); + var ins = _packagingService.GetCompiledPackageInfo(zipFile); model.Name = ins.Name; model.Author = ins.Author; @@ -150,22 +149,11 @@ namespace Umbraco.Web.Editors } [HttpPost] - [FileUploadCleanupFilter(false)] - public async Task UploadLocalPackage() + public async Task> UploadLocalPackage(List file) { - if (Request.Content.IsMimeMultipartContent() == false) - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - - var root = _ioHelper.MapPath(Core.Constants.SystemDirectories.TempFileUploads); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); - - var result = await Request.Content.ReadAsMultipartAsync(provider); - //must have a file - if (result.FileData.Count == 0) - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + if (file.Count == 0) + return NotFound(); var model = new LocalPackageInstallModel { @@ -174,38 +162,36 @@ namespace Umbraco.Web.Editors }; //get the files - foreach (var file in result.FileData) + foreach (var formFile in file) { - var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); + var fileName = formFile.FileName.Trim('\"'); var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); if (ext.InvariantEquals("zip") || ext.InvariantEquals("umb")) { //we always save package files to /App_Data/packages/package-guid.umb for processing as a standard so lets copy. - var packagesFolder = _ioHelper.MapPath(Core.Constants.SystemDirectories.Packages); + var packagesFolder = _hostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.Packages); Directory.CreateDirectory(packagesFolder); var packageFile = Path.Combine(packagesFolder, model.PackageGuid + ".umb"); - File.Copy(file.LocalFileName, packageFile); + + using (var stream = System.IO.File.Create(packageFile)) + { + await formFile.CopyToAsync(stream); + } model.ZipFileName = Path.GetFileName(packageFile); - //add to the outgoing model so that all temp files are cleaned up - model.UploadedFiles.Add(new ContentPropertyFile - { - TempFilePath = file.LocalFileName - }); - //Populate the model from the metadata in the package file (zip file) PopulateFromPackageData(model); - var installType = Services.PackagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); + var installType = _packagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); if (installType == PackageInstallType.AlreadyInstalled) { //this package is already installed - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/packageAlreadyInstalled"))); + throw HttpResponseException.CreateNotificationValidationErrorResponse( + _localizedTextService.Localize("packager/packageAlreadyInstalled")); } model.OriginalVersion = installType == PackageInstallType.Upgrade ? alreadyInstalled.Version : null; @@ -214,8 +200,8 @@ namespace Umbraco.Web.Editors else { model.Notifications.Add(new BackOfficeNotification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("media/disallowedFileType"), + _localizedTextService.Localize("speechBubbles/operationFailedHeader"), + _localizedTextService.Localize("media/disallowedFileType"), NotificationStyle.Warning)); } @@ -235,12 +221,13 @@ namespace Umbraco.Web.Editors { //Default path string fileName = packageGuid + ".umb"; - if (File.Exists(Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Packages), fileName)) == false) + if (System.IO.File.Exists(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages), fileName)) == false) { - var packageFile = await Services.PackagingService.FetchPackageFileAsync( + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var packageFile = await _packagingService.FetchPackageFileAsync( Guid.Parse(packageGuid), _umbracoVersion.Current, - Security.GetUserId().ResultOr(0)); + umbracoContext.Security.GetUserId().ResultOr(0)); fileName = packageFile.Name; } @@ -254,12 +241,12 @@ namespace Umbraco.Web.Editors //Populate the model from the metadata in the package file (zip file) PopulateFromPackageData(model); - var installType = Services.PackagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); + var installType = _packagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); if (installType == PackageInstallType.AlreadyInstalled) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/packageAlreadyInstalled"))); + throw HttpResponseException.CreateNotificationValidationErrorResponse( + _localizedTextService.Localize("packager/packageAlreadyInstalled")); } model.OriginalVersion = installType == PackageInstallType.Upgrade ? alreadyInstalled.Version : null; @@ -275,20 +262,20 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallModel Import(PackageInstallModel model) { - var zipFile = new FileInfo(Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Packages), model.ZipFileName)); + var zipFile = new FileInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages), model.ZipFileName)); - var packageInfo = Services.PackagingService.GetCompiledPackageInfo(zipFile); + var packageInfo = _packagingService.GetCompiledPackageInfo(zipFile); //now we need to check for version comparison if (packageInfo.UmbracoVersionRequirementsType == RequirementsType.Strict) { var packageMinVersion = packageInfo.UmbracoVersion; if (_umbracoVersion.Current < packageMinVersion) - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()}))); + throw HttpResponseException.CreateNotificationValidationErrorResponse( + _localizedTextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()})); } - var installType = Services.PackagingService.GetPackageInstallType(packageInfo.Name, SemVersion.Parse(packageInfo.Version), out var alreadyInstalled); + var installType = _packagingService.GetPackageInstallType(packageInfo.Name, SemVersion.Parse(packageInfo.Version), out var alreadyInstalled); var packageDefinition = PackageDefinition.FromCompiledPackage(packageInfo); packageDefinition.PackagePath = zipFile.FullName; @@ -302,8 +289,8 @@ namespace Umbraco.Web.Editors case PackageInstallType.Upgrade: //save to the installedPackages.config, this will create a new entry with a new Id - if (!Services.PackagingService.SaveInstalledPackage(packageDefinition)) - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Could not save the package")); + if (!_packagingService.SaveInstalledPackage(packageDefinition)) + throw HttpResponseException.CreateNotificationValidationErrorResponse("Could not save the package"); model.Id = packageDefinition.Id; break; @@ -323,12 +310,12 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallModel InstallFiles(PackageInstallModel model) { - var definition = Services.PackagingService.GetInstalledPackageById(model.Id); + var definition = _packagingService.GetInstalledPackageById(model.Id); if (definition == null) throw new InvalidOperationException("Not package definition found with id " + model.Id); var zipFile = new FileInfo(definition.PackagePath); - - var installedFiles = Services.PackagingService.InstallCompiledPackageFiles(definition, zipFile, Security.GetUserId().ResultOr(0)); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var installedFiles = _packagingService.InstallCompiledPackageFiles(definition, zipFile, umbracoContext.Security.GetUserId().ResultOr(0)); //set a restarting marker and reset the app pool _umbracoApplicationLifetime.Restart(); @@ -356,12 +343,12 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallModel InstallData(PackageInstallModel model) { - var definition = Services.PackagingService.GetInstalledPackageById(model.Id); + var definition = _packagingService.GetInstalledPackageById(model.Id); if (definition == null) throw new InvalidOperationException("Not package definition found with id " + model.Id); var zipFile = new FileInfo(definition.PackagePath); - - var installSummary = Services.PackagingService.InstallCompiledPackageData(definition, zipFile, Security.GetUserId().ResultOr(0)); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var installSummary = _packagingService.InstallCompiledPackageData(definition, zipFile, umbracoContext.Security.GetUserId().ResultOr(0)); return model; } @@ -374,12 +361,12 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallResult CleanUp(PackageInstallModel model) { - var definition = Services.PackagingService.GetInstalledPackageById(model.Id); + var definition = _packagingService.GetInstalledPackageById(model.Id); if (definition == null) throw new InvalidOperationException("Not package definition found with id " + model.Id); var zipFile = new FileInfo(definition.PackagePath); - var packageInfo = Services.PackagingService.GetCompiledPackageInfo(zipFile); + var packageInfo = _packagingService.GetCompiledPackageInfo(zipFile); zipFile.Delete(); diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 404bf88c89..f6d5f7091d 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -225,10 +225,10 @@ namespace Umbraco.Web.Editors "memberApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetByKey(Guid.Empty)) }, - { - "packageInstallApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.Fetch(string.Empty)) - }, + // { + // "packageInstallApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.Fetch(string.Empty)) + // }, { "packageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetCreatedPackages()) From bfff08e98e4e2ec63e4d5515378a14bfbb416627 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 27 May 2020 12:17:46 +0200 Subject: [PATCH 42/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Build fix --- .../Models/LocalPackageInstallModel.cs | 4 +- .../Controllers}/PackageInstallController.cs | 171 ++++++++---------- .../Editors/BackOfficeServerVariables.cs | 16 +- src/Umbraco.Web/Umbraco.Web.csproj | 5 - 4 files changed, 88 insertions(+), 108 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/PackageInstallController.cs (62%) diff --git a/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs b/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs index 4eab8ed2c2..97d27ffdcb 100644 --- a/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs +++ b/src/Umbraco.Infrastructure/Models/LocalPackageInstallModel.cs @@ -9,10 +9,8 @@ namespace Umbraco.Web.Models /// A model that represents uploading a local package /// [DataContract(Name = "localPackageInstallModel")] - public class LocalPackageInstallModel : PackageInstallModel, IHaveUploadedFiles, INotificationModel + public class LocalPackageInstallModel : PackageInstallModel, INotificationModel { - public List UploadedFiles { get; } = new List(); - [DataMember(Name = "notifications")] public List Notifications { get; } = new List(); diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs similarity index 62% rename from src/Umbraco.Web/Editors/PackageInstallController.cs rename to src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs index 650050ff66..9f86d3e5cc 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs @@ -1,69 +1,66 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Tasks; -using System.Web.Http; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Semver; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; -using Umbraco.Core.Mapping; using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Packaging; using Umbraco.Net; using Umbraco.Core.Packaging; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Core.WebAssets; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Editors; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using File = System.IO.File; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { /// /// A controller used for installing packages and managing all of the data in the packages section in the back office /// [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Core.Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Packages} })] public class PackageInstallController : UmbracoAuthorizedJsonController { private readonly IUmbracoVersion _umbracoVersion; - private readonly IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IRuntimeMinifier _runtimeMinifier; + private readonly IPackagingService _packagingService; + private readonly ILogger _logger; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILocalizedTextService _localizedTextService; public PackageInstallController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, IUmbracoVersion umbracoVersion, - UmbracoMapper umbracoMapper, - IIOHelper ioHelper, - IPublishedUrlProvider publishedUrlProvider, + IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime, - IRuntimeMinifier runtimeMinifier) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) + IRuntimeMinifier runtimeMinifier, + IPackagingService packagingService, + ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + ILocalizedTextService localizedTextService) { - _umbracoVersion = umbracoVersion; - _ioHelper = ioHelper; - _umbracoApplicationLifetime = umbracoApplicationLifetime; - _runtimeMinifier = runtimeMinifier; + _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + _umbracoApplicationLifetime = umbracoApplicationLifetime ?? throw new ArgumentNullException(nameof(umbracoApplicationLifetime)); + _runtimeMinifier = runtimeMinifier ?? throw new ArgumentNullException(nameof(runtimeMinifier)); + _packagingService = packagingService ?? throw new ArgumentNullException(nameof(packagingService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); } /// @@ -73,9 +70,9 @@ namespace Umbraco.Web.Editors /// /// [HttpPost] - public IHttpActionResult ValidateInstalled(string name, string version) + public IActionResult ValidateInstalled(string name, string version) { - var installType = Services.PackagingService.GetPackageInstallType(name, SemVersion.Parse(version), out _); + var installType = _packagingService.GetPackageInstallType(name, SemVersion.Parse(version), out _); if (installType == PackageInstallType.AlreadyInstalled) return BadRequest(); @@ -84,26 +81,28 @@ namespace Umbraco.Web.Editors } [HttpPost] - public IHttpActionResult Uninstall(int packageId) + public IActionResult Uninstall(int packageId) { try { - var package = Services.PackagingService.GetInstalledPackageById(packageId); + + var package = _packagingService.GetInstalledPackageById(packageId); if (package == null) return NotFound(); - var summary = Services.PackagingService.UninstallPackage(package.Name, Security.GetUserId().ResultOr(0)); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var summary = _packagingService.UninstallPackage(package.Name, umbracoContext.Security.GetUserId().ResultOr(0)); //now get all other packages by this name since we'll uninstall all versions - foreach (var installed in Services.PackagingService.GetAllInstalledPackages() + foreach (var installed in _packagingService.GetAllInstalledPackages() .Where(x => x.Name == package.Name && x.Id != package.Id)) { //remove from the xml - Services.PackagingService.DeleteInstalledPackage(installed.Id, Security.GetUserId().ResultOr(0)); + _packagingService.DeleteInstalledPackage(installed.Id, umbracoContext.Security.GetUserId().ResultOr(0)); } } catch (Exception ex) { - Logger.Error(ex, "Failed to uninstall."); + _logger.Error(ex, "Failed to uninstall."); throw; } @@ -114,9 +113,9 @@ namespace Umbraco.Web.Editors private void PopulateFromPackageData(LocalPackageInstallModel model) { - var zipFile = new FileInfo(Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Packages), model.ZipFileName)); + var zipFile = new FileInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages), model.ZipFileName)); - var ins = Services.PackagingService.GetCompiledPackageInfo(zipFile); + var ins = _packagingService.GetCompiledPackageInfo(zipFile); model.Name = ins.Name; model.Author = ins.Author; @@ -150,22 +149,11 @@ namespace Umbraco.Web.Editors } [HttpPost] - [FileUploadCleanupFilter(false)] - public async Task UploadLocalPackage() + public async Task> UploadLocalPackage(List file) { - if (Request.Content.IsMimeMultipartContent() == false) - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - - var root = _ioHelper.MapPath(Core.Constants.SystemDirectories.TempFileUploads); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); - - var result = await Request.Content.ReadAsMultipartAsync(provider); - //must have a file - if (result.FileData.Count == 0) - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + if (file.Count == 0) + return NotFound(); var model = new LocalPackageInstallModel { @@ -174,38 +162,36 @@ namespace Umbraco.Web.Editors }; //get the files - foreach (var file in result.FileData) + foreach (var formFile in file) { - var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); + var fileName = formFile.FileName.Trim('\"'); var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); if (ext.InvariantEquals("zip") || ext.InvariantEquals("umb")) { //we always save package files to /App_Data/packages/package-guid.umb for processing as a standard so lets copy. - var packagesFolder = _ioHelper.MapPath(Core.Constants.SystemDirectories.Packages); + var packagesFolder = _hostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.Packages); Directory.CreateDirectory(packagesFolder); var packageFile = Path.Combine(packagesFolder, model.PackageGuid + ".umb"); - File.Copy(file.LocalFileName, packageFile); + + using (var stream = System.IO.File.Create(packageFile)) + { + await formFile.CopyToAsync(stream); + } model.ZipFileName = Path.GetFileName(packageFile); - //add to the outgoing model so that all temp files are cleaned up - model.UploadedFiles.Add(new ContentPropertyFile - { - TempFilePath = file.LocalFileName - }); - //Populate the model from the metadata in the package file (zip file) PopulateFromPackageData(model); - var installType = Services.PackagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); + var installType = _packagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); if (installType == PackageInstallType.AlreadyInstalled) { //this package is already installed - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/packageAlreadyInstalled"))); + throw HttpResponseException.CreateNotificationValidationErrorResponse( + _localizedTextService.Localize("packager/packageAlreadyInstalled")); } model.OriginalVersion = installType == PackageInstallType.Upgrade ? alreadyInstalled.Version : null; @@ -214,8 +200,8 @@ namespace Umbraco.Web.Editors else { model.Notifications.Add(new BackOfficeNotification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("media/disallowedFileType"), + _localizedTextService.Localize("speechBubbles/operationFailedHeader"), + _localizedTextService.Localize("media/disallowedFileType"), NotificationStyle.Warning)); } @@ -235,12 +221,13 @@ namespace Umbraco.Web.Editors { //Default path string fileName = packageGuid + ".umb"; - if (File.Exists(Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Packages), fileName)) == false) + if (System.IO.File.Exists(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages), fileName)) == false) { - var packageFile = await Services.PackagingService.FetchPackageFileAsync( + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var packageFile = await _packagingService.FetchPackageFileAsync( Guid.Parse(packageGuid), _umbracoVersion.Current, - Security.GetUserId().ResultOr(0)); + umbracoContext.Security.GetUserId().ResultOr(0)); fileName = packageFile.Name; } @@ -254,12 +241,12 @@ namespace Umbraco.Web.Editors //Populate the model from the metadata in the package file (zip file) PopulateFromPackageData(model); - var installType = Services.PackagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); + var installType = _packagingService.GetPackageInstallType(model.Name, SemVersion.Parse(model.Version), out var alreadyInstalled); if (installType == PackageInstallType.AlreadyInstalled) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/packageAlreadyInstalled"))); + throw HttpResponseException.CreateNotificationValidationErrorResponse( + _localizedTextService.Localize("packager/packageAlreadyInstalled")); } model.OriginalVersion = installType == PackageInstallType.Upgrade ? alreadyInstalled.Version : null; @@ -275,20 +262,20 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallModel Import(PackageInstallModel model) { - var zipFile = new FileInfo(Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Packages), model.ZipFileName)); + var zipFile = new FileInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages), model.ZipFileName)); - var packageInfo = Services.PackagingService.GetCompiledPackageInfo(zipFile); + var packageInfo = _packagingService.GetCompiledPackageInfo(zipFile); //now we need to check for version comparison if (packageInfo.UmbracoVersionRequirementsType == RequirementsType.Strict) { var packageMinVersion = packageInfo.UmbracoVersion; if (_umbracoVersion.Current < packageMinVersion) - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()}))); + throw HttpResponseException.CreateNotificationValidationErrorResponse( + _localizedTextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()})); } - var installType = Services.PackagingService.GetPackageInstallType(packageInfo.Name, SemVersion.Parse(packageInfo.Version), out var alreadyInstalled); + var installType = _packagingService.GetPackageInstallType(packageInfo.Name, SemVersion.Parse(packageInfo.Version), out var alreadyInstalled); var packageDefinition = PackageDefinition.FromCompiledPackage(packageInfo); packageDefinition.PackagePath = zipFile.FullName; @@ -302,8 +289,8 @@ namespace Umbraco.Web.Editors case PackageInstallType.Upgrade: //save to the installedPackages.config, this will create a new entry with a new Id - if (!Services.PackagingService.SaveInstalledPackage(packageDefinition)) - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Could not save the package")); + if (!_packagingService.SaveInstalledPackage(packageDefinition)) + throw HttpResponseException.CreateNotificationValidationErrorResponse("Could not save the package"); model.Id = packageDefinition.Id; break; @@ -323,12 +310,12 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallModel InstallFiles(PackageInstallModel model) { - var definition = Services.PackagingService.GetInstalledPackageById(model.Id); + var definition = _packagingService.GetInstalledPackageById(model.Id); if (definition == null) throw new InvalidOperationException("Not package definition found with id " + model.Id); var zipFile = new FileInfo(definition.PackagePath); - - var installedFiles = Services.PackagingService.InstallCompiledPackageFiles(definition, zipFile, Security.GetUserId().ResultOr(0)); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var installedFiles = _packagingService.InstallCompiledPackageFiles(definition, zipFile, umbracoContext.Security.GetUserId().ResultOr(0)); //set a restarting marker and reset the app pool _umbracoApplicationLifetime.Restart(); @@ -356,12 +343,12 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallModel InstallData(PackageInstallModel model) { - var definition = Services.PackagingService.GetInstalledPackageById(model.Id); + var definition = _packagingService.GetInstalledPackageById(model.Id); if (definition == null) throw new InvalidOperationException("Not package definition found with id " + model.Id); var zipFile = new FileInfo(definition.PackagePath); - - var installSummary = Services.PackagingService.InstallCompiledPackageData(definition, zipFile, Security.GetUserId().ResultOr(0)); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var installSummary = _packagingService.InstallCompiledPackageData(definition, zipFile, umbracoContext.Security.GetUserId().ResultOr(0)); return model; } @@ -374,12 +361,12 @@ namespace Umbraco.Web.Editors [HttpPost] public PackageInstallResult CleanUp(PackageInstallModel model) { - var definition = Services.PackagingService.GetInstalledPackageById(model.Id); + var definition = _packagingService.GetInstalledPackageById(model.Id); if (definition == null) throw new InvalidOperationException("Not package definition found with id " + model.Id); var zipFile = new FileInfo(definition.PackagePath); - var packageInfo = Services.PackagingService.GetCompiledPackageInfo(zipFile); + var packageInfo = _packagingService.GetCompiledPackageInfo(zipFile); zipFile.Delete(); diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 404bf88c89..b63964972d 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -225,14 +225,14 @@ namespace Umbraco.Web.Editors "memberApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetByKey(Guid.Empty)) }, - { - "packageInstallApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.Fetch(string.Empty)) - }, - { - "packageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetCreatedPackages()) - }, + // { + // "packageInstallApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.Fetch(string.Empty)) + // }, + // { + // "packageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetCreatedPackages()) + // }, { "relationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(0)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e18b9be839..cd090a050e 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -158,7 +158,6 @@ - @@ -231,7 +230,6 @@ - @@ -251,7 +249,6 @@ - @@ -338,7 +335,6 @@ - @@ -352,7 +348,6 @@ - From 0108db553cbce9dadfbe348cc091cbee032affef Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 27 May 2020 13:31:18 +0200 Subject: [PATCH 43/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Removed OnlyLocalRequestsAttribute from Umbraco.Web --- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../Filters/OnlyLocalRequestsAttribute.cs | 20 ------------------- 2 files changed, 21 deletions(-) delete mode 100644 src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cd090a050e..34903a7dc5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -151,7 +151,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs deleted file mode 100644 index 710749b39f..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; - -namespace Umbraco.Web.WebApi.Filters -{ - // Migrated to .NET Core - public class OnlyLocalRequestsAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(HttpActionContext actionContext) - { - if (!actionContext.Request.IsLocal()) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - } - } -} From 00b7c2d13bf769d659f27e9df81f7436e71ec63e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 27 May 2020 14:14:32 +0200 Subject: [PATCH 44/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved PublishedSnapshotCacheStatusController --- .../PublishedSnapshotCacheStatusController.cs | 17 +++++++++++------ .../Editors/BackOfficeServerVariables.cs | 8 ++++---- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 15 insertions(+), 11 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/PublishedSnapshotCacheStatusController.cs (68%) diff --git a/src/Umbraco.Web/Editors/PublishedSnapshotCacheStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs similarity index 68% rename from src/Umbraco.Web/Editors/PublishedSnapshotCacheStatusController.cs rename to src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs index 6a701fdcfa..1a0e3457ca 100644 --- a/src/Umbraco.Web/Editors/PublishedSnapshotCacheStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs @@ -1,19 +1,24 @@ using System; -using System.Web.Http; +using System.Reflection.Metadata; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Web.Cache; -using Umbraco.Web.Composing; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.PublishedCache; -using Umbraco.Web.WebApi; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { + [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] + [IsBackOffice] public class PublishedSnapshotCacheStatusController : UmbracoAuthorizedApiController { private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly DistributedCache _distributedCache; - public PublishedSnapshotCacheStatusController(IPublishedSnapshotService publishedSnapshotService) + public PublishedSnapshotCacheStatusController(IPublishedSnapshotService publishedSnapshotService, DistributedCache distributedCache) { _publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService)); + _distributedCache = distributedCache; } [HttpPost] @@ -40,7 +45,7 @@ namespace Umbraco.Web.Editors [HttpPost] public void ReloadCache() { - Current.DistributedCache.RefreshAllPublishedSnapshot(); + _distributedCache.RefreshAllPublishedSnapshot(); } } } diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index b63964972d..2e5e078882 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -303,10 +303,10 @@ namespace Umbraco.Web.Editors "dictionaryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.DeleteById(int.MaxValue)) }, - { - "publishedSnapshotCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetStatus()) - }, + // { + // "publishedSnapshotCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetStatus()) + // }, //TODO Reintroduce // { // "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 34903a7dc5..e9ab9db390 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -297,7 +297,6 @@ - From f2ad7bb6315fa28f55c6f663bb1ecbf9af4f3ff6 Mon Sep 17 00:00:00 2001 From: Enkel Media Date: Wed, 27 May 2020 14:39:03 +0200 Subject: [PATCH 45/55] Fixes #8004 umbnodepreview setting contentType alias to null (#8109) --- .../components/property/umbproperty.directive.js | 13 +++++++++---- .../components/umbnodepreview.directive.js | 13 +++++++++---- .../src/views/components/property/umb-property.html | 2 +- .../src/views/components/umb-node-preview.html | 2 +- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 9c33b35e82..ad62bcd3db 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -16,10 +16,15 @@ angular.module("umbraco.directives") replace: true, templateUrl: 'views/components/property/umb-property.html', link: function (scope) { - userService.getCurrentUser().then(function (u) { - var isAdmin = u.userGroups.indexOf('admin') !== -1; - scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null; - }); + + scope.controlLabelTitle = null; + if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + userService.getCurrentUser().then(function (u) { + if(u.allowedSections.indexOf("settings") !== -1 ? true : false) { + scope.controlLabelTitle = scope.property.alias; + } + }); + } }, //Define a controller for this directive to expose APIs to other directives controller: function ($scope) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js index 9f1f7a0d2e..fd52c4d7ea 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js @@ -102,10 +102,15 @@ if (!scope.editLabelKey) { scope.editLabelKey = "general_edit"; } - userService.getCurrentUser().then(function (u) { - var isAdmin = u.userGroups.indexOf('admin') !== -1; - scope.alias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.alias : null; - }); + + scope.nodeNameTitle = null; + if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + userService.getCurrentUser().then(function (u) { + if (u.allowedSections.indexOf("settings") !== -1 ? true : false) { + scope.nodeNameTitle = scope.alias; + } + }); + } } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index ca57679f51..658b7c73ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -11,7 +11,7 @@ {{inheritsFrom}} -
-
{{ name }}
+
{{ name }}
{{ description }}
From 9df6a8bd6af17ecfb2aa2e0a7107798236703db8 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 27 May 2020 14:59:15 +0000 Subject: [PATCH 46/55] Bugfix: Adds new confirmation email marketing step (#8091) --- .../confirm/confirm.controller.js | 15 +++++++++++++++ .../tours/umbEmailMarketing/confirm/confirm.html | 15 +++++++++++++++ .../umbEmailMarketing/emails/emails.controller.js | 5 +---- .../config/BackOfficeTours/getting-started.json | 4 ++++ 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js new file mode 100644 index 0000000000..ef4f5ce1ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js @@ -0,0 +1,15 @@ +(function () { + "use strict"; + + function ConfirmController($scope, userService) { + + var vm = this; + vm.userEmailAddress = ""; + + userService.getCurrentUser().then(function(user){ + vm.userEmailAddress = user.email; + }); + } + + angular.module("umbraco").controller("Umbraco.Tours.UmbEmailMarketing.ConfirmController", ConfirmController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html new file mode 100644 index 0000000000..34acb194e3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html @@ -0,0 +1,15 @@ +
+ + + + +

We have sent a welcome email to your email address {{ vm.userEmailAddress }}

+
+ + +
+ +
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js index 8ecc737278..d3040a5c3f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js @@ -13,10 +13,7 @@ userService.addUserToEmailMarketing(user); }); - // Mark Tour as complete - // This is also can help us indicate that the user accepted - // Where disabled is set if user closes modal or chooses NO - $scope.model.completeTour(); + $scope.model.nextStep(); } } diff --git a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json index 7b3f2a2184..d0aa1a1c34 100644 --- a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json +++ b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json @@ -14,6 +14,10 @@ "content": "

Thank you for using Umbraco! Would you like to stay up-to-date with Umbraco product updates, security advisories, community news and special offers? Sign up for our newsletter and never miss out on the latest Umbraco news.

By signing up, you agree that we can use your info according to our privacy policy.

", "view": "emails", "type": "promotion" + }, + { + "title": "Thank you for subscribing to our mailing list", + "view": "confirm" } ] }, From 0dbf182d9717a62312dd0e5553217c31c34050b8 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 28 May 2020 07:12:53 +0200 Subject: [PATCH 47/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved PublishedStatusController amd RedirectUrlManagementController --- .../Controllers}/PublishedStatusController.cs | 5 +- .../RedirectUrlManagementController.cs | 69 +++++++++++-------- .../Editors/BackOfficeServerVariables.cs | 16 ++--- src/Umbraco.Web/Umbraco.Web.csproj | 2 - 4 files changed, 49 insertions(+), 43 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/PublishedStatusController.cs (91%) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/RedirectUrlManagementController.cs (56%) diff --git a/src/Umbraco.Web/Editors/PublishedStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedStatusController.cs similarity index 91% rename from src/Umbraco.Web/Editors/PublishedStatusController.cs rename to src/Umbraco.Web.BackOffice/Controllers/PublishedStatusController.cs index 5ed70c4811..f63c2d5e6a 100644 --- a/src/Umbraco.Web/Editors/PublishedStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedStatusController.cs @@ -1,9 +1,8 @@ using System; -using System.Web.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Web.PublishedCache; -using Umbraco.Web.WebApi; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { public class PublishedStatusController : UmbracoAuthorizedApiController { diff --git a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs similarity index 56% rename from src/Umbraco.Web/Editors/RedirectUrlManagementController.cs rename to src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs index d1bb3f873c..c749e85839 100644 --- a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RedirectUrlManagementController.cs @@ -1,30 +1,42 @@ using System; -using System.Web.Http; using System.Xml; -using System.Linq; using System.Security; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using File = System.IO.File; using Umbraco.Core; -using Umbraco.Web.Composing; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Hosting; +using Umbraco.Core.Mapping; +using Umbraco.Core.Services; +using Umbraco.Web.Common.Attributes; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { [PluginController("UmbracoApi")] public class RedirectUrlManagementController : UmbracoAuthorizedApiController { private readonly ILogger _logger; private readonly IWebRoutingSettings _webRoutingSettings; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IRedirectUrlService _redirectUrlService; + private readonly UmbracoMapper _umbracoMapper; + private readonly IHostingEnvironment _hostingEnvironment; - public RedirectUrlManagementController(ILogger logger, IWebRoutingSettings webRoutingSettings) + public RedirectUrlManagementController(ILogger logger, + IWebRoutingSettings webRoutingSettings, + IUmbracoContextAccessor umbracoContextAccessor, + IRedirectUrlService redirectUrlService, + UmbracoMapper umbracoMapper, + IHostingEnvironment hostingEnvironment) { - _logger = logger; - _webRoutingSettings = webRoutingSettings; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _redirectUrlService = redirectUrlService ?? throw new ArgumentNullException(nameof(redirectUrlService)); + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); } /// @@ -32,10 +44,11 @@ namespace Umbraco.Web.Editors /// /// [HttpGet] - public IHttpActionResult GetEnableState() + public IActionResult GetEnableState() { var enabled = _webRoutingSettings.DisableRedirectUrlTracking == false; - var userIsAdmin = UmbracoContext.Security.CurrentUser.IsAdmin(); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var userIsAdmin = umbracoContext.Security.CurrentUser.IsAdmin(); return Ok(new { enabled, userIsAdmin }); } @@ -44,14 +57,13 @@ namespace Umbraco.Web.Editors public RedirectUrlSearchResult SearchRedirectUrls(string searchTerm, int page = 0, int pageSize = 10) { var searchResult = new RedirectUrlSearchResult(); - var redirectUrlService = Services.RedirectUrlService; long resultCount; var redirects = string.IsNullOrWhiteSpace(searchTerm) - ? redirectUrlService.GetAllRedirectUrls(page, pageSize, out resultCount) - : redirectUrlService.SearchRedirectUrls(searchTerm, page, pageSize, out resultCount); + ? _redirectUrlService.GetAllRedirectUrls(page, pageSize, out resultCount) + : _redirectUrlService.SearchRedirectUrls(searchTerm, page, pageSize, out resultCount); - searchResult.SearchResults = Mapper.MapEnumerable(redirects); + searchResult.SearchResults = _umbracoMapper.MapEnumerable(redirects); searchResult.TotalCount = resultCount; searchResult.CurrentPage = page; searchResult.PageCount = ((int)resultCount + pageSize - 1) / pageSize; @@ -71,43 +83,40 @@ namespace Umbraco.Web.Editors var redirectsResult = new RedirectUrlSearchResult(); if (UdiParser.TryParse(contentUdi, out GuidUdi guidIdi)) { - var redirectUrlService = Services.RedirectUrlService; - var redirects = redirectUrlService.GetContentRedirectUrls(guidIdi.Guid); - var mapped = Mapper.MapEnumerable(redirects); + + var redirects = _redirectUrlService.GetContentRedirectUrls(guidIdi.Guid); + var mapped = _umbracoMapper.MapEnumerable(redirects); redirectsResult.SearchResults = mapped; //not doing paging 'yet' - redirectsResult.TotalCount = mapped.Count(); + redirectsResult.TotalCount = mapped.Count; redirectsResult.CurrentPage = 1; redirectsResult.PageCount = 1; } return redirectsResult; } [HttpPost] - public IHttpActionResult DeleteRedirectUrl(Guid id) + public IActionResult DeleteRedirectUrl(Guid id) { - var redirectUrlService = Services.RedirectUrlService; - redirectUrlService.Delete(id); + _redirectUrlService.Delete(id); return Ok(); } [HttpPost] - public IHttpActionResult ToggleUrlTracker(bool disable) + public IActionResult ToggleUrlTracker(bool disable) { - var userIsAdmin = UmbracoContext.Security.CurrentUser.IsAdmin(); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var userIsAdmin = umbracoContext.Security.CurrentUser.IsAdmin(); if (userIsAdmin == false) { var errorMessage = "User is not a member of the administrators group and so is not allowed to toggle the URL tracker"; _logger.Debug(errorMessage); throw new SecurityException(errorMessage); } - - var httpContext = TryGetHttpContext(); - if (httpContext.Success == false) throw new InvalidOperationException("Cannot acquire HttpContext"); - var configFilePath = httpContext.Result.Server.MapPath("~/config/umbracoSettings.config"); + var configFilePath =_hostingEnvironment.MapPathContentRoot("~/config/umbracoSettings.config"); var action = disable ? "disable" : "enable"; - if (File.Exists(configFilePath) == false) + if (System.IO.File.Exists(configFilePath) == false) return BadRequest($"Couldn't {action} URL Tracker, the umbracoSettings.config file does not exist."); var umbracoConfig = new XmlDocument { PreserveWhitespace = true }; diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 2e5e078882..46995fd7ba 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -137,10 +137,10 @@ namespace Umbraco.Web.Editors { "packagesRestApiBaseUrl", Constants.PackageRepository.RestApiBaseUrl }, - { - "redirectUrlManagementApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetEnableState()) - }, + // { + // "redirectUrlManagementApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetEnableState()) + // }, //TODO reintroduce // { // "tourApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( @@ -295,10 +295,10 @@ namespace Umbraco.Web.Editors // "codeFileApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetByPath("", "")) // }, - { - "publishedStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetPublishedStatusUrl()) - }, + // { + // "publishedStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetPublishedStatusUrl()) + // }, { "dictionaryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.DeleteById(int.MaxValue)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e9ab9db390..87c653e71f 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -296,7 +296,6 @@ - @@ -307,7 +306,6 @@ - From b67bff94a5f2b65e76d4eafa8839805beeb63d57 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 28 May 2020 07:20:06 +0200 Subject: [PATCH 48/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved RelationController --- .../Controllers/RelationController.cs | 75 +++++++++++++++++++ .../Editors/BackOfficeServerVariables.cs | 8 +- src/Umbraco.Web/Editors/RelationController.cs | 60 --------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 4 files changed, 79 insertions(+), 65 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Controllers/RelationController.cs delete mode 100644 src/Umbraco.Web/Editors/RelationController.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs new file mode 100644 index 0000000000..699c06f1d9 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; +using Umbraco.Core.Mapping; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.ActionsResults; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.BackOffice.Controllers +{ + [PluginController("UmbracoApi")] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content} })] + public class RelationController : UmbracoAuthorizedJsonController + { + private readonly UmbracoMapper _umbracoMapper; + private readonly IRelationService _relationService; + + public RelationController(UmbracoMapper umbracoMapper, + IRelationService relationService) + { + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); + _relationService = relationService ?? throw new ArgumentNullException(nameof(relationService)); + } + + public RelationDisplay GetById(int id) + { + return _umbracoMapper.Map(_relationService.GetById(id)); + } + + //[EnsureUserPermissionForContent("childId")] + public IEnumerable GetByChildId(int childId, string relationTypeAlias = "") + { + var relations = _relationService.GetByChildId(childId).ToArray(); + + if (relations.Any() == false) + { + return Enumerable.Empty(); + } + + if (string.IsNullOrWhiteSpace(relationTypeAlias) == false) + { + return + _umbracoMapper.MapEnumerable( + relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))); + } + + return _umbracoMapper.MapEnumerable(relations); + } + + [HttpDelete] + [HttpPost] + public IActionResult DeleteById(int id) + { + var foundRelation = _relationService.GetById(id); + + if (foundRelation == null) + { + return new UmbracoProblemResult("No relation found with the specified id", HttpStatusCode.NotFound); + } + + _relationService.Delete(foundRelation); + + return Ok(); + } + } +} diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 46995fd7ba..689fa39553 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -233,10 +233,10 @@ namespace Umbraco.Web.Editors // "packageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetCreatedPackages()) // }, - { - "relationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, + // { + // "relationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetById(0)) + // }, // { // "rteApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetConfiguration()) diff --git a/src/Umbraco.Web/Editors/RelationController.cs b/src/Umbraco.Web/Editors/RelationController.cs deleted file mode 100644 index b7f9baba55..0000000000 --- a/src/Umbraco.Web/Editors/RelationController.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Web.Http; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Editors -{ - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Constants.Applications.Content)] - public class RelationController : UmbracoAuthorizedJsonController - { - public RelationDisplay GetById(int id) - { - return Mapper.Map(Services.RelationService.GetById(id)); - } - - //[EnsureUserPermissionForContent("childId")] - public IEnumerable GetByChildId(int childId, string relationTypeAlias = "") - { - var relations = Services.RelationService.GetByChildId(childId).ToArray(); - - if (relations.Any() == false) - { - return Enumerable.Empty(); - } - - if (string.IsNullOrWhiteSpace(relationTypeAlias) == false) - { - return - Mapper.MapEnumerable( - relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))); - } - - return Mapper.MapEnumerable(relations); - } - - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundRelation = Services.RelationService.GetById(id); - - if (foundRelation == null) - { - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No relation found with the specified id"); - } - - Services.RelationService.Delete(foundRelation); - - return Request.CreateResponse(HttpStatusCode.OK); - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 87c653e71f..a5ed5fdde3 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -331,7 +331,6 @@ - From 49923cef5eedd525eee7a5535f1965168c1b4bbb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 28 May 2020 07:28:46 +0200 Subject: [PATCH 49/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved RelationTypeController --- .../Controllers}/RelationTypeController.cs | 89 +++++++++---------- .../Editors/BackOfficeServerVariables.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 48 insertions(+), 50 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/RelationTypeController.cs (61%) diff --git a/src/Umbraco.Web/Editors/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs similarity index 61% rename from src/Umbraco.Web/Editors/RelationTypeController.cs rename to src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index b5b983c9ff..a0e0c1cb94 100644 --- a/src/Umbraco.Web/Editors/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -1,50 +1,49 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; -using System.Web.Http; -using System.Linq; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Mapping; -using Umbraco.Web.Routing; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Editors; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { /// /// The API controller for editing relation types. /// [PluginController("UmbracoApi")] - [UmbracoTreeAuthorize(Constants.Trees.RelationTypes)] - [EnableOverrideAuthorization] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.RelationTypes} })] public class RelationTypeController : BackOfficeNotificationsController { + private readonly ILogger _logger; + private readonly UmbracoMapper _umbracoMapper; + private readonly IRelationService _relationService; + private readonly IShortStringHelper _shortStringHelper; + public RelationTypeController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, + ILogger logger, UmbracoMapper umbracoMapper, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) + IRelationService relationService, + IShortStringHelper shortStringHelper) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); + _relationService = relationService ?? throw new ArgumentNullException(nameof(relationService)); + _shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper)); } + /// /// Gets a relation type by ID. /// @@ -52,14 +51,14 @@ namespace Umbraco.Web.Editors /// Returns the . public RelationTypeDisplay GetById(int id) { - var relationType = Services.RelationService.GetRelationTypeById(id); + var relationType = _relationService.GetRelationTypeById(id); if (relationType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - var display = Mapper.Map(relationType); + var display = _umbracoMapper.Map(relationType); return display; } @@ -73,11 +72,11 @@ namespace Umbraco.Web.Editors } // Ordering do we need to pass through? - var relations = Services.RelationService.GetPagedByRelationTypeId(id, pageNumber -1, pageSize, out long totalRecords); + var relations = _relationService.GetPagedByRelationTypeId(id, pageNumber -1, pageSize, out long totalRecords); return new PagedResult(totalRecords, pageNumber, pageSize) { - Items = relations.Select(x => Mapper.Map(x)) + Items = relations.Select(x => _umbracoMapper.Map(x)) }; } @@ -109,20 +108,20 @@ namespace Umbraco.Web.Editors ///
/// The relation type to create. /// A containing the persisted relation type's ID. - public HttpResponseMessage PostCreate(RelationTypeSave relationType) + public IActionResult PostCreate(RelationTypeSave relationType) { - var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(ShortStringHelper, true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType); + var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(_shortStringHelper, true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType); try { - Services.RelationService.Save(relationTypePersisted); + _relationService.Save(relationTypePersisted); - return Request.CreateResponse(HttpStatusCode.OK, relationTypePersisted.Id); + return Ok(relationTypePersisted.Id); } catch (Exception ex) { - Logger.Error(GetType(), ex, "Error creating relation type with {Name}", relationType.Name); - return Request.CreateNotificationValidationErrorResponse("Error creating relation type."); + _logger.Error(GetType(), ex, "Error creating relation type with {Name}", relationType.Name); + throw HttpResponseException.CreateNotificationValidationErrorResponse("Error creating relation type."); } } @@ -133,27 +132,27 @@ namespace Umbraco.Web.Editors /// A display object containing the updated relation type. public RelationTypeDisplay PostSave(RelationTypeSave relationType) { - var relationTypePersisted = Services.RelationService.GetRelationTypeById(relationType.Key); + var relationTypePersisted = _relationService.GetRelationTypeById(relationType.Key); if (relationTypePersisted == null) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Relation type does not exist")); + throw HttpResponseException.CreateNotificationValidationErrorResponse("Relation type does not exist"); } - Mapper.Map(relationType, relationTypePersisted); + _umbracoMapper.Map(relationType, relationTypePersisted); try { - Services.RelationService.Save(relationTypePersisted); - var display = Mapper.Map(relationTypePersisted); + _relationService.Save(relationTypePersisted); + var display = _umbracoMapper.Map(relationTypePersisted); display.AddSuccessNotification("Relation type saved", ""); return display; } catch (Exception ex) { - Logger.Error(GetType(), ex, "Error saving relation type with {Id}", relationType.Id); - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Something went wrong when saving the relation type")); + _logger.Error(GetType(), ex, "Error saving relation type with {Id}", relationType.Id); + throw HttpResponseException.CreateNotificationValidationErrorResponse("Something went wrong when saving the relation type"); } } @@ -164,16 +163,16 @@ namespace Umbraco.Web.Editors /// A . [HttpPost] [HttpDelete] - public HttpResponseMessage DeleteById(int id) + public IActionResult DeleteById(int id) { - var relationType = Services.RelationService.GetRelationTypeById(id); + var relationType = _relationService.GetRelationTypeById(id); - if(relationType == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + if (relationType == null) + return NotFound(); - Services.RelationService.Delete(relationType); + _relationService.Delete(relationType); - return Request.CreateResponse(HttpStatusCode.OK); + return Ok(); } } } diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 689fa39553..5ecb37bc48 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -321,10 +321,10 @@ namespace Umbraco.Web.Editors // "languageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetAllLanguages()) // }, - { - "relationTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(1)) - }, + // { + // "relationTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetById(1)) + // }, // { // "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetNumberOfErrors(null, null)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a5ed5fdde3..9f917ca753 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -158,7 +158,6 @@ - From 956252f82da46adfd96f5edf7d3b2a39a6bb55f2 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 28 May 2020 07:50:56 +0200 Subject: [PATCH 50/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved StylesheetController --- .../Controllers}/StylesheetController.cs | 19 ++++++++++++++----- .../Editors/BackOfficeServerVariables.cs | 8 ++++---- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 18 insertions(+), 10 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/StylesheetController.cs (66%) diff --git a/src/Umbraco.Web/Editors/StylesheetController.cs b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs similarity index 66% rename from src/Umbraco.Web/Editors/StylesheetController.cs rename to src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs index 9785644d3f..852bff28c1 100644 --- a/src/Umbraco.Web/Editors/StylesheetController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/StylesheetController.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Services; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { /// /// The API controller used for retrieving available stylesheets @@ -12,9 +14,16 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class StylesheetController : UmbracoAuthorizedJsonController { + private readonly IFileService _fileService; + + public StylesheetController(IFileService fileService) + { + _fileService = fileService; + } + public IEnumerable GetAll() { - return Services.FileService.GetStylesheets() + return _fileService.GetStylesheets() .Select(x => new Stylesheet() { Name = x.Alias, @@ -24,7 +33,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetRulesByName(string name) { - var css = Services.FileService.GetStylesheetByName(name.EnsureEndsWith(".css")); + var css = _fileService.GetStylesheetByName(name.EnsureEndsWith(".css")); if (css == null) return Enumerable.Empty(); @@ -32,4 +41,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 5ecb37bc48..05aa602533 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -241,10 +241,10 @@ namespace Umbraco.Web.Editors // "rteApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetConfiguration()) // }, - { - "stylesheetApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAll()) - }, + // { + // "stylesheetApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetAll()) + // }, { "memberTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetAllTypes()) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9f917ca753..e0b901712e 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -345,7 +345,6 @@ - From 1034e980b5aa5a75d295a668050c4a9e76db12dd Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 28 May 2020 07:59:36 +0200 Subject: [PATCH 51/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Moved TemplateController --- .../Controllers}/TemplateController.cs | 85 +++++++++---------- .../Editors/BackOfficeServerVariables.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 43 insertions(+), 51 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/TemplateController.cs (67%) diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs similarity index 67% rename from src/Umbraco.Web/Editors/TemplateController.cs rename to src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index a02536e2bb..dd51126dc5 100644 --- a/src/Umbraco.Web/Editors/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -2,43 +2,36 @@ using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Http; -using System.Web.Http; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.IO; -using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; -using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { [PluginController("UmbracoApi")] - [UmbracoTreeAuthorize(Constants.Trees.Templates)] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.Templates} })] public class TemplateController : BackOfficeNotificationsController { + private readonly IFileService _fileService; + private readonly UmbracoMapper _umbracoMapper; + private readonly IShortStringHelper _shortStringHelper; + public TemplateController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, + IFileService fileService, UmbracoMapper umbracoMapper, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) + IShortStringHelper shortStringHelper) { + _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); + _shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper)); } /// @@ -48,8 +41,8 @@ namespace Umbraco.Web.Editors /// public TemplateDisplay GetByAlias(string alias) { - var template = Services.FileService.GetTemplate(alias); - return template == null ? null : Mapper.Map(template); + var template = _fileService.GetTemplate(alias); + return template == null ? null : _umbracoMapper.Map(template); } /// @@ -58,7 +51,7 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetAll() { - return Services.FileService.GetTemplates().Select(Mapper.Map); + return _fileService.GetTemplates().Select(_umbracoMapper.Map); } /// @@ -68,11 +61,11 @@ namespace Umbraco.Web.Editors /// public TemplateDisplay GetById(int id) { - var template = Services.FileService.GetTemplate(id); + var template = _fileService.GetTemplate(id); if (template == null) throw new HttpResponseException(HttpStatusCode.NotFound); - return Mapper.Map(template); + return _umbracoMapper.Map(template); } /// @@ -82,25 +75,25 @@ namespace Umbraco.Web.Editors /// [HttpDelete] [HttpPost] - public HttpResponseMessage DeleteById(int id) + public IActionResult DeleteById(int id) { - var template = Services.FileService.GetTemplate(id); + var template = _fileService.GetTemplate(id); if (template == null) throw new HttpResponseException(HttpStatusCode.NotFound); - Services.FileService.DeleteTemplate(template.Alias); - return Request.CreateResponse(HttpStatusCode.OK); + _fileService.DeleteTemplate(template.Alias); + return Ok(); } public TemplateDisplay GetScaffold(int id) { //empty default - var dt = new Template(ShortStringHelper, string.Empty, string.Empty); + var dt = new Template(_shortStringHelper, string.Empty, string.Empty); dt.Path = "-1"; if (id > 0) { - var master = Services.FileService.GetTemplate(id); + var master = _fileService.GetTemplate(id); if(master != null) { dt.SetMasterTemplate(master); @@ -108,7 +101,7 @@ namespace Umbraco.Web.Editors } var content = ViewHelper.GetDefaultFileContent( layoutPageAlias: dt.MasterTemplateAlias ); - var scaffold = Mapper.Map(dt); + var scaffold = _umbracoMapper.Map(dt); scaffold.Content = content + "\r\n\r\n@* the fun starts here *@\r\n\r\n"; return scaffold; @@ -119,33 +112,33 @@ namespace Umbraco.Web.Editors /// /// /// - public TemplateDisplay PostSave(TemplateDisplay display) + public ActionResult PostSave(TemplateDisplay display) { //Checking the submitted is valid with the Required attributes decorated on the ViewModel if (ModelState.IsValid == false) { - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + return ValidationProblem(ModelState); } if (display.Id > 0) { // update - var template = Services.FileService.GetTemplate(display.Id); + var template = _fileService.GetTemplate(display.Id); if (template == null) throw new HttpResponseException(HttpStatusCode.NotFound); var changeMaster = template.MasterTemplateAlias != display.MasterTemplateAlias; var changeAlias = template.Alias != display.Alias; - Mapper.Map(display, template); + _umbracoMapper.Map(display, template); if (changeMaster) { if (string.IsNullOrEmpty(display.MasterTemplateAlias) == false) { - var master = Services.FileService.GetTemplate(display.MasterTemplateAlias); + var master = _fileService.GetTemplate(display.MasterTemplateAlias); if(master == null || master.Id == display.Id) { template.SetMasterTemplate(null); @@ -154,7 +147,7 @@ namespace Umbraco.Web.Editors template.SetMasterTemplate(master); //After updating the master - ensure we update the path property if it has any children already assigned - var templateHasChildren = Services.FileService.GetTemplateDescendants(display.Id); + var templateHasChildren = _fileService.GetTemplateDescendants(display.Id); foreach (var childTemplate in templateHasChildren) { @@ -177,7 +170,7 @@ namespace Umbraco.Web.Editors childTemplate.Path = master.Path + "," + display.Id + "," + childTemplatePath; //Save the children with the updated path - Services.FileService.SaveTemplate(childTemplate); + _fileService.SaveTemplate(childTemplate); } } } @@ -188,14 +181,14 @@ namespace Umbraco.Web.Editors } } - Services.FileService.SaveTemplate(template); + _fileService.SaveTemplate(template); if (changeAlias) { - template = Services.FileService.GetTemplate(template.Id); + template = _fileService.GetTemplate(template.Id); } - Mapper.Map(template, display); + _umbracoMapper.Map(template, display); } else { @@ -203,15 +196,15 @@ namespace Umbraco.Web.Editors ITemplate master = null; if (string.IsNullOrEmpty(display.MasterTemplateAlias) == false) { - master = Services.FileService.GetTemplate(display.MasterTemplateAlias); + master = _fileService.GetTemplate(display.MasterTemplateAlias); if (master == null) throw new HttpResponseException(HttpStatusCode.NotFound); } // we need to pass the template name as alias to keep the template file casing consistent with templates created with content // - see comment in FileService.CreateTemplateForContentType for additional details - var template = Services.FileService.CreateTemplateWithIdentity(display.Name, display.Name, display.Content, master); - Mapper.Map(template, display); + var template = _fileService.CreateTemplateWithIdentity(display.Name, display.Name, display.Content, master); + _umbracoMapper.Map(template, display); } return display; diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 05aa602533..7fc113661b 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -257,10 +257,10 @@ namespace Umbraco.Web.Editors "updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetCheck()) }, - { - "templateApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, + // { + // "templateApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetById(0)) + // }, { "memberTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetNodes("-1", null)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e0b901712e..3f1a5509f5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -249,7 +249,6 @@ - From c3606b31ce8cc487dde756ce76201e659e7e720c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 28 May 2020 15:20:02 +0200 Subject: [PATCH 52/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Clean up and fixed issue with Ambiguous actions --- .../Controllers/CodeFileController.cs | 79 +++++++++---------- .../Controllers/DataTypeController.cs | 14 ++-- ...rmineAmbiguousActionByPassingParameters.cs | 51 ++++++++++++ .../Controllers/LanguageController.cs | 2 + .../Controllers/RelationTypeController.cs | 4 +- ...alidateAngularAntiForgeryTokenAttribute.cs | 1 - ...racoApiBehaviorApplicationModelProvider.cs | 2 + 7 files changed, 103 insertions(+), 50 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Controllers/DetermineAmbiguousActionByPassingParameters.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 63a7330c29..05c0513bed 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -24,7 +24,7 @@ using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; -namespace Umbraco.Web.BackOffice.Controllers + namespace Umbraco.Web.BackOffice.Controllers { // TODO: Put some exception filters in our webapi to return 404 instead of 500 when we throw ArgumentNullException // ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/ @@ -70,7 +70,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Will return a simple 200 if file creation succeeds [ValidationFilter] - public IActionResult PostCreate(string type, CodeFileDisplay display) + public ActionResult PostCreate(string type, CodeFileDisplay display) { if (display == null) throw new ArgumentNullException("display"); if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); @@ -108,7 +108,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// The name of the container/folder /// [HttpPost] - public IActionResult PostCreateContainer(string type, string parentId, string name) + public ActionResult PostCreateContainer(string type, string parentId, string name) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId"); @@ -154,11 +154,11 @@ namespace Umbraco.Web.BackOffice.Controllers } - return Ok(new CodeFileDisplay + return new CodeFileDisplay { VirtualPath = virtualPath, Path = Url.GetTreePathFromFilePath(virtualPath) - }); + }; } /// @@ -167,7 +167,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets' /// The filename or urlencoded path of the file to open /// The file and its contents from the virtualPath - public IActionResult GetByPath(string type, string virtualPath) + public ActionResult GetByPath(string type, string virtualPath) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath"); @@ -176,49 +176,49 @@ namespace Umbraco.Web.BackOffice.Controllers switch (type) { - case Core.Constants.Trees.PartialViews: + case Constants.Trees.PartialViews: var view = _fileService.GetPartialView(virtualPath); if (view != null) { var display = _umbracoMapper.Map(view); - display.FileType = Core.Constants.Trees.PartialViews; + display.FileType = Constants.Trees.PartialViews; display.Path = Url.GetTreePathFromFilePath(view.Path); display.Id = System.Web.HttpUtility.UrlEncode(view.Path); - return Ok(display); + return display; } break; - case Core.Constants.Trees.PartialViewMacros: + case Constants.Trees.PartialViewMacros: var viewMacro = _fileService.GetPartialViewMacro(virtualPath); if (viewMacro != null) { var display = _umbracoMapper.Map(viewMacro); - display.FileType = Core.Constants.Trees.PartialViewMacros; + display.FileType = Constants.Trees.PartialViewMacros; display.Path = Url.GetTreePathFromFilePath(viewMacro.Path); display.Id = System.Web.HttpUtility.UrlEncode(viewMacro.Path); - return Ok(display); + return display; } break; - case Core.Constants.Trees.Scripts: + case Constants.Trees.Scripts: var script = _fileService.GetScriptByName(virtualPath); if (script != null) { var display = _umbracoMapper.Map(script); - display.FileType = Core.Constants.Trees.Scripts; + display.FileType = Constants.Trees.Scripts; display.Path = Url.GetTreePathFromFilePath(script.Path); display.Id = System.Web.HttpUtility.UrlEncode(script.Path); - return Ok(display); + return display; } break; - case Core.Constants.Trees.Stylesheets: + case Constants.Trees.Stylesheets: var stylesheet = _fileService.GetStylesheetByName(virtualPath); if (stylesheet != null) { var display = _umbracoMapper.Map(stylesheet); - display.FileType = Core.Constants.Trees.Stylesheets; + display.FileType = Constants.Trees.Stylesheets; display.Path = Url.GetTreePathFromFilePath(stylesheet.Path); display.Id = System.Web.HttpUtility.UrlEncode(stylesheet.Path); - return Ok(display); + return display; } break; } @@ -263,12 +263,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - public IActionResult GetScaffold(string type, string id, string snippetName = null) + public ActionResult GetScaffold(string type, string id, string snippetName = null) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id"); - CodeFileDisplay codeFileDisplay; + CodeFileDisplay codeFileDisplay; switch (type) { @@ -308,7 +308,7 @@ namespace Umbraco.Web.BackOffice.Controllers codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~"); codeFileDisplay.FileType = type; - return new OkObjectResult(codeFileDisplay); + return codeFileDisplay; } /// @@ -328,8 +328,8 @@ namespace Umbraco.Web.BackOffice.Controllers var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; switch (type) { - case Core.Constants.Trees.PartialViews: - if (IsDirectory(virtualPath, Core.Constants.SystemDirectories.PartialViews)) + case Constants.Trees.PartialViews: + if (IsDirectory(virtualPath, Constants.SystemDirectories.PartialViews)) { _fileService.DeletePartialViewFolder(virtualPath); return Ok(); @@ -340,8 +340,8 @@ namespace Umbraco.Web.BackOffice.Controllers } return new UmbracoProblemResult("No Partial View or folder found with the specified path", HttpStatusCode.NotFound); - case Core.Constants.Trees.PartialViewMacros: - if (IsDirectory(virtualPath, Core.Constants.SystemDirectories.MacroPartials)) + case Constants.Trees.PartialViewMacros: + if (IsDirectory(virtualPath, Constants.SystemDirectories.MacroPartials)) { _fileService.DeletePartialViewMacroFolder(virtualPath); return Ok(); @@ -352,7 +352,7 @@ namespace Umbraco.Web.BackOffice.Controllers } return new UmbracoProblemResult("No Partial View Macro or folder found with the specified path", HttpStatusCode.NotFound); - case Core.Constants.Trees.Scripts: + case Constants.Trees.Scripts: if (IsDirectory(virtualPath, _globalSettings.UmbracoScriptsPath)) { _fileService.DeleteScriptFolder(virtualPath); @@ -364,7 +364,7 @@ namespace Umbraco.Web.BackOffice.Controllers return Ok(); } return new UmbracoProblemResult("No Script or folder found with the specified path", HttpStatusCode.NotFound); - case Core.Constants.Trees.Stylesheets: + case Constants.Trees.Stylesheets: if (IsDirectory(virtualPath, _globalSettings.UmbracoCssPath)) { _fileService.DeleteStyleSheetFolder(virtualPath); @@ -379,8 +379,6 @@ namespace Umbraco.Web.BackOffice.Controllers default: return NotFound(); } - - throw new HttpResponseException(HttpStatusCode.NotFound); } /// @@ -388,10 +386,11 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// The updated CodeFileDisplay model - public IActionResult PostSave(CodeFileDisplay display) + public ActionResult PostSave(CodeFileDisplay display) { if (display == null) throw new ArgumentNullException("display"); + TryValidateModel(display); if (ModelState.IsValid == false) { return ValidationProblem(ModelState); @@ -399,14 +398,14 @@ namespace Umbraco.Web.BackOffice.Controllers switch (display.FileType) { - case Core.Constants.Trees.PartialViews: + case Constants.Trees.PartialViews: var partialViewResult = CreateOrUpdatePartialView(display); if (partialViewResult.Success) { display = _umbracoMapper.Map(partialViewResult.Result, display); display.Path = Url.GetTreePathFromFilePath(partialViewResult.Result.Path); display.Id = System.Web.HttpUtility.UrlEncode(partialViewResult.Result.Path); - return Ok(display); + return display; } display.AddErrorNotification( @@ -414,14 +413,14 @@ namespace Umbraco.Web.BackOffice.Controllers _localizedTextService.Localize("speechBubbles/partialViewErrorText")); break; - case Core.Constants.Trees.PartialViewMacros: + case Constants.Trees.PartialViewMacros: var partialViewMacroResult = CreateOrUpdatePartialViewMacro(display); if (partialViewMacroResult.Success) { display = _umbracoMapper.Map(partialViewMacroResult.Result, display); display.Path = Url.GetTreePathFromFilePath(partialViewMacroResult.Result.Path); display.Id = System.Web.HttpUtility.UrlEncode(partialViewMacroResult.Result.Path); - return Ok(display); + return display; } display.AddErrorNotification( @@ -429,31 +428,31 @@ namespace Umbraco.Web.BackOffice.Controllers _localizedTextService.Localize("speechBubbles/partialViewErrorText")); break; - case Core.Constants.Trees.Scripts: + case Constants.Trees.Scripts: var scriptResult = CreateOrUpdateScript(display); display = _umbracoMapper.Map(scriptResult, display); display.Path = Url.GetTreePathFromFilePath(scriptResult.Path); display.Id = System.Web.HttpUtility.UrlEncode(scriptResult.Path); - return Ok(display); + return display; //display.AddErrorNotification( // _localizedTextService.Localize("speechBubbles/partialViewErrorHeader"), // _localizedTextService.Localize("speechBubbles/partialViewErrorText")); - case Core.Constants.Trees.Stylesheets: + case Constants.Trees.Stylesheets: var stylesheetResult = CreateOrUpdateStylesheet(display); display = _umbracoMapper.Map(stylesheetResult, display); display.Path = Url.GetTreePathFromFilePath(stylesheetResult.Path); display.Id = System.Web.HttpUtility.UrlEncode(stylesheetResult.Path); - return Ok(display); + return display; default: - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } - return Ok(display); + return display; } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 8cc5d884bd..bdd1435b34 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -8,8 +8,6 @@ using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.WebApi.Filters; -using System.Net.Http; using System.Text; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Mapping; @@ -18,10 +16,10 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Editors; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { - /// /// The API controller used for editing data types /// @@ -68,7 +66,6 @@ namespace Umbraco.Web.Editors _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); } - /// /// Gets data type by name /// @@ -85,6 +82,7 @@ namespace Umbraco.Web.Editors /// /// /// + [DetermineAmbiguousActionByPassingParameters] public DataTypeDisplay GetById(int id) { var dataType = _dataTypeService.GetDataType(id); @@ -100,6 +98,7 @@ namespace Umbraco.Web.Editors /// /// /// + [DetermineAmbiguousActionByPassingParameters] public DataTypeDisplay GetById(Guid id) { var dataType = _dataTypeService.GetDataType(id); @@ -115,6 +114,7 @@ namespace Umbraco.Web.Editors /// /// /// + [DetermineAmbiguousActionByPassingParameters] public DataTypeDisplay GetById(Udi id) { var guidUdi = id as GuidUdi; @@ -265,7 +265,7 @@ namespace Umbraco.Web.Editors /// /// [TypeFilter(typeof(DataTypeValidateAttribute))] - public IActionResult PostSave(DataTypeSave dataType) + public ActionResult PostSave(DataTypeSave dataType) { //If we've made it here, then everything has been wired up and validated by the attribute @@ -297,7 +297,7 @@ namespace Umbraco.Web.Editors // map back to display model, and return var display = _umbracoMapper.Map(dataType.PersistedDataType); display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/dataTypeSaved"), ""); - return Ok(display); + return display; } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/DetermineAmbiguousActionByPassingParameters.cs b/src/Umbraco.Web.BackOffice/Controllers/DetermineAmbiguousActionByPassingParameters.cs new file mode 100644 index 0000000000..a1f7782ea6 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/DetermineAmbiguousActionByPassingParameters.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Routing; +using Umbraco.Core; + +namespace Umbraco.Web.BackOffice.Controllers +{ + public class DetermineAmbiguousActionByPassingParameters : ActionMethodSelectorAttribute + { + public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action) + { + var parameters = action.Parameters; + if (parameters.Any()) + { + var canUse = true; + foreach (var parameterDescriptor in parameters) + { + var value = routeContext.HttpContext.Request.Query[parameterDescriptor.Name]; + + if (parameterDescriptor.ParameterType == typeof(Udi)) + { + canUse &= UdiParser.TryParse(value, out _); + } + else if (parameterDescriptor.ParameterType == typeof(int)) + { + canUse &= int.TryParse(value, out _); + } + else if (parameterDescriptor.ParameterType == typeof(Guid)) + { + canUse &= Guid.TryParse(value, out _); + } + else if (parameterDescriptor.ParameterType == typeof(string)) + { + canUse = true; + } + else + { + canUse = false; + } + } + + return canUse; + } + + + return true; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 8038e7841a..91a99c9182 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.BackOffice.Validation; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; @@ -20,6 +21,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// Backoffice controller supporting the dashboard for language administration. /// [PluginController("UmbracoApi")] + //[PrefixlessBodyModelValidator] public class LanguageController : UmbracoAuthorizedJsonController { private readonly ILocalizationService _localizationService; diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index a0e0c1cb94..fe43944779 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -108,7 +108,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The relation type to create. /// A containing the persisted relation type's ID. - public IActionResult PostCreate(RelationTypeSave relationType) + public int PostCreate(RelationTypeSave relationType) { var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(_shortStringHelper, true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType); @@ -116,7 +116,7 @@ namespace Umbraco.Web.BackOffice.Controllers { _relationService.Save(relationTypePersisted); - return Ok(relationTypePersisted.Id); + return relationTypePersisted.Id; } catch (Exception ex) { diff --git a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs index d2e22dc623..b8f035077b 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs @@ -109,7 +109,6 @@ namespace Umbraco.Web.BackOffice.Filters _logger.Error(ex, "Could not validate XSRF token"); return false; } - return true; } } } diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs index e332c89536..bbd9d5207a 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs @@ -69,6 +69,8 @@ namespace Umbraco.Web.Common.ApplicationModels if (!IsUmbracoApiController(controller)) continue; + + foreach (var action in controller.Actions) { foreach (var convention in ActionModelConventions) From a7a9b8204ee34a1fff6cc631fa941866432850f4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 28 May 2020 20:01:18 +0200 Subject: [PATCH 53/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Build fix --- src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 91a99c9182..a9f448d575 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -9,7 +9,6 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Filters; -using Umbraco.Web.BackOffice.Validation; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; From 878e5056b59d402a54b697aea48571ce8f69216c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 29 May 2020 08:05:45 +0200 Subject: [PATCH 54/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Cleanup when using typefilters --- .../Controllers/CodeFileController.cs | 2 +- .../Controllers/DataTypeController.cs | 18 ++--- .../Controllers/LanguageController.cs | 4 +- .../Controllers/LogController.cs | 2 +- .../Controllers/PackageController.cs | 2 +- .../Controllers/PackageInstallController.cs | 2 +- .../Controllers/RelationController.cs | 2 +- .../Controllers/RelationTypeController.cs | 2 +- .../Controllers/TemplateController.cs | 2 +- .../UmbracoApplicationAuthorizeAttribute.cs | 80 +++++++++++-------- .../Filters/UmbracoTreeAuthorizeAttribute.cs | 20 ++++- .../HealthCheck/HealthCheckController.cs | 2 +- .../Profiling/WebProfilingController.cs | 4 +- .../PrefixlessBodyModelValidator.cs | 45 +++++++++++ 14 files changed, 129 insertions(+), 58 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 05c0513bed..2d0ffc5d33 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -30,7 +30,7 @@ using Umbraco.Web.Editors; // ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/ [PluginController("UmbracoApi")] //[PrefixlessBodyModelValidator] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)] public class CodeFileController : BackOfficeNotificationsController { private readonly IIOHelper _ioHelper; diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index bdd1435b34..0d66a5e329 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// Content Types, Member Types or Media Types ... and of course to Data Types /// [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes}})] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)] public class DataTypeController : BackOfficeNotificationsController { private readonly PropertyEditorCollection _propertyEditors; @@ -408,8 +408,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IEnumerable GetAll() { return _dataTypeService @@ -424,8 +424,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoTreeAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IDictionary> GetGroupedDataTypes() { var dataTypes = _dataTypeService @@ -456,8 +456,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoTreeAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IDictionary> GetGroupedPropertyEditors() { @@ -489,8 +489,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoTreeAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IEnumerable GetAllPropertyEditors() { diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index a9f448d575..03e4ad163d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -78,7 +78,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Deletes a language with a given ID /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{ Constants.Trees.Languages} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.Languages)] [HttpDelete] [HttpPost] public IActionResult DeleteLanguage(int id) @@ -107,7 +107,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Creates or saves a language /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{ Constants.Trees.Languages} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.Languages)] [HttpPost] public Language SaveLanguage(Language language) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/LogController.cs b/src/Umbraco.Web.BackOffice/Controllers/LogController.cs index 76f7f35838..9bc2be8a39 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LogController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LogController.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web.BackOffice.Controllers _sqlContext = sqlContext ?? throw new ArgumentNullException(nameof(sqlContext)); } - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{ Constants.Applications.Content, Constants.Applications.Media } })] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media)] public PagedResult GetPagedEntityLog(int id, int pageNumber = 1, int pageSize = 10, diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 220d67f794..23ba2e5771 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// A controller used for managing packages in the back office /// [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Packages}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)] public class PackageController : UmbracoAuthorizedJsonController { private readonly IHostingEnvironment _hostingEnvironment; diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs index 9f86d3e5cc..5330d4466f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// A controller used for installing packages and managing all of the data in the packages section in the back office /// [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Packages} })] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)] public class PackageInstallController : UmbracoAuthorizedJsonController { diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs index 699c06f1d9..686afe284b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs @@ -18,7 +18,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content} })] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] public class RelationController : UmbracoAuthorizedJsonController { private readonly UmbracoMapper _umbracoMapper; diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index fe43944779..f679dd6b8e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// The API controller for editing relation types. /// [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.RelationTypes} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.RelationTypes)] public class RelationTypeController : BackOfficeNotificationsController { private readonly ILogger _logger; diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index dd51126dc5..6eae71a27e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -17,7 +17,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.Templates} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.Templates)] public class TemplateController : BackOfficeNotificationsController { private readonly IFileService _fileService; diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs index 2a9f88c0be..81e61af5bf 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs @@ -7,50 +7,62 @@ using Umbraco.Core; namespace Umbraco.Web.BackOffice.Filters { - public class UmbracoApplicationAuthorizeAttribute : Attribute, IAuthorizationFilter + public class UmbracoApplicationAuthorizeAttribute : TypeFilterAttribute { - /// - /// Can be used by unit tests to enable/disable this filter - /// - internal static bool Enable = true; - - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly string[] _appNames; - - /// - /// Constructor to set any number of applications that the user needs access to be authorized - /// - /// - /// If the user has access to any of the specified apps, they will be authorized. - /// - public UmbracoApplicationAuthorizeAttribute(IUmbracoContextAccessor umbracoContextAccessor, params string[] appName) + public UmbracoApplicationAuthorizeAttribute(params string[] appName) : base(typeof(UmbracoApplicationAuthorizeFilter)) { - _umbracoContextAccessor = umbracoContextAccessor; - _appNames = appName; + base.Arguments = new object[] + { + appName + }; } - - public void OnAuthorization(AuthorizationFilterContext context) + private class UmbracoApplicationAuthorizeFilter : IAuthorizationFilter { - if (!IsAuthorized()) - { - context.Result = new ForbidResult(); - } - } + /// + /// Can be used by unit tests to enable/disable this filter + /// + internal static bool Enable = true; - private bool IsAuthorized() - { - if (Enable == false) + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly string[] _appNames; + + /// + /// Constructor to set any number of applications that the user needs access to be authorized + /// + /// + /// If the user has access to any of the specified apps, they will be authorized. + /// + public UmbracoApplicationAuthorizeFilter(IUmbracoContextAccessor umbracoContextAccessor, params string[] appName) { - return true; + _umbracoContextAccessor = umbracoContextAccessor; + _appNames = appName; } - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - var authorized = umbracoContext.Security.CurrentUser != null - && _appNames.Any(app => umbracoContext.Security.UserHasSectionAccess( - app, umbracoContext.Security.CurrentUser)); - return authorized; + public void OnAuthorization(AuthorizationFilterContext context) + { + if (!IsAuthorized()) + { + context.Result = new ForbidResult(); + } + } + + private bool IsAuthorized() + { + if (Enable == false) + { + return true; + } + + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var authorized = umbracoContext.Security.CurrentUser != null + && _appNames.Any(app => umbracoContext.Security.UserHasSectionAccess( + app, umbracoContext.Security.CurrentUser)); + + return authorized; + } } } + } diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs index 9a8239b637..6db37d16f6 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -6,13 +6,24 @@ using Umbraco.Web.Services; namespace Umbraco.Web.BackOffice.Filters { - /// + + public class UmbracoTreeAuthorizeAttribute : TypeFilterAttribute + { + public UmbracoTreeAuthorizeAttribute(params string[] treeAliases) : base(typeof(UmbracoTreeAuthorizeFilter)) + { + base.Arguments = new object[] + { + treeAliases + }; + } + + /// /// Ensures that the current user has access to the application for which the specified tree(s) belongs /// /// /// This would allow a tree to be moved between sections /// - public sealed class UmbracoTreeAuthorizeAttribute : IAuthorizationFilter + private sealed class UmbracoTreeAuthorizeFilter : IAuthorizationFilter { /// /// Can be used by unit tests to enable/disable this filter @@ -32,7 +43,7 @@ namespace Umbraco.Web.BackOffice.Filters /// Multiple trees may be specified. /// /// - public UmbracoTreeAuthorizeAttribute(ITreeService treeService, IUmbracoContextAccessor umbracoContextAccessor, params string[] treeAliases) + public UmbracoTreeAuthorizeFilter(ITreeService treeService, IUmbracoContextAccessor umbracoContextAccessor, params string[] treeAliases) { _treeService = treeService; _umbracoContextAccessor = umbracoContextAccessor; @@ -68,4 +79,7 @@ namespace Umbraco.Web.BackOffice.Filters } } } + } + + } diff --git a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs index 00de2db0b6..131a4ac62c 100644 --- a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.HealthCheck /// /// The API controller used to display the health check info and execute any actions /// - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)] public class HealthCheckController : UmbracoAuthorizedJsonController { private readonly HealthCheckCollection _checks; diff --git a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs index ba55dcb51c..b6cdf75f6f 100644 --- a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs +++ b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs @@ -10,8 +10,8 @@ namespace Umbraco.Web.BackOffice.Profiling /// /// The API controller used to display the state of the web profiler /// - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] - public class WebProfilingController : UmbracoAuthorizedJsonController + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)] + public class WebProfilingController : UmbracoAuthorizedJsonController { private readonly IHostingEnvironment _hosting; diff --git a/src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs b/src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs new file mode 100644 index 0000000000..d22b044e51 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Umbraco.Web.BackOffice.Validation +{ + public class PrefixlessBodyModelValidator : ObjectModelValidator + { + public PrefixlessBodyModelValidator(IModelMetadataProvider modelMetadataProvider, IList validatorProviders) : + base(modelMetadataProvider, validatorProviders) + { + + } + + public override ValidationVisitor GetValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, + ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) + { + var visitor = new PrefixlessValidationVisitor( + actionContext, + validatorProvider, + validatorCache, + metadataProvider, + validationState); + + return visitor; + } + + private class PrefixlessValidationVisitor : ValidationVisitor + { + public PrefixlessValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) + : base(actionContext, validatorProvider, validatorCache, metadataProvider, validationState) { + + } + + public override bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel) + { + return base.Validate(metadata, string.Empty, model, alwaysValidateAtTopLevel); + } + } + } + + +} From 95e2262eb1565d253acb60d29ba79571445d365a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 29 May 2020 08:29:39 +0200 Subject: [PATCH 55/55] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Bugfix for ModelState.AddValidationError. Now using correct extension method --- .../Extensions/ModelStateExtensions.cs | 217 ++++++++++++++++++ .../Filters/DataTypeValidateAttribute.cs | 3 +- src/Umbraco.Web/ModelStateExtensions.cs | 16 +- 3 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs diff --git a/src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs new file mode 100644 index 0000000000..271ba17e74 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs @@ -0,0 +1,217 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Umbraco.Core; + +namespace Umbraco.Extensions +{ + internal static class ModelStateExtensions + { + + /// + /// Checks if there are any model errors on any fields containing the prefix + /// + /// + /// + /// + public static bool IsValid(this ModelStateDictionary state, string prefix) + { + return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); + } + + /// + /// Adds the error to model state correctly for a property so we can use it on the client side. + /// + /// + /// + /// + /// The culture for the property, if the property is invariant than this is empty + internal static void AddPropertyError(this ModelStateDictionary modelState, + ValidationResult result, string propertyAlias, string culture = "", string segment = "") + { + if (culture == null) + culture = ""; + modelState.AddValidationError(result, "_Properties", propertyAlias, + //if the culture is null, we'll add the term 'invariant' as part of the key + culture.IsNullOrWhiteSpace() ? "invariant" : culture, + // if the segment is null, we'll add the term 'null' as part of the key + segment.IsNullOrWhiteSpace() ? "null" : segment); + } + + /// + /// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs + /// + /// + /// + /// + /// + internal static void AddVariantValidationError(this ModelStateDictionary modelState, + string culture, string segment, string errMsg) + { + var key = "_content_variant_" + (culture.IsNullOrWhiteSpace() ? "invariant" : culture) + "_" + (segment.IsNullOrWhiteSpace() ? "null" : segment) + "_"; + if (modelState.ContainsKey(key)) return; + modelState.AddModelError(key, errMsg); + } + + /// + /// Returns a list of cultures that have property validation errors + /// + /// + /// + /// The culture to affiliate invariant errors with + /// + /// A list of cultures that have property validation errors. The default culture will be returned for any invariant property errors. + /// + internal static IReadOnlyList<(string culture, string segment)> GetVariantsWithPropertyErrors(this ModelStateDictionary modelState, + string cultureForInvariantErrors) + { + //Add any variant specific errors here + var variantErrors = modelState.Keys + .Where(key => key.StartsWith("_Properties.")) //only choose _Properties errors + .Select(x => x.Split('.')) //split into parts + .Where(x => x.Length >= 4 && !x[2].IsNullOrWhiteSpace() && !x[3].IsNullOrWhiteSpace()) + .Select(x => (culture: x[2], segment: x[3])) + //if the culture is marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language + //so errors for those must show up under the default lang. + //if the segment is marked "null" then return an actual null + .Select(x => + { + var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture; + var segment = x.segment == "null" ? null : x.segment; + return (culture, segment); + }) + .Distinct() + .ToList(); + + return variantErrors; + } + + /// + /// Returns a list of cultures that have any validation errors + /// + /// + /// + /// The culture to affiliate invariant errors with + /// + /// A list of cultures that have validation errors. The default culture will be returned for any invariant errors. + /// + internal static IReadOnlyList<(string culture, string segment)> GetVariantsWithErrors(this ModelStateDictionary modelState, string cultureForInvariantErrors) + { + var propertyVariantErrors = modelState.GetVariantsWithPropertyErrors(cultureForInvariantErrors); + + //now check the other special variant errors that are + var genericVariantErrors = modelState.Keys + .Where(x => x.StartsWith("_content_variant_") && x.EndsWith("_")) + .Select(x => x.TrimStart("_content_variant_").TrimEnd("_")) + .Select(x => + { + // Format "_" + var cs = x.Split(new[] { '_' }); + return (culture: cs[0], segment: cs[1]); + }) + .Where(x => !x.culture.IsNullOrWhiteSpace()) + //if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language + //so errors for those must show up under the default lang. + //if the segment is marked "null" then return an actual null + .Select(x => + { + var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture; + var segment = x.segment == "null" ? null : x.segment; + return (culture, segment); + }) + .Distinct(); + + return propertyVariantErrors.Union(genericVariantErrors).Distinct().ToList(); + } + + /// + /// Adds the error to model state correctly for a property so we can use it on the client side. + /// + /// + /// + /// + /// Each model state validation error has a name and in most cases this name is made up of parts which are delimited by a '.' + /// + internal static void AddValidationError(this ModelStateDictionary modelState, + ValidationResult result, params string[] parts) + { + // if there are assigned member names, we combine the member name with the owner name + // so that we can try to match it up to a real field. otherwise, we assume that the + // validation message is for the overall owner. + // Owner = the component being validated, like a content property but could be just an HTML field on another editor + + var withNames = false; + var delimitedParts = string.Join(".", parts); + foreach (var memberName in result.MemberNames) + { + modelState.TryAddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage); + withNames = true; + } + if (!withNames) + { + modelState.TryAddModelError($"{delimitedParts}", result.ErrorMessage); + } + + } + + /// + /// Will add an error to model state for a key if that key and error don't already exist + /// + /// + /// + /// + /// + private static bool TryAddModelError(this ModelStateDictionary modelState, string key, string errorMsg) + { + if (modelState.TryGetValue(key, out var errs)) + { + foreach(var e in errs.Errors) + if (e.ErrorMessage == errorMsg) return false; //if this same error message exists for the same key, just exit + } + + modelState.AddModelError(key, errorMsg); + return true; + } + + public static IDictionary ToErrorDictionary(this ModelStateDictionary modelState) + { + var modelStateError = new Dictionary(); + foreach (var keyModelStatePair in modelState) + { + var key = keyModelStatePair.Key; + var errors = keyModelStatePair.Value.Errors; + if (errors != null && errors.Count > 0) + { + modelStateError.Add(key, errors.Select(error => error.ErrorMessage)); + } + } + return modelStateError; + } + + /// + /// Serializes the ModelState to JSON for JavaScript to interrogate the errors + /// + /// + /// + public static JsonResult ToJsonErrors(this ModelStateDictionary state) + { + return new JsonResult(new + { + success = state.IsValid.ToString().ToLower(), + failureType = "ValidationError", + validationErrors = from e in state + where e.Value.Errors.Count > 0 + select new + { + name = e.Key, + errors = e.Value.Errors.Select(x => x.ErrorMessage) + .Concat( + e.Value.Errors.Where(x => x.Exception != null).Select(x => x.Exception.Message)) + } + }); + } + + } +} diff --git a/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs index efd45f3a6e..41e928053d 100644 --- a/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Extensions; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; @@ -95,7 +96,7 @@ namespace Umbraco.Web.Editors // run each IValueValidator (with null valueType and dataTypeConfiguration: not relevant here) foreach (var validator in editorField.Validators) foreach (var result in validator.Validate(field.Value, null, null)) - context.ModelState.AddModelError(field.Key,result.ErrorMessage); + context.ModelState.AddValidationError(result, "Properties", field.Key); } if (context.ModelState.IsValid == false) diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 10b1b5dadd..bb064b71c5 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -1,14 +1,12 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web.Mvc; using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Services; namespace Umbraco.Web { + //Migrated to .NET Core except the Merge method internal static class ModelStateExtensions { @@ -40,7 +38,7 @@ namespace Umbraco.Web { return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); } - + /// /// Adds the error to model state correctly for a property so we can use it on the client side. /// @@ -101,7 +99,7 @@ namespace Umbraco.Web var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture; var segment = x.segment == "null" ? null : x.segment; return (culture, segment); - }) + }) .Distinct() .ToList(); @@ -124,7 +122,7 @@ namespace Umbraco.Web //now check the other special variant errors that are var genericVariantErrors = modelState.Keys .Where(x => x.StartsWith("_content_variant_") && x.EndsWith("_")) - .Select(x => x.TrimStart("_content_variant_").TrimEnd("_")) + .Select(x => x.TrimStart("_content_variant_").TrimEnd("_")) .Select(x => { // Format "_" @@ -145,7 +143,7 @@ namespace Umbraco.Web return propertyVariantErrors.Union(genericVariantErrors).Distinct().ToList(); } - + /// /// Adds the error to model state correctly for a property so we can use it on the client side. /// @@ -173,7 +171,7 @@ namespace Umbraco.Web { modelState.TryAddModelError($"{delimitedParts}", result.ErrorMessage); } - + } ///