From 7eee09758e0f32d57831f3b174899c8c0aaf1e06 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Nov 2020 09:49:58 +1100 Subject: [PATCH 01/86] Ensure there's explicit scopes used for all service access in the Examine indexing logic --- .../Services/Implement/PublicAccessService.cs | 6 +- .../ContentValueSetValidator.cs | 47 ++++++++++--- src/Umbraco.Web/Search/ExamineComponent.cs | 67 ++++++++++++------- 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs index ab9ea64292..06570da725 100644 --- a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs @@ -62,12 +62,10 @@ namespace Umbraco.Core.Services.Implement //start with the deepest id ids.Reverse(); - using (var scope = ScopeProvider.CreateScope()) + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { //This will retrieve from cache! - var entries = _publicAccessRepository.GetMany().ToArray(); - - scope.Complete(); + var entries = _publicAccessRepository.GetMany().ToList(); foreach (var id in ids) { var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); diff --git a/src/Umbraco.Examine/ContentValueSetValidator.cs b/src/Umbraco.Examine/ContentValueSetValidator.cs index 9555566c53..f702e8197d 100644 --- a/src/Umbraco.Examine/ContentValueSetValidator.cs +++ b/src/Umbraco.Examine/ContentValueSetValidator.cs @@ -4,7 +4,9 @@ using System.Linq; using Examine; using Examine.LuceneEngine.Providers; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; namespace Umbraco.Examine @@ -15,9 +17,9 @@ namespace Umbraco.Examine public class ContentValueSetValidator : ValueSetValidator, IContentValueSetValidator { private readonly IPublicAccessService _publicAccessService; - + private readonly IScopeProvider _scopeProvider; private const string PathKey = "path"; - private static readonly IEnumerable ValidCategories = new[] {IndexTypes.Content, IndexTypes.Media}; + private static readonly IEnumerable ValidCategories = new[] { IndexTypes.Content, IndexTypes.Media }; protected override IEnumerable ValidIndexCategories => ValidCategories; public bool PublishedValuesOnly { get; } @@ -53,25 +55,38 @@ namespace Umbraco.Examine public bool ValidateProtectedContent(string path, string category) { - if (category == IndexTypes.Content - && !SupportProtectedContent - //if the service is null we can't look this up so we'll return false - && (_publicAccessService == null || _publicAccessService.IsProtected(path))) + if (category == IndexTypes.Content && !SupportProtectedContent) { - return false; + //if the service is null we can't look this up so we'll return false + if (_publicAccessService == null || _scopeProvider == null) + { + return false; + } + + // explicit scope since we may be in a background thread + using (_scopeProvider.CreateScope(autoComplete: true)) + { + if (_publicAccessService.IsProtected(path)) + { + return false; + } + } } return true; } + // used for tests public ContentValueSetValidator(bool publishedValuesOnly, int? parentId = null, IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null) - : this(publishedValuesOnly, true, null, parentId, includeItemTypes, excludeItemTypes) + : this(publishedValuesOnly, true, null, null, parentId, includeItemTypes, excludeItemTypes) { } public ContentValueSetValidator(bool publishedValuesOnly, bool supportProtectedContent, - IPublicAccessService publicAccessService, int? parentId = null, + IPublicAccessService publicAccessService, + IScopeProvider scopeProvider, + int? parentId = null, IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null) : base(includeItemTypes, excludeItemTypes, null, null) { @@ -79,6 +94,16 @@ namespace Umbraco.Examine SupportProtectedContent = supportProtectedContent; ParentId = parentId; _publicAccessService = publicAccessService; + _scopeProvider = scopeProvider; + } + + [Obsolete("Use the ctor with all parameters instead")] + public ContentValueSetValidator(bool publishedValuesOnly, bool supportProtectedContent, + IPublicAccessService publicAccessService, int? parentId = null, + IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null) + : this(publishedValuesOnly, supportProtectedContent, publicAccessService, Current.ScopeProvider, + parentId, includeItemTypes, excludeItemTypes) + { } public override ValueSetValidationResult Validate(ValueSet valueSet) @@ -103,7 +128,7 @@ namespace Umbraco.Examine && variesByCulture.Count > 0 && variesByCulture[0].Equals("y")) { //so this valueset is for a content that varies by culture, now check for non-published cultures and remove those values - foreach(var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineIndex.PublishedFieldName}_")).ToList()) + foreach (var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineIndex.PublishedFieldName}_")).ToList()) { if (publishField.Value.Count <= 0 || !publishField.Value[0].Equals("y")) { @@ -134,7 +159,7 @@ namespace Umbraco.Examine || !ValidateProtectedContent(path, valueSet.Category)) return ValueSetValidationResult.Filtered; - return isFiltered ? ValueSetValidationResult.Filtered: ValueSetValidationResult.Valid; + return isFiltered ? ValueSetValidationResult.Filtered : ValueSetValidationResult.Valid; } } } diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index c9d7b7cf56..bb3f30d4cf 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -629,22 +629,27 @@ namespace Umbraco.Web.Search // perform the ValueSet lookup on a background thread examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - // for content we have a different builder for published vs unpublished - // we don't want to build more value sets than is needed so we'll lazily build 2 one for published one for non-published - var builders = new Dictionary>> + // Background thread, wrap the whole thing in an explicit scope since we know + // DB services are used within this logic. + using (examineComponent._scopeProvider.CreateScope(autoComplete: true)) { - [true] = new Lazy>(() => examineComponent._publishedContentValueSetBuilder.GetValueSets(content).ToList()), - [false] = new Lazy>(() => examineComponent._contentValueSetBuilder.GetValueSets(content).ToList()) - }; + // for content we have a different builder for published vs unpublished + // we don't want to build more value sets than is needed so we'll lazily build 2 one for published one for non-published + var builders = new Dictionary>> + { + [true] = new Lazy>(() => examineComponent._publishedContentValueSetBuilder.GetValueSets(content).ToList()), + [false] = new Lazy>(() => examineComponent._contentValueSetBuilder.GetValueSets(content).ToList()) + }; - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => isPublished || !x.PublishedValuesOnly) - .Where(x => x.EnableDefaultEventHandler)) - { - var valueSet = builders[index.PublishedValuesOnly].Value; - index.IndexItems(valueSet); - } + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => isPublished || !x.PublishedValuesOnly) + .Where(x => x.EnableDefaultEventHandler)) + { + var valueSet = builders[index.PublishedValuesOnly].Value; + index.IndexItems(valueSet); + } + } })); } } @@ -675,14 +680,19 @@ namespace Umbraco.Web.Search // perform the ValueSet lookup on a background thread examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); - - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => isPublished || !x.PublishedValuesOnly) - .Where(x => x.EnableDefaultEventHandler)) + // Background thread, wrap the whole thing in an explicit scope since we know + // DB services are used within this logic. + using (examineComponent._scopeProvider.CreateScope(autoComplete: true)) { - index.IndexItems(valueSet); + var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); + + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => isPublished || !x.PublishedValuesOnly) + .Where(x => x.EnableDefaultEventHandler)) + { + index.IndexItems(valueSet); + } } })); } @@ -712,12 +722,17 @@ namespace Umbraco.Web.Search // perform the ValueSet lookup on a background thread examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => x.EnableDefaultEventHandler)) + // Background thread, wrap the whole thing in an explicit scope since we know + // DB services are used within this logic. + using (examineComponent._scopeProvider.CreateScope(autoComplete: true)) { - index.IndexItems(valueSet); + var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => x.EnableDefaultEventHandler)) + { + index.IndexItems(valueSet); + } } })); } From 348ae22d91f72336e6bcbc7bcb2be63bf0355eca Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 14 Dec 2020 12:37:30 +1100 Subject: [PATCH 02/86] Fixing tests --- .../UmbracoContentValueSetValidatorTests.cs | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs index 8bdb0c71c7..330529a5db 100644 --- a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core; using Umbraco.Core.Models; using System; using System.Linq; +using Umbraco.Core.Scoping; namespace Umbraco.Tests.UmbracoExamine { @@ -17,10 +18,14 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Invalid_Category() { - var validator = new ContentValueSetValidator(false, true, Mock.Of()); + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); - Assert.AreEqual(ValueSetValidationResult.Valid, result); + Assert.AreEqual(ValueSetValidationResult.Valid, result); result = validator.Validate(ValueSet.FromObject("777", IndexTypes.Media, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); @@ -33,7 +38,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Must_Have_Path() { - var validator = new ContentValueSetValidator(false, true, Mock.Of()); + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -45,7 +54,12 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Parent_Id() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), 555); + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), + 555); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); @@ -63,7 +77,9 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, new[] { "hello", "world" }, null); @@ -79,7 +95,9 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Exclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, null, new[] { "hello", "world" }); @@ -95,7 +113,9 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Exclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, new[] { "hello", "world" }, new[] { "world" }); @@ -111,7 +131,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), includeItemTypes: new List { "include-content" }); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); @@ -127,7 +151,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Exclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), excludeItemTypes: new List { "exclude-content" }); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); @@ -143,7 +171,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Exclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), includeItemTypes: new List { "include-content", "exclude-content" }, excludeItemTypes: new List { "exclude-content" }); @@ -163,7 +195,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Recycle_Bin_Content() { - var validator = new ContentValueSetValidator(true, false, Mock.Of()); + var validator = new ContentValueSetValidator( + true, + false, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,-20,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -187,7 +223,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Recycle_Bin_Media() { - var validator = new ContentValueSetValidator(true, false, Mock.Of()); + var validator = new ContentValueSetValidator( + true, + false, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Media, new { hello = "world", path = "-1,-21,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); @@ -203,7 +243,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Published_Only() { - var validator = new ContentValueSetValidator(true, true, Mock.Of()); + var validator = new ContentValueSetValidator( + true, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -230,7 +274,10 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Published_Only_With_Variants() { - var validator = new ContentValueSetValidator(true, true, Mock.Of()); + var validator = new ContentValueSetValidator(true, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(new ValueSet("555", IndexTypes.Content, new Dictionary @@ -288,7 +335,11 @@ namespace Umbraco.Tests.UmbracoExamine .Returns(Attempt.Succeed(new PublicAccessEntry(Guid.NewGuid(), 555, 444, 333, Enumerable.Empty()))); publicAccessService.Setup(x => x.IsProtected("-1,777")) .Returns(Attempt.Fail()); - var validator = new ContentValueSetValidator(false, false, publicAccessService.Object); + var validator = new ContentValueSetValidator( + false, + false, + publicAccessService.Object, + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); From f8692590d026b378b43a91dfef01fa3f6a03822f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 11 Jan 2021 10:25:43 +0100 Subject: [PATCH 03/86] Fix legacy icons no longer displaying after changes in #9255 --- .../src/views/common/infiniteeditors/iconpicker/iconpicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 3a8c6d2196..7368d2f39b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -44,7 +44,7 @@
  • From a62a96dd92a3fbaf2b4024b4a9edc250db8a0a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Tue, 12 Jan 2021 00:12:24 +0100 Subject: [PATCH 04/86] Allow use of infinite editors in list views (#9026) Co-authored-by: Nathan Woulfe --- .../common/services/listviewhelper.service.js | 46 ++++++++++++++++--- .../grid/grid.listviewlayout.controller.js | 2 +- .../list/list.listviewlayout.controller.js | 2 +- .../listview/listview.controller.js | 37 +++++++++++++++ .../PropertyEditors/ListViewConfiguration.cs | 3 ++ 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index 79c3880e60..14643dc9cd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -45,8 +45,7 @@ (function () { 'use strict'; - function listViewHelper($location, $rootScope, localStorageService, urlHelper) { - + function listViewHelper($location, $rootScope, localStorageService, urlHelper, editorService) { var firstSelectedIndex = 0; var localStorageKey = "umblistViewLayout"; @@ -574,16 +573,51 @@ * * @param {Object} item The item to edit */ - function editItem(item) { + function editItem(item, scope) { if (!item.editPath) { return; } + + if (scope.options.useInfiniteEditor) + { + var editorModel = { + id: item.id, + submit: function(model) { + editorService.close(); + scope.getContent(scope.contentId); + }, + close: function() { + editorService.close(); + scope.getContent(scope.contentId); + } + }; + + if (item.editPath.indexOf("/content/") == 0) + { + editorService.contentEditor(editorModel); + return; + } + + if (item.editPath.indexOf("/media/") == 0) + { + editorService.mediaEditor(editorModel); + return; + } + + if (item.editPath.indexOf("/member/") == 0) + { + editorModel.id = item.key; + editorService.memberEditor(editorModel); + return; + } + } + var parts = item.editPath.split("?"); var path = parts[0]; var params = parts[1] - ? urlHelper.getQueryStringParams("?" + parts[1]) - : {}; - + ? urlHelper.getQueryStringParams("?" + parts[1]) + : {}; + $location.path(path); for (var p in params) { $location.search(p, params[p]); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index e688443dd8..1bd370ae01 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -117,7 +117,7 @@ } function goToItem(item, $event, $index) { - listViewHelper.editItem(item); + listViewHelper.editItem(item, $scope); } activate(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 58e7f45785..f0a165c22f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -53,7 +53,7 @@ } function clickItem(item) { - listViewHelper.editItem(item); + listViewHelper.editItem(item, $scope); } function isSortDirection(col, direction) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index d98dc92f24..6acea26286 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -147,6 +147,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time var listParamsForCurrent = $routeParams.id == $routeParams.list; $scope.options = { + useInfiniteEditor: $scope.model.config.useInfiniteEditor === true, pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, pageNumber: (listParamsForCurrent && $routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, filter: (listParamsForCurrent && $routeParams.filter ? $routeParams.filter : '').trim(), @@ -800,6 +801,42 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } function createBlank(entityType, docTypeAlias) { + if ($scope.options.useInfiniteEditor) { + + var editorModel = { + create: true, + submit: function(model) { + editorService.close(); + $scope.reloadView($scope.contentId); + }, + close: function() { + editorService.close(); + $scope.reloadView($scope.contentId); + } + }; + + if (entityType == "content") + { + editorModel.parentId = $scope.contentId; + editorModel.documentTypeAlias = docTypeAlias; + editorService.contentEditor(editorModel); + return; + } + + if (entityType == "media") + { + editorService.mediaEditor(editorModel); + return; + } + + if (entityType == "member") + { + editorModel.doctype = docTypeAlias; + editorService.memberEditor(editorModel); + return; + } + } + $location .path("/" + entityType + "/" + entityType + "/edit/" + $scope.contentId) .search("doctype", docTypeAlias) diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs index ce41628ec1..de538793a5 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs @@ -69,6 +69,9 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("showContentFirst", "Show Content App First", "boolean", Description = "Enable this to show the content app by default instead of the list view app")] public bool ShowContentFirst { get; set; } + [ConfigurationField("useInfiniteEditor", "Edit in Infinite Editor", "boolean", Description = "Enable this to use infinite editing to edit the content of the list view")] + public bool UseInfiniteEditor { get; set; } + public class Property { [JsonProperty("alias")] From 143d2e7e30ecf0537f2ccc7b539580a16690ec46 Mon Sep 17 00:00:00 2001 From: David Brendel Date: Sun, 18 Oct 2020 02:18:28 +0200 Subject: [PATCH 05/86] Check if urls is null Check if node.urls is null and set scope.currentUrls to null and return --- .../components/content/umbcontentnodeinfo.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 60f877d0b6..18ba264c94 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -313,7 +313,7 @@ function updateCurrentUrls() { // never show URLs for element types (if they happen to have been created in the content tree) - if (scope.node.isElement) { + if (scope.node.isElement || scope.node.urls === null) { scope.currentUrls = null; return; } From 2398c407204d8e8a21eed286d721ca4a8eeaa43a Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sun, 3 Jan 2021 15:19:34 +0100 Subject: [PATCH 06/86] Replace jQuery trim() using vanilla JS --- .../src/views/propertyeditors/listview/listview.controller.js | 2 +- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 6acea26286..76e6759a47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -688,7 +688,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time if (e.nameExp) { var newValue = e.nameExp({ value }); - if (newValue && (newValue = $.trim(newValue))) { + if (newValue && (newValue = newValue.trim())) { value = newValue; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index d04c5f722b..a629bd2eea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -352,7 +352,7 @@ item["$index"] = (idx + 1); var newName = contentType.nameExp(item); - if (newName && (newName = $.trim(newName))) { + if (newName && (newName = newName.trim())) { name = newName; } From 1d6d4aec5692fb8933fd17db8347305ff96e963d Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Wed, 13 Jan 2021 11:58:19 +0000 Subject: [PATCH 07/86] ngDocs for contentresource (#9570) * ngDocs for contentresource * Update content.resource.js typo: paramater => parameter Co-authored-by: Nathan Woulfe --- .../src/common/resources/content.resource.js | 222 +++++++++++++++++- 1 file changed, 219 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 3dfbeade4f..368eab2339 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -42,6 +42,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return { + /** + * @ngdoc method + * @name umbraco.resources.contentResource#allowsCultureVariation + * @methodOf umbraco.resources.contentResource + * + * @description + * Check whether any content types have culture variant enabled + * + * ##usage + *
    +        * contentResource.allowsCultureVariation()
    +        *    .then(function() {
    +        *       Do stuff...
    +        *    });
    +        * 
    + * + * @returns {Promise} resourcePromise object. + * + */ allowsCultureVariation: function () { return umbRequestHelper.resourcePromise( $http.get( @@ -51,6 +70,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve variant content types'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#savePermissions + * @methodOf umbraco.resources.contentResource + * + * @description + * Save user group permissions for the content + * + * ##usage + *
    +        * contentResource.savePermissions(saveModel)
    +        *    .then(function() {
    +        *       Do stuff...
    +        *    });
    +        * 
    + * + * @param {object} The object which contains the user group permissions for the content + * @returns {Promise} resourcePromise object. + * + */ savePermissions: function (saveModel) { if (!saveModel) { throw "saveModel cannot be null"; @@ -68,7 +107,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to save permissions'); }, - + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Get the recycle bin + * + * ##usage + *
    +        * contentResource.getRecycleBin()
    +        *    .then(function() {
    +        *       Do stuff...
    +        *    });
    +        * 
    + * + * @returns {Promise} resourcePromise object. + * + */ getRecycleBin: function () { return umbRequestHelper.resourcePromise( $http.get( @@ -337,6 +394,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to delete item ' + id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteBlueprint + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content blueprint item with a given id + * + * ##usage + *
    +        * contentResource.deleteBlueprint(1234)
    +        *    .then(function() {
    +        *        alert('its gone!');
    +        *    });
    +        * 
    + * + * @param {Int} id id of content blueprint item to delete + * @returns {Promise} resourcePromise object. + * + */ deleteBlueprint: function (id) { return umbRequestHelper.resourcePromise( $http.post( @@ -382,6 +459,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getBlueprintById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content blueprint item with a given id + * + * ##usage + *
    +        * contentResource.getBlueprintById(1234)
    +        *    .then(function() {
    +        *       Do stuff...
    +        *    });
    +        * 
    + * + * @param {Int} id id of content blueprint item to retrieve + * @returns {Promise} resourcePromise object. + * + */ getBlueprintById: function (id) { return umbRequestHelper.resourcePromise( $http.get( @@ -395,6 +492,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNotifySettingsById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets notification options for a content item with a given id for the current user + * + * ##usage + *
    +        * contentResource.getNotifySettingsById(1234)
    +        *    .then(function() {
    +        *       Do stuff...
    +        *    });
    +        * 
    + * + * @param {Int} id id of content item + * @returns {Promise} resourcePromise object. + * + */ getNotifySettingsById: function (id) { return umbRequestHelper.resourcePromise( $http.get( @@ -405,6 +522,27 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve data for content id ' + id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNotifySettingsById + * @methodOf umbraco.resources.contentResource + * + * @description + * Sets notification settings for a content item with a given id for the current user + * + * ##usage + *
    +        * contentResource.setNotifySettingsById(1234,["D", "F", "H"])
    +        *    .then(function() {
    +        *       Do stuff...
    +        *    });
    +        * 
    + * + * @param {Int} id id of content item + * @param {Array} options the notification options to set for the content item + * @returns {Promise} resourcePromise object. + * + */ setNotifySettingsById: function (id, options) { if (!id) { throw "contentId cannot be null"; @@ -697,7 +835,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @methodOf umbraco.resources.contentResource * * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * Saves changes made to a content item to its current version, if the content item is new, the isNew parameter must be passed to force creation * if the content item needs to have files attached, they must be provided as the files param and passed separately * * @@ -727,6 +865,34 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#saveBlueprint + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content blueprint item to its current version, if the content blueprint item is new, the isNew parameter must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * ##usage + *
    +        * contentResource.getById(1234)
    +        *    .then(function(content) {
    +        *          content.name = "I want a new name!";
    +        *          contentResource.saveBlueprint(content, false)
    +        *            .then(function(content){
    +        *                alert("Retrieved, updated and saved again");
    +        *            });
    +        *    });
    +        * 
    + * + * @param {Object} content The content blueprint item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @param {Bool} showNotifications an option to disable/show notifications (default is true) + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ saveBlueprint: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -740,7 +906,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @methodOf umbraco.resources.contentResource * * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew parameter must be passed to force creation * if the content item needs to have files attached, they must be provided as the files param and passed separately * * @@ -770,6 +936,35 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item and its descendants to a new version, if the content item is new, the isNew parameter must be passed to force creation + * if the content items needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +        * contentResource.getById(1234)
    +        *    .then(function(content) {
    +        *          content.name = "I want a new name, and be published!";
    +        *          contentResource.publishWithDescendants(content, false)
    +        *            .then(function(content){
    +        *                alert("Retrieved, updated and published again");
    +        *            });
    +        *    });
    +        * 
    + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @param {Bool} showNotifications an option to disable/show notifications (default is true) + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ publishWithDescendants: function (content, isNew, force, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -873,6 +1068,27 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#createBlueprintFromContent + * @methodOf umbraco.resources.contentResource + * + * @description + * Creates a content blueprint with a given name from a given content id + * + * ##usage + *
    +        * contentResource.createBlueprintFromContent(1234,"name")
    +        *    .then(function(content) {
    +        *        alert("created");
    +        *    });
    +            * 
    + * + * @param {Int} id The ID of the content to create the content blueprint from + * @param {string} id The name of the content blueprint + * @returns {Promise} resourcePromise object + * + */ createBlueprintFromContent: function (contentId, name) { return umbRequestHelper.resourcePromise( $http.post( From 1ccd3e6ec1626b7937b16a10996094aadb1e5d76 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 11 Jan 2021 19:35:53 +0100 Subject: [PATCH 08/86] Fix issue with button didn't do anything --- .../src/views/prevalueeditors/treesource.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html index 865b59506b..c9da687f0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html @@ -41,9 +41,9 @@
    • - + -
      +

      Use Xpath query to set a root node on the tree, either based on a search from the root of the content tree, or by using a context-aware placeholder.

      From 16cd19ed9a6a3f3930fb784371b36f87ed91e007 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sun, 10 Jan 2021 13:43:57 +0100 Subject: [PATCH 09/86] Fix styling of search icon in search filter --- .../src/less/components/umb-search-filter.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less index bda9fa7a7e..b96d3e8569 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less @@ -18,6 +18,8 @@ html .umb-search-filter { margin: 0; } + // "icon-search" class it kept for backward compatibility + .umb-icon, .icon-search { color: #d8d7d9; position: absolute; From 0fdd599596640b55a9dbe7955880fea2bfc3bd39 Mon Sep 17 00:00:00 2001 From: Callum Whyte Date: Wed, 13 Jan 2021 14:11:50 +0000 Subject: [PATCH 10/86] Nested content UI fixes (#9384) * Fixing spacing of nested content header + icon * Removing border from single Nested Content properties * fix overlapping icon on nested content with multiple items Co-authored-by: Sebastiaan Janssen Co-authored-by: Mads Rasmussen --- .../less/components/umb-nested-content.less | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 29a19bbba9..bd787e2329 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -4,6 +4,7 @@ .umb-nested-content-property-container { position: relative; + &:not(:last-child){ margin-bottom: 12px; } @@ -54,19 +55,19 @@ visibility: visible !important; } -.umb-nested-content__item--single > .umb-nested-content__content { +.umb-nested-content__item--single { border: 0; -} -.umb-nested-content__item--single > .umb-nested-content__content > .umb-pane { - margin: 0; + > .umb-nested-content__content { + > .umb-pane { + margin: 0; + } + } } .umb-nested-content__header-bar { - cursor: pointer; background-color: @white; - -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; @@ -78,21 +79,19 @@ padding-right: 60px; } } - } .umb-nested-content__heading { + display: flex; + padding: 15px; line-height: 20px; - position: relative; - padding: 15px 5px; - color:@ui-option-type; + color: @ui-option-type; &:hover { - color:@ui-option-type-hover; + color: @ui-option-type-hover; } .umb-nested-content__item-icon { - position: absolute; margin-top: -3px; font-size: 22px; } @@ -106,10 +105,9 @@ padding-left: 5px; &.--has-icon { - padding-left: 30px; + padding-left: 10px; } } - } .umb-nested-content__icons { @@ -125,13 +123,16 @@ .umb-nested-content__item--active > .umb-nested-content__header-bar { .umb-nested-content__heading { background-color: @ui-active; + &:hover { - color:@ui-option-type; + color: @ui-option-type; } + .umb-nested-content__item-name { padding-right: 60px; } } + .umb-nested-content__icons { background-color: @ui-active; &:before { @@ -140,8 +141,6 @@ } } - - .umb-nested-content__header-bar:hover .umb-nested-content__icons, .umb-nested-content__header-bar:focus .umb-nested-content__icons, .umb-nested-content__header-bar:focus-within .umb-nested-content__icons, @@ -149,8 +148,6 @@ opacity: 1; } - - .umb-nested-content__icon { background: transparent; border: 0 none; @@ -180,9 +177,6 @@ } } - - - .umb-nested-content__footer-bar { margin-top: 20px; } @@ -212,7 +206,6 @@ cursor: not-allowed; } - .umb-nested-content__content { border-top: 1px solid transparent; border-bottom: 1px solid transparent; @@ -299,4 +292,4 @@ .umb-textarea, .umb-textstring { width:100%; } -} +} \ No newline at end of file From 5efc1817d66ffd59946d89c635e89d55b9769dfa Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Tue, 8 Dec 2020 09:06:28 +0100 Subject: [PATCH 11/86] Add unattended upgrade support --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 22 ++++++++++++++++++++++ src/Umbraco.Core/RuntimeOptions.cs | 10 ++++++++++ src/Umbraco.Core/RuntimeState.cs | 4 ++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 81738151f3..ba2e38f80f 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -13,6 +13,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Sync; @@ -189,6 +190,16 @@ namespace Umbraco.Core.Runtime // create the factory _factory = Current.Factory = composition.CreateFactory(); + // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade + if (_state.Reason == RuntimeLevelReason.UpgradeMigrations && _state.Level == RuntimeLevel.Run) + { + // do the upgrade + DoUnattendedUpgrade(_factory.GetInstance()); + + // upgrade is done, set reason to Run + _state.Reason = RuntimeLevelReason.Run; + } + // create & initialize the components _components = _factory.GetInstance(); _components.Initialize(); @@ -288,6 +299,17 @@ namespace Umbraco.Core.Runtime } } + private void DoUnattendedUpgrade(DatabaseBuilder databaseBuilder) + { + var plan = new UmbracoPlan(); + Logger.Info("Starting unattended upgrade."); + var result = databaseBuilder.UpgradeSchemaAndData(plan); + Logger.Info("Unattended upgrade completed."); + + if (result.Success == false) + throw new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message); + } + protected virtual void ConfigureUnhandledException() { //take care of unhandled exceptions - there is nothing we can do to diff --git a/src/Umbraco.Core/RuntimeOptions.cs b/src/Umbraco.Core/RuntimeOptions.cs index 0d64d36849..6183f62c1c 100644 --- a/src/Umbraco.Core/RuntimeOptions.cs +++ b/src/Umbraco.Core/RuntimeOptions.cs @@ -23,6 +23,7 @@ namespace Umbraco.Core private static bool? _installMissingDatabase; private static bool? _installEmptyDatabase; private static bool? _installUnattended; + private static bool? _upgradeUnattended; // reads a boolean appSetting private static bool BoolSetting(string key, bool missing) => ConfigurationManager.AppSettings[key]?.InvariantEquals("true") ?? missing; @@ -66,6 +67,15 @@ namespace Umbraco.Core set => _installUnattended = value; } + /// + /// Gets a value indicating whether unattended upgrade is enabled. + /// + public static bool UpgradeUnattended + { + get => _upgradeUnattended ?? BoolSetting("Umbraco.Core.RuntimeState.UpgradeUnattended", false); + set => _upgradeUnattended = value; + } + /// /// Executes the RuntimeBoot handlers. /// diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 4a10b48dd6..c7a1a18d44 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -130,7 +130,7 @@ namespace Umbraco.Core { var localVersion = UmbracoVersion.LocalVersion; // the local, files, version var codeVersion = SemanticVersion; // the executing code version - + if (localVersion == null) { // there is no local version, we are not installed @@ -202,7 +202,7 @@ namespace Umbraco.Core // although the files version matches the code version, the database version does not // which means the local files have been upgraded but not the database - need to upgrade _logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); - Level = RuntimeLevel.Upgrade; + Level = RuntimeOptions.UpgradeUnattended ? RuntimeLevel.Run : RuntimeLevel.Upgrade; Reason = RuntimeLevelReason.UpgradeMigrations; } break; From 9851c308f5263bb7af8c0cecba68a4565ba3a410 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Wed, 9 Dec 2020 16:39:59 +0100 Subject: [PATCH 12/86] Add custom error page for BootFailedException --- .../src/views/errors/BootFailed.html | 79 +++++++++++++++++++ src/Umbraco.Web/Composing/ModuleInjector.cs | 11 ++- src/Umbraco.Web/UmbracoApplication.cs | 52 +++++++++++- src/Umbraco.Web/UmbracoApplicationBase.cs | 1 - src/Umbraco.Web/UmbracoInjectedModule.cs | 8 +- 5 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html diff --git a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html new file mode 100644 index 0000000000..c08627739a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html @@ -0,0 +1,79 @@ + + + + + + Boot Failed + + + +
      + +
      +

      Boot Failed

      +

      Umbraco failed to boot, if you are the owner of the website please see the log file for more details.

      +
      +
      + + diff --git a/src/Umbraco.Web/Composing/ModuleInjector.cs b/src/Umbraco.Web/Composing/ModuleInjector.cs index 57ef766dea..41540d196c 100644 --- a/src/Umbraco.Web/Composing/ModuleInjector.cs +++ b/src/Umbraco.Web/Composing/ModuleInjector.cs @@ -35,7 +35,16 @@ namespace Umbraco.Web.Composing catch { /* don't make it worse */ } if (runtimeState?.BootFailedException != null) - BootFailedException.Rethrow(runtimeState.BootFailedException); + { + // if we throw the exception here the HttpApplication.Application_Error method will never be hit + // and our custom error page will not be shown, so we need to wait for the request to begin + // before we throw the exception. + context.BeginRequest += (sender, args) => + { + BootFailedException.Rethrow(runtimeState.BootFailedException); + }; + return; + } // else... throw what we have throw; diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index f70651a43e..c96e21e348 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -1,7 +1,10 @@ -using System.Configuration; +using System; +using System.Configuration; +using System.IO; using System.Threading; using System.Web; using Umbraco.Core; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Runtime; @@ -23,6 +26,53 @@ namespace Umbraco.Web return runtime; } + protected override void OnApplicationError(object sender, EventArgs evargs) + { + base.OnApplicationError(sender, evargs); + + // if the exception is a BootFailedException we want to show a custom 500 page + if (Server.GetLastError() is BootFailedException) + { + // if the requested file exists on disk, clear the error and return + // this is needed to serve static files + if (File.Exists(Request.PhysicalPath)) + { + Server.ClearError(); + return; + } + + // if the application is in debug mode we don't want to show the custom 500 page + if (Context.IsDebuggingEnabled) return; + + // find the error file to show + var fileName = GetBootErrorFileName(); + + // if the file doesn't exist we return and a YSOD will be shown + if (File.Exists(fileName) == false) return; + + Response.TrySkipIisCustomErrors = true; + Server.ClearError(); + Response.Clear(); + + Response.StatusCode = 500; + Response.ContentType = "text/html"; + Response.WriteFile(fileName); + + CompleteRequest(); + } + } + + /// + /// Returns the absolute filename to the BootException html file. + /// + protected virtual string GetBootErrorFileName() + { + var fileName = Server.MapPath("~/config/errors/BootFailed.html"); + if (File.Exists(fileName)) return fileName; + + return Server.MapPath("~/umbraco/views/errors/BootFailed.html"); + } + /// /// Returns a new MainDom /// diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index 59b5b1779b..32a949e972 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -6,7 +6,6 @@ using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Logging; -using Umbraco.Core.Logging.Serilog; namespace Umbraco.Web { diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 6eeef081a2..92fee22983 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -357,15 +357,13 @@ namespace Umbraco.Web // there's nothing we can do really app.BeginRequest += (sender, args) => { - // would love to avoid throwing, and instead display a customized Umbraco 500 - // page - however if we don't throw here, something else might go wrong, and - // it's this later exception that would be reported. could not figure out how - // to prevent it, either with httpContext.Response.End() or .ApplicationInstance - // .CompleteRequest() + // if we don't throw here, something else might go wrong, + // and it's this later exception that would be reported. // also, if something goes wrong with our DI setup, the logging subsystem may // not even kick in, so here we try to give as much detail as possible + // the exception is handled in UmbracoApplication which shows a custom error page BootFailedException.Rethrow(Core.Composing.Current.RuntimeState.BootFailedException); }; return; From 5c5d0dad91fedc00f53c93fadc5ef6aa57d24a1e Mon Sep 17 00:00:00 2001 From: nzdev <834725+nzdev@users.noreply.github.com> Date: Sun, 17 Jan 2021 14:12:47 +1300 Subject: [PATCH 13/86] Pass int to Umbraco.Content instead of int? given we know _contentId has value. --- src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs index 168da455e3..d0b95aafa9 100644 --- a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web.Mvc { if (_contentId.HasValue) { - var content = Umbraco.Content(_contentId); + var content = Umbraco.Content(_contentId.Value); if (content == null) { throw new InvalidOperationException("Could not resolve content with id " + _contentId); From c5642fa513a5298a50825c5bdae4bcf04cda7d91 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 18 Jan 2021 15:33:14 +1100 Subject: [PATCH 14/86] change logging to profile loggin --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index ba2e38f80f..60e7851251 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -302,12 +302,12 @@ namespace Umbraco.Core.Runtime private void DoUnattendedUpgrade(DatabaseBuilder databaseBuilder) { var plan = new UmbracoPlan(); - Logger.Info("Starting unattended upgrade."); - var result = databaseBuilder.UpgradeSchemaAndData(plan); - Logger.Info("Unattended upgrade completed."); - - if (result.Success == false) - throw new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message); + using (ProfilingLogger.TraceDuration("Starting unattended upgrade.", "Unattended upgrade completed.")) + { + var result = databaseBuilder.UpgradeSchemaAndData(plan); + if (result.Success == false) + throw new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message); + } } protected virtual void ConfigureUnhandledException() From c1ebfc58cf3c57d62bc71a13e6a722191f758acd Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 15 Jan 2021 20:19:35 +0100 Subject: [PATCH 15/86] Add back icon name in class attribute to work again with font icons --- .../src/views/components/umb-node-preview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html index a7daa57775..34fb4d7dfd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html @@ -1,6 +1,6 @@
      - +
      From aa5470a801107de2046454612f42bc02f9672099 Mon Sep 17 00:00:00 2001 From: Nik Date: Tue, 19 Jan 2021 16:29:13 +0000 Subject: [PATCH 16/86] =?UTF-8?q?[AMEND]=20Updated=20the=20redirect=20URL?= =?UTF-8?q?=20Repository=20to=20search=20for=20entries=20wit=E2=80=A6=20(#?= =?UTF-8?q?9653)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nik Rimington --- .../Implement/RedirectUrlRepository.cs | 29 +++++---- .../Integration/ContentEventsTests.cs | 2 +- .../Services/ContentServiceTests.cs | 14 ++--- .../Services/EntityServiceTests.cs | 6 +- .../Services/RedirectUrlServiceTests.cs | 61 +++++++++++++------ .../Services/TestWithSomeContentBase.cs | 3 + 6 files changed, 72 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 95aa0a8461..24c1e31c20 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -167,6 +167,23 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); return dto == null ? null : Map(dto); } + public IRedirectUrl GetMostRecentUrl(string url, string culture) + { + if (string.IsNullOrWhiteSpace(culture)) return GetMostRecentUrl(url); + var urlHash = url.GenerateHash(); + var sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash && + (x.Culture == culture.ToLower() || x.Culture == string.Empty)) + .OrderByDescending(x => x.CreateDateUtc); + var dtos = Database.Fetch(sql); + var dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); + + if (dto == null) + dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); + + return dto == null ? null : Map(dto); + } + public IEnumerable GetContentUrls(Guid contentKey) { var sql = GetBaseQuery(false) @@ -208,17 +225,5 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); var rules = result.Items.Select(Map); return rules; } - - public IRedirectUrl GetMostRecentUrl(string url, string culture) - { - if (string.IsNullOrWhiteSpace(culture)) return GetMostRecentUrl(url); - var urlHash = url.GenerateHash(); - var sql = GetBaseQuery(false) - .Where(x => x.Url == url && x.UrlHash == urlHash && x.Culture == culture.ToLower()) - .OrderByDescending(x => x.CreateDateUtc); - var dtos = Database.Fetch(sql); - var dto = dtos.FirstOrDefault(); - return dto == null ? null : Map(dto); - } } } diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 7f103e13e4..af8ebe626e 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -2171,7 +2171,7 @@ namespace Umbraco.Tests.Integration [Test] public void HasInitialContent() { - Assert.AreEqual(4, ServiceContext.ContentService.Count()); + Assert.AreEqual(5, ServiceContext.ContentService.Count()); } #endregion diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index bfbb9a0d95..008c24fcbf 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -343,7 +343,7 @@ namespace Umbraco.Tests.Services } // Assert - Assert.AreEqual(24, contentService.Count()); + Assert.AreEqual(25, contentService.Count()); } [Test] @@ -1415,7 +1415,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(NodeDto.NodeIdSeed + 5); + var content = contentService.GetById(NodeDto.NodeIdSeed + 6); // Act var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); @@ -1687,7 +1687,7 @@ namespace Umbraco.Tests.Services Assert.AreNotEqual(-20, content.ParentId); Assert.IsFalse(content.Trashed); - Assert.AreEqual(3, descendants.Count); + Assert.AreEqual(4, descendants.Count); Assert.IsFalse(descendants.Any(x => x.Path.StartsWith("-1,-20,"))); Assert.IsFalse(descendants.Any(x => x.Trashed)); @@ -1700,7 +1700,7 @@ namespace Umbraco.Tests.Services Assert.AreEqual(-20, content.ParentId); Assert.IsTrue(content.Trashed); - Assert.AreEqual(3, descendants.Count); + Assert.AreEqual(4, descendants.Count); Assert.IsTrue(descendants.All(x => x.Path.StartsWith("-1,-20,"))); Assert.True(descendants.All(x => x.Trashed)); @@ -1987,7 +1987,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 2); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, contentService.CountChildren(temp.Id)); + Assert.AreEqual(3, contentService.CountChildren(temp.Id)); // Act var copy = contentService.Copy(temp, temp.ParentId, false, true, Constants.Security.SuperUserId); @@ -1997,7 +1997,7 @@ namespace Umbraco.Tests.Services Assert.That(copy, Is.Not.Null); Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); Assert.AreNotSame(content, copy); - Assert.AreEqual(2, contentService.CountChildren(copy.Id)); + Assert.AreEqual(3, contentService.CountChildren(copy.Id)); var child = contentService.GetById(NodeDto.NodeIdSeed + 3); var childCopy = contentService.GetPagedChildren(copy.Id, 0, 500, out var total).First(); @@ -2013,7 +2013,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 2); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, contentService.CountChildren(temp.Id)); + Assert.AreEqual(3, contentService.CountChildren(temp.Id)); // Act var copy = contentService.Copy(temp, temp.ParentId, false, false, Constants.Security.SuperUserId); diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 75f3662ee2..4b83407e63 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -477,7 +477,7 @@ namespace Umbraco.Tests.Services var entities = service.GetAll(UmbracoObjectTypes.Document).ToArray(); Assert.That(entities.Any(), Is.True); - Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(entities.Length, Is.EqualTo(5)); Assert.That(entities.Any(x => x.Trashed), Is.True); } @@ -490,7 +490,7 @@ namespace Umbraco.Tests.Services var entities = service.GetAll(objectTypeId).ToArray(); Assert.That(entities.Any(), Is.True); - Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(entities.Length, Is.EqualTo(5)); Assert.That(entities.Any(x => x.Trashed), Is.True); } @@ -502,7 +502,7 @@ namespace Umbraco.Tests.Services var entities = service.GetAll().ToArray(); Assert.That(entities.Any(), Is.True); - Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(entities.Length, Is.EqualTo(5)); Assert.That(entities.Any(x => x.Trashed), Is.True); } diff --git a/src/Umbraco.Tests/Services/RedirectUrlServiceTests.cs b/src/Umbraco.Tests/Services/RedirectUrlServiceTests.cs index 1344b1661b..437de3ac5b 100644 --- a/src/Umbraco.Tests/Services/RedirectUrlServiceTests.cs +++ b/src/Umbraco.Tests/Services/RedirectUrlServiceTests.cs @@ -1,13 +1,10 @@ -using System; +using Moq; +using NUnit.Framework; using System.Linq; using System.Threading; -using Moq; -using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; - -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.Testing; @@ -19,11 +16,14 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class RedirectUrlServiceTests : TestWithSomeContentBase { - private IContent _testPage; - private IContent _altTestPage; - private string _url = "blah"; - private string _cultureA = "en"; - private string _cultureB = "de"; + private IContent _firstSubPage; + private IContent _secondSubPage; + private IContent _thirdSubPage; + private readonly string _url = "blah"; + private readonly string _urlAlt = "alternativeUrl"; + private readonly string _cultureEnglish = "en"; + private readonly string _cultureGerman = "de"; + private readonly string _unusedCulture = "es"; public override void CreateTestData() { base.CreateTestData(); @@ -33,22 +33,34 @@ namespace Umbraco.Tests.Services { var repository = new RedirectUrlRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of()); var rootContent = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); - var subPages = ServiceContext.ContentService.GetPagedChildren(rootContent.Id, 0, 2, out _).ToList(); - _testPage = subPages[0]; - _altTestPage = subPages[1]; + var subPages = ServiceContext.ContentService.GetPagedChildren(rootContent.Id, 0, 3, out _).ToList(); + _firstSubPage = subPages[0]; + _secondSubPage = subPages[1]; + _thirdSubPage = subPages[2]; + + repository.Save(new RedirectUrl { - ContentKey = _testPage.Key, + ContentKey = _firstSubPage.Key, Url = _url, - Culture = _cultureA + Culture = _cultureEnglish }); + Thread.Sleep(1000); //Added delay to ensure timestamp difference as sometimes they seem to have the same timestamp repository.Save(new RedirectUrl { - ContentKey = _altTestPage.Key, + ContentKey = _secondSubPage.Key, Url = _url, - Culture = _cultureB + Culture = _cultureGerman }); + Thread.Sleep(1000); + repository.Save(new RedirectUrl + { + ContentKey = _thirdSubPage.Key, + Url = _urlAlt, + Culture = string.Empty + }); + scope.Complete(); } } @@ -64,7 +76,7 @@ namespace Umbraco.Tests.Services { var redirectUrlService = ServiceContext.RedirectUrlService; var redirect = redirectUrlService.GetMostRecentRedirectUrl(_url); - Assert.AreEqual(redirect.ContentId, _altTestPage.Id); + Assert.AreEqual(redirect.ContentId, _secondSubPage.Id); } @@ -72,8 +84,17 @@ namespace Umbraco.Tests.Services public void Can_Get_Most_Recent_RedirectUrl_With_Culture() { var redirectUrlService = ServiceContext.RedirectUrlService; - var redirect = redirectUrlService.GetMostRecentRedirectUrl(_url, _cultureA); - Assert.AreEqual(redirect.ContentId, _testPage.Id); + var redirect = redirectUrlService.GetMostRecentRedirectUrl(_url, _cultureEnglish); + Assert.AreEqual(redirect.ContentId, _firstSubPage.Id); + + } + + [Test] + public void Can_Get_Most_Recent_RedirectUrl_With_Culture_When_No_CultureVariant_Exists() + { + var redirectUrlService = ServiceContext.RedirectUrlService; + var redirect = redirectUrlService.GetMostRecentRedirectUrl(_urlAlt, _unusedCulture); + Assert.AreEqual(redirect.ContentId, _thirdSubPage.Id); } diff --git a/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs b/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs index 2b313afc5c..6daa16470b 100644 --- a/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs +++ b/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs @@ -41,6 +41,9 @@ namespace Umbraco.Tests.Services Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); ServiceContext.ContentService.Save(subpage2, 0); + Content subpage3 = MockedContent.CreateSimpleContent(contentType, "Text Page 3", textpage.Id); + ServiceContext.ContentService.Save(subpage3, 0); + //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> 1064 Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); trashed.Trashed = true; From 0822f0cbe54b663b1fda6a133c7a198374fdc2f7 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 19 Jan 2021 22:27:28 +0000 Subject: [PATCH 17/86] V8: Angular Divorce: equals (#7860) * Bye bye angular.equals & use Utilities.equals which is still using angular.equals under the hood for now * Update umbvariantcontenteditors.directive.js Co-authored-by: Nathan Woulfe --- .../components/forms/checklistmodel.directive.js | 12 ++++++------ .../components/imaging/umbimagegravity.directive.js | 2 +- .../directives/validation/valserver.directive.js | 2 +- .../propertyeditors/grid/editors/media.controller.js | 2 +- .../test/lib/angular/angular-mocks.js | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js index d944989bab..491dff3a41 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js @@ -11,7 +11,7 @@ angular.module('umbraco.directives') function contains(arr, item) { if (Utilities.isArray(arr)) { for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { + if (Utilities.equals(arr[i], item)) { return true; } } @@ -19,23 +19,23 @@ angular.module('umbraco.directives') return false; } - // add + // add function add(arr, item) { arr = Utilities.isArray(arr) ? arr : []; for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { + if (Utilities.equals(arr[i], item)) { return arr; } - } + } arr.push(item); return arr; - } + } // remove function remove(arr, item) { if (Utilities.isArray(arr)) { for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { + if (Utilities.equals(arr[i], item)) { arr.splice(i, 1); break; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js index dfa58f34f8..fd9a236f87 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js @@ -160,7 +160,7 @@ function onChanges(changes) { if (changes.center && !changes.center.isFirstChange() && changes.center.currentValue - && !angular.equals(changes.center.currentValue, changes.center.previousValue)) { + && !Utilities.equals(changes.center.currentValue, changes.center.previousValue)) { //when center changes update the dimensions setDimensions(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index ea6087d4e9..4180457792 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -72,7 +72,7 @@ function valServer(serverValidationManager) { return modelCtrl.$modelValue; }, function (newValue, oldValue) { - if (!newValue || angular.equals(newValue, oldValue)) { + if (!newValue || Utilities.equals(newValue, oldValue)) { return; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 0ed3ea6419..c2c4d8081c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -64,7 +64,7 @@ angular.module("umbraco") }; $scope.$watch('control.value', function(newValue, oldValue) { - if(angular.equals(newValue, oldValue)){ + if(Utilities.equals(newValue, oldValue)){ return; // simply skip that } diff --git a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js index 9760a91c7c..1c13b21fb1 100644 --- a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js +++ b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js @@ -171,7 +171,7 @@ angular.mock.$Browser.prototype = { } } } else { - if (!angular.equals(this.cookieHash, this.lastCookieHash)) { + if (!Utilities.equals(this.cookieHash, this.lastCookieHash)) { this.lastCookieHash = Utilities.copy(this.cookieHash); this.cookieHash = Utilities.copy(this.cookieHash); } @@ -1379,7 +1379,7 @@ function MockHttpExpectation(method, url, data, headers) { this.matchHeaders = function (h) { if (Utilities.isUndefined(headers)) return true; if (angular.isFunction(headers)) return headers(h); - return angular.equals(headers, h); + return Utilities.equals(headers, h); }; this.matchData = function (d) { From 9601dead7ba386cf087a9073368ea832fa2d77d4 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 19 Jan 2021 22:40:21 +0000 Subject: [PATCH 18/86] V8: Angular Divorce: isFunction (#7861) * Bye bye angular.isFunction & use Utilities.isFunction * Utilities.js is not available on first load - replace isFunction detection with underscore equivalent * found a stray isFunction Co-authored-by: Kenn Jacobsen Co-authored-by: Nathan Woulfe --- src/Umbraco.Web.UI.Client/src/app.js | 2 +- .../components/tree/umbtree.directive.js | 4 ++-- .../components/umbaceeditor.directive.js | 6 +++--- .../components/umbminilistview.directive.js | 2 +- .../validation/valpropertyvalidator.directive.js | 6 +++--- .../src/common/services/angularhelper.service.js | 4 ++-- .../services/contenteditinghelper.service.js | 6 +++--- .../src/common/services/events.service.js | 2 +- .../src/common/services/tree.service.js | 12 ++++++------ .../src/installer/installer.service.js | 4 ++-- .../treepicker/treepicker.controller.js | 8 ++++---- .../test/lib/angular/angular-mocks.js | 16 ++++++++-------- 12 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/app.js b/src/Umbraco.Web.UI.Client/src/app.js index 74a7008901..645296f0e0 100644 --- a/src/Umbraco.Web.UI.Client/src/app.js +++ b/src/Umbraco.Web.UI.Client/src/app.js @@ -91,6 +91,6 @@ angular.module("umbraco.viewcache", []) // be able to configure angular values in the Default.cshtml // view which is much easier to do that configuring values by injecting them in the back office controller // to follow through to the js initialization stuff -if (angular.isFunction(document.angularReady)) { +if (_.isFunction(document.angularReady)) { document.angularReady.apply(this, [app]); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index a412f73c5a..e27ae11e6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -409,8 +409,8 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use //load the tree loadTree().then(function () { //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - if (args && args.onLoaded && angular.isFunction(args.onLoaded)) { + //like normal JS promises we could do resolve(...).then() + if (args && args.onLoaded && Utilities.isFunction(args.onLoaded)) { args.onLoaded(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index 070ffd4ddd..5e1f2489e6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -82,7 +82,7 @@ if (Utilities.isDefined(opts.firstLineNumber)) { if (Utilities.isNumber(opts.firstLineNumber)) { session.setOption('firstLineNumber', opts.firstLineNumber); - } else if (angular.isFunction(opts.firstLineNumber)) { + } else if (Utilities.isFunction(opts.firstLineNumber)) { session.setOption('firstLineNumber', opts.firstLineNumber()); } } @@ -116,7 +116,7 @@ // onLoad callbacks angular.forEach(opts.callbacks, function(cb) { - if (angular.isFunction(cb)) { + if (Utilities.isFunction(cb)) { cb(acee); } }); @@ -208,7 +208,7 @@ if (Utilities.isDefined(callback)) { scope.$evalAsync(function() { - if (angular.isFunction(callback)) { + if (Utilities.isFunction(callback)) { callback(args); } else { throw new Error('ui-ace use a function as callback.'); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 3865ffcdae..783cd7f90a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -92,7 +92,7 @@ // advanced item filtering is handled here if (scope.entityTypeFilter && scope.entityTypeFilter.filter && scope.entityTypeFilter.filterAdvanced) { - var filtered = angular.isFunction(scope.entityTypeFilter.filter) + var filtered = Utilities.isFunction(scope.entityTypeFilter.filter) ? _.filter(miniListView.children, scope.entityTypeFilter.filter) : _.where(miniListView.children, scope.entityTypeFilter.filter); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js index 37303d22ad..d66e4bd2af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js @@ -28,9 +28,9 @@ function valPropertyValidator(serverValidationManager) { var modelCtrl = ctrls[0]; var propCtrl = ctrls.length > 1 ? ctrls[1] : null; - - // Check whether the scope has a valPropertyValidator method - if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) { + + // Check whether the scope has a valPropertyValidator method + if (!scope.valPropertyValidator || !Utilities.isFunction(scope.valPropertyValidator)) { throw new Error('val-property-validator directive must specify a function to call'); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index 7e7f804656..32d6a0c340 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -147,12 +147,12 @@ function angularHelper($q) { */ safeApply: function (scope, fn) { if (scope.$$phase || (scope.$root && scope.$root.$$phase)) { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { fn(); } } else { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { scope.$apply(fn); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 6d41ea087d..928b8c8714 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -589,7 +589,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //instead of having a property editor $watch their expression to check if it has // been updated, instead we'll check for the existence of a special method on their model // and just call it. - if (angular.isFunction(origProp.onValueChanged)) { + if (Utilities.isFunction(origProp.onValueChanged)) { //send the newVal + oldVal origProp.onValueChanged(origProp.value, origVal); } @@ -643,7 +643,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // In this case we need to detect what properties have changed and re-bind them with the server data. - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + if (args.rebindCallback && Utilities.isFunction(args.rebindCallback)) { args.rebindCallback(); } @@ -690,7 +690,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // In this case we need to detect what properties have changed and re-bind them with the server data. - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + if (args.rebindCallback && Utilities.isFunction(args.rebindCallback)) { args.rebindCallback(); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js index 965ac3d635..c7ef5bd28f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js @@ -34,7 +34,7 @@ function eventsService($q, $rootScope) { /** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */ unsubscribe: function(handle) { - if (angular.isFunction(handle)) { + if (Utilities.isFunction(handle)) { handle(); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 9ba4d2964b..9970995a28 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -235,7 +235,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } }); } - else if (args.filter && angular.isFunction(args.filter)) { + else if (args.filter && Utilities.isFunction(args.filter)) { //if a filter is supplied a cacheKey must be supplied as well if (!args.cacheKey) { throw "args.cacheKey is required if args.filter is supplied"; @@ -315,7 +315,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS args.node.hasChildren = true; //Since we've removed the children & reloaded them, we need to refresh the UI now because the tree node UI doesn't operate on normal angular $watch since that will be pretty slow - if (angular.isFunction(args.node.updateNodeData)) { + if (Utilities.isFunction(args.node.updateNodeData)) { args.node.updateNodeData(args.node); } } @@ -349,7 +349,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @param {object} treeNode the node to remove */ removeNode: function (treeNode) { - if (!angular.isFunction(treeNode.parent)) { + if (!Utilities.isFunction(treeNode.parent)) { return; } @@ -509,7 +509,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (current.metaData && current.metaData["treeAlias"]) { root = current; } - else if (angular.isFunction(current.parent)) { + else if (Utilities.isFunction(current.parent)) { //we can only continue if there is a parent() method which means this // tree node was loaded in as part of a real tree, not just as a single tree // node from the server. @@ -706,7 +706,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //to fire, instead we're just going to replace all the properties of this node. //there should always be a method assigned but we'll check anyways - if (angular.isFunction(node.parent().children[index].updateNodeData)) { + if (Utilities.isFunction(node.parent().children[index].updateNodeData)) { node.parent().children[index].updateNodeData(found); } else { @@ -741,7 +741,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!node) { throw "node cannot be null"; } - if (!angular.isFunction(node.parent)) { + if (!Utilities.isFunction(node.parent)) { throw "node.parent is not a function, the path cannot be resolved"; } diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 05c391c3e7..74858d652e 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -85,12 +85,12 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco /** Have put this here because we are not referencing our other modules */ function safeApply (scope, fn) { if (scope.$$phase || scope.$root.$$phase) { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { fn(); } } else { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { scope.$apply(fn); } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index c519a1d4fa..0c5fe9af1b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -173,7 +173,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", $scope.model.filterAdvanced = false; //used advanced filtering - if (angular.isFunction($scope.model.filter)) { + if (Utilities.isFunction($scope.model.filter)) { $scope.model.filterAdvanced = true; } else if (Utilities.isObject($scope.model.filter)) { @@ -189,9 +189,9 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if ($scope.model.filter.startsWith("{")) { $scope.model.filterAdvanced = true; - if ($scope.model.filterByMetadata && !angular.isFunction($scope.model.filter)) + if ($scope.model.filterByMetadata && !Utilities.isFunction($scope.model.filter)) { - var filter = angular.fromJson($scope.model.filter); + var filter = Utilities.fromJson($scope.model.filter); $scope.model.filter = function (node){ return _.isMatch(node.metaData, filter);}; } else @@ -456,7 +456,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if ($scope.model.filterAdvanced) { //filter either based on a method or an object - var filtered = angular.isFunction($scope.model.filter) + var filtered = Utilities.isFunction($scope.model.filter) ? _.filter(nodes, $scope.model.filter) : _.where(nodes, $scope.model.filter); diff --git a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js index 1c13b21fb1..c9dde30c4f 100644 --- a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js +++ b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js @@ -699,7 +699,7 @@ angular.mock.dump = function (object) { }); out = '[ ' + out.join(', ') + ' ]'; } else if (Utilities.isObject(object)) { - if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + if (Utilities.isFunction(object.$eval) && Utilities.isFunction(object.$apply)) { out = serializeScope(object); } else if (object instanceof Error) { out = object.stack || ('' + object.name + ': ' + object.message); @@ -927,7 +927,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { responsesPush = angular.bind(responses, responses.push); function createResponse(status, data, headers) { - if (angular.isFunction(status)) return status; + if (Utilities.isFunction(status)) return status; return function () { return angular.isNumber(status) @@ -943,7 +943,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { wasExpected = false; function prettyPrint(data) { - return (Utilities.isString(data) || angular.isFunction(data) || data instanceof RegExp) + return (Utilities.isString(data) || Utilities.isFunction(data) || data instanceof RegExp) ? data : angular.toJson(data); } @@ -1372,20 +1372,20 @@ function MockHttpExpectation(method, url, data, headers) { this.matchUrl = function (u) { if (!url) return true; - if (angular.isFunction(url.test)) return url.test(u); + if (Utilities.isFunction(url.test)) return url.test(u); return url == u; }; this.matchHeaders = function (h) { if (Utilities.isUndefined(headers)) return true; - if (angular.isFunction(headers)) return headers(h); + if (Utilities.isFunction(headers)) return headers(h); return Utilities.equals(headers, h); }; this.matchData = function (d) { if (Utilities.isUndefined(data)) return true; - if (data && angular.isFunction(data.test)) return data.test(d); - if (data && !Utilities.isString(data)) return angular.toJson(data) == d; + if (data && Utilities.isFunction(data.test)) return data.test(d); + if (data && !Utilities.isString(data)) return Utilities.toJson(data) == d; return data == d; }; @@ -1484,7 +1484,7 @@ angular.mock.$TimeoutDecorator = function ($delegate, $browser) { function formatPendingTasksAsString(tasks) { var result = []; - angular.forEach(tasks, function (task) { + Utilities.forEach(tasks, function (task) { result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); }); From c501ef0b68f135811ca9000e4f091c53851b5db4 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 19 Jan 2021 18:17:48 +0100 Subject: [PATCH 19/86] Inject missing localization service --- .../directives/components/forms/umbradiobutton.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js index 3232ed7f34..8c7157c414 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -40,7 +40,7 @@ (function () { 'use strict'; - function UmbRadiobuttonController($timeout) { + function UmbRadiobuttonController($timeout, localizationService) { var vm = this; From 14961e670d4a2cbc6341d18110b52218dade80b9 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 19 Jan 2021 23:12:46 +0000 Subject: [PATCH 20/86] Improvements to validation and accessibility of relationship create view (#9154) * Improved validation on save for relationships and also added support for accesibilty * Moved the localisation into the radiobutton and added support for localisation using lavel-key in the UmbRadioButtonController Co-authored-by: Nathan Woulfe --- .../src/views/relationtypes/create.html | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html b/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html index 4d8d94e1aa..93debb5507 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html @@ -5,48 +5,49 @@ ng-submit="vm.createRelationType()"> - - + + +

      • -
      • -
      - - + class="umb-property-editor umb-dropdown" required> - - + class="umb-property-editor umb-dropdown" required> From 877640e60071379e8a8f52e4f432df9b0417d334 Mon Sep 17 00:00:00 2001 From: andreymkarandashov Date: Fri, 15 Jan 2021 10:00:11 +0200 Subject: [PATCH 21/86] up swedish lang config --- src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index f2cdc54657..511765e604 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -243,10 +243,10 @@ Installera Umbraco Forms - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes + Stanna + Ignorera ändringar + Du har ändringar som inte är sparade + Vill du verkligen lämna sidan? Du har ändringar som inte är sparade Klar @@ -741,8 +741,8 @@ Flikar - Sort order - Creation date + Sortera ordningen + Skapandedatum Sortering klar Välj i vilken ordning du vill ha sidorna genom att dra dem upp eller ner i listan. Du kan också klicka på kolumnrubrikerna för att sortera grupper av sidor @@ -815,12 +815,12 @@ Bild - Macro + Makro Lägg till - Choose layout + Välj utformning Lägg till rad - Add content - Drop content + Lägg till innehåll + Släpp innehåll Indholdet er ikke tilladt her Indholdet er tilladt her Klicka för att lägga in @@ -847,7 +847,7 @@ Alternativt fält Alternativ text - Casing + Hölje Välj fält Konvertera radbrytningar Byter radbrytningar mot html-taggen &lt;br&gt; @@ -963,7 +963,7 @@ Senast utlåst Senast inloggad Lösenordet ändrades - Login + Logga in Startnod i mediabiblioteket Begränsa media sectionen till en specifik startnod Media startnoder From 6d712b0d16b1f2d85c53d020fa68fb41bb0d3d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 20 Jan 2021 14:11:12 +0100 Subject: [PATCH 22/86] Call rebindCallback on errors as well (#9688) --- .../components/content/edit.controller.js | 8 ++++++-- .../services/contenteditinghelper.service.js | 20 ++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index f2dc0622c7..1e1d37af63 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -271,7 +271,7 @@ * @param {any} app the active content app */ function createButtons(content) { - + var isBlueprint = content.isBlueprint; if ($scope.page.isNew && $location.path().search(/contentBlueprints/i) !== -1) { @@ -474,6 +474,10 @@ return $q.when(data); }, function (err) { + + //needs to be manually set for infinite editing mode + $scope.page.isNew = false; + syncTreeNode($scope.content, $scope.content.path); resetNestedFieldValiation(fieldsToRollback); @@ -981,7 +985,7 @@ $scope.appChanged = function (activeApp) { $scope.activeApp = activeApp; - + _.forEach($scope.content.apps, function (app) { app.active = false; if (app.alias === $scope.activeApp.alias) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 928b8c8714..b301960eab 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -84,7 +84,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //when true, the url will change but it won't actually re-route //this is merely here for compatibility, if only the content/media/members used this service we'd prob be ok but tons of editors //use this service unfortunately and probably packages too. - args.softRedirect = false; + args.softRedirect = false; } @@ -123,7 +123,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt self.handleSaveError({ showNotifications: args.showNotifications, softRedirect: args.softRedirect, - err: err + err: err, + rebindCallback: function () { + // if the error contains data, we want to map that back as we want to continue editing this save. Especially important when the content is new as the returned data will contain ID etc. + if(err.data) { + rebindCallback.apply(self, [args.content, err.data]); + } + } }); //update editor state to what is current @@ -298,7 +304,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt } // if publishing is allowed also allow schedule publish - // we add this manually becuase it doesn't have a permission so it wont + // we add this manually becuase it doesn't have a permission so it wont // get picked up by the loop through permissions if (_.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); @@ -622,7 +628,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!args.err) { throw "args.err cannot be null"; } - + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -640,7 +646,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!this.redirectToCreatedContent(args.err.data.id, args.softRedirect) || args.softRedirect) { // If we are not redirecting it's because this is not newly created content, else in some cases we are - // soft-redirecting which means the URL will change but the route wont (i.e. creating content). + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && Utilities.isFunction(args.rebindCallback)) { @@ -687,7 +693,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id, args.softRedirect) || args.softRedirect) { // If we are not redirecting it's because this is not newly created content, else in some cases we are - // soft-redirecting which means the URL will change but the route wont (i.e. creating content). + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && Utilities.isFunction(args.rebindCallback)) { @@ -723,7 +729,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt navigationService.setSoftRedirect(); } //change to new path - $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); + $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this $location.replace(); return true; From da0c82aabdfdb8ecf78f6abf7e890d06534e972b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 20 Jan 2021 14:26:40 +0100 Subject: [PATCH 23/86] Call rebindCallback on errors as well (#9690) --- .../directives/components/content/edit.controller.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 1e1d37af63..3734b22bf3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -466,7 +466,7 @@ syncTreeNode($scope.content, data.path, false, args.reloadChildren); - eventsService.emit("content.saved", { content: $scope.content, action: args.action }); + eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: true }); resetNestedFieldValiation(fieldsToRollback); ensureDirtyIsSetIfAnyVariantIsDirty(); @@ -475,11 +475,14 @@ }, function (err) { - //needs to be manually set for infinite editing mode - $scope.page.isNew = false; syncTreeNode($scope.content, $scope.content.path); + if (err.status === 400 && err.data) { + // content was saved but is invalid. + eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: false }); + } + resetNestedFieldValiation(fieldsToRollback); return $q.reject(err); From 0c9b4565f1d71ef8353d8057eba4cc2c119e505c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 13 Jan 2021 11:57:21 +0100 Subject: [PATCH 24/86] updates the css to support items with long titles (cherry picked from commit 8e64fe54b1213bef9e3218064d4b6ba74ebe04d3) --- .../src/less/components/card.less | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index a1a4b4bc5e..7174c0f290 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -104,19 +104,18 @@ margin: 0 auto; list-style: none; width: 100%; - display: flex; flex-flow: row wrap; justify-content: flex-start; } .umb-card-grid li { - font-size: 12px; text-align: center; box-sizing: border-box; position: relative; width: 100px; + margin-bottom: 5px; } .umb-card-grid.-six-in-row li { @@ -142,18 +141,20 @@ .umb-card-grid .umb-card-grid-item { position: relative; display: block; - width: 100%; - //height: 100%; - padding-top: 100%; + width: 100%; + height: 100%; + padding: 10px 5px; border-radius: @baseBorderRadius * 2; transition: background-color 120ms; + font-size: 13px; + line-height: 1.3em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; > span { - position: absolute; - top: 10px; - bottom: 10px; - left: 10px; - right: 10px; + position: relative; display: flex; align-items: center; justify-content: center; From e0f00a87547ab8f70e4afd1f29fec23bb251249c Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Thu, 21 Jan 2021 01:21:31 +0000 Subject: [PATCH 25/86] Added missing alt attributes to the images on the login screen and in avatars (#9597) * Added missing alt text for login and avatar * Added localisation for the avatar copy * update alt avatar tag issue + translation Co-authored-by: Michael --- .../components/umbavatar.directive.js | 17 ++++++++++++++++- .../views/components/application/umb-login.html | 2 +- .../src/views/components/umb-avatar.html | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 1 + 7 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js index 7dd2f0d7a3..321cd8a59d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js @@ -50,18 +50,20 @@ Use this directive to render an avatar. (function() { 'use strict'; - function AvatarDirective() { + function AvatarDirective(localizationService) { function link(scope, element, attrs, ctrl) { var eventBindings = []; scope.initials = ""; + scope.avatarAlt = ""; function onInit() { if (!scope.unknownChar) { scope.unknownChar = "?"; } scope.initials = getNameInitials(scope.name); + setAvatarAlt(scope.name); } function getNameInitials(name) { @@ -77,10 +79,23 @@ Use this directive to render an avatar. return null; } + function setAvatarAlt(name) { + if (name) { + localizationService + .localize('general_avatar') + .then(function(data) { + scope.avatarAlt = data + ' ' + name; + } + ); + } + scope.avatarAlt = null; + } + eventBindings.push(scope.$watch('name', function (newValue, oldValue) { if (newValue === oldValue) { return; } if (oldValue === undefined || newValue === undefined) { return; } scope.initials = getNameInitials(newValue); + setAvatarAlt(newValue); })); onInit(); 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 46d3a5c17d..1e570b4af6 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 @@ -5,7 +5,7 @@ diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index f21d673dce..97fc223cc3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -797,6 +797,7 @@ Articles Videos Installing + Avatar for Blue 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 7feb88d654..1456cce9f0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -805,6 +805,7 @@ Articles Videos Installing + Avatar for Blue diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index f3d981ddfb..75377f54c9 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -762,6 +762,7 @@ actuel Intégrer sélectionné + Avatar de Bleu diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index e81874d4c1..11049a279c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -806,6 +806,7 @@ Artikels Videos Installeren + Avatar van Blauw From 0bd4dced0b3e9205660114406b7e814f817179c7 Mon Sep 17 00:00:00 2001 From: Chad Date: Fri, 22 Jan 2021 15:02:25 +1300 Subject: [PATCH 26/86] Improve performance and reduce memory use and reduce array allocations by reusing char[] (#9166) * Reduce array allocations by reusing char[] * don't hide .Equals() * Reduce memory use by reducing array allocations * Revert "Reduce memory use by reducing array allocations" This reverts commit faf6b60234167f5443435243eb52187ab07d514c. * reuse char[] for string.split() to avoid params [] allocation --- src/Umbraco.Core/Composing/TypeFinder.cs | 2 +- .../Configuration/GlobalSettingsExtensions.cs | 2 +- .../ContentSectionExtensions.cs | 2 +- src/Umbraco.Core/Constants-CharArrays.cs | 138 ++++++++++++++++++ src/Umbraco.Core/DictionaryExtensions.cs | 2 +- src/Umbraco.Core/GuidUdi.cs | 2 +- src/Umbraco.Core/HttpContextExtensions.cs | 2 +- src/Umbraco.Core/IO/IOHelper.cs | 10 +- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 10 +- src/Umbraco.Core/IO/ShadowFileSystem.cs | 4 +- .../Migrations/Install/DatabaseBuilder.cs | 2 +- .../V_8_0_0/PropertyEditorsMigrationBase.cs | 2 +- .../Models/PathValidationExtensions.cs | 4 +- .../Packaging/PackageDataInstallation.cs | 4 +- .../Packaging/PackageDefinitionXmlParser.cs | 14 +- .../DefinitionFactory.cs | 2 +- .../Implement/ContentRepositoryBase.cs | 2 +- .../Implement/ContentTypeRepositoryBase.cs | 2 +- .../Implement/DocumentRepository.cs | 2 +- .../MicrosoftSqlSyntaxProviderBase.cs | 2 +- .../SqlSyntax/SqlCeSyntaxProvider.cs | 2 +- .../SqlSyntax/SqlServerSyntaxProvider.cs | 2 +- .../SqlSyntax/SqlSyntaxProviderBase.cs | 2 +- .../ValueConverters/SliderValueConverter.cs | 2 +- .../Services/ContentServiceExtensions.cs | 2 +- .../Services/Implement/ContentService.cs | 2 +- ...peServiceBaseOfTRepositoryTItemTService.cs | 2 +- .../Services/Implement/DataTypeService.cs | 2 +- .../Services/Implement/MediaService.cs | 2 +- .../Services/Implement/NotificationService.cs | 4 +- .../Services/Implement/PublicAccessService.cs | 2 +- .../Services/UserServiceExtensions.cs | 2 +- src/Umbraco.Core/StringExtensions.cs | 12 +- src/Umbraco.Core/StringUdi.cs | 4 +- .../Strings/Css/StylesheetRule.cs | 2 +- src/Umbraco.Core/Strings/Diff.cs | 2 +- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 8 +- src/Umbraco.Core/Udi.cs | 2 +- src/Umbraco.Core/UdiGetterExtensions.cs | 6 +- src/Umbraco.Core/UdiRange.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Core/UriExtensions.cs | 6 +- src/Umbraco.Core/Xml/XmlHelper.cs | 2 +- .../Building/TextBuilder.cs | 2 +- .../Cache/DistributedCacheBinder.cs | 3 +- .../Editors/BackOfficeController.cs | 2 +- .../Editors/BackOfficeServerVariables.cs | 6 +- .../Binders/ContentModelBinderHelper.cs | 4 +- src/Umbraco.Web/Editors/CodeFileController.cs | 5 +- .../Editors/ContentTypeController.cs | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 19 +-- src/Umbraco.Web/Editors/MacrosController.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 4 +- .../Editors/PackageInstallController.cs | 4 +- src/Umbraco.Web/Editors/TinyMceController.cs | 2 +- src/Umbraco.Web/Editors/TourController.cs | 5 +- src/Umbraco.Web/Editors/UsersController.cs | 2 +- .../FormDataCollectionExtensions.cs | 2 +- src/Umbraco.Web/HttpCookieExtensions.cs | 4 +- src/Umbraco.Web/Macros/MacroRenderer.cs | 2 +- .../PublishedContentHashtableConverter.cs | 2 +- .../Media/Exif/ExifPropertyFactory.cs | 5 +- src/Umbraco.Web/Media/Exif/MathEx.cs | 6 +- .../Media/UploadAutoFillProperties.cs | 4 +- src/Umbraco.Web/ModelStateExtensions.cs | 4 +- .../Models/Mapping/ContentMapDefinition.cs | 2 +- .../Mapping/ContentTypeMapDefinition.cs | 2 +- src/Umbraco.Web/Models/Trees/TreeNode.cs | 2 +- .../Mvc/MemberAuthorizeAttribute.cs | 6 +- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 2 +- .../MediaPickerPropertyEditor.cs | 2 +- .../MultiNodeTreePickerPropertyEditor.cs | 2 +- .../PropertyEditors/TagsPropertyEditor.cs | 2 +- .../UploadFileTypeValidator.cs | 2 +- .../MediaPickerValueConverter.cs | 2 +- .../MultiNodeTreePickerValueConverter.cs | 2 +- .../PublishedCache/NuCache/ContentCache.cs | 2 +- src/Umbraco.Web/RoutableDocumentFilter.cs | 8 +- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 8 +- .../Routing/ContentFinderByUrlAlias.cs | 2 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 5 +- src/Umbraco.Web/Routing/DomainUtilities.cs | 4 +- .../Routing/NotFoundHandlerHelper.cs | 2 +- .../Routing/UrlProviderExtensions.cs | 2 +- src/Umbraco.Web/Scheduling/KeepAlive.cs | 2 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 6 +- src/Umbraco.Web/Services/DashboardService.cs | 6 +- .../Trees/ContentTreeController.cs | 2 +- .../Trees/ContentTreeControllerBase.cs | 2 +- .../Trees/FileSystemTreeController.cs | 2 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 2 +- src/Umbraco.Web/Trees/UrlHelperExtensions.cs | 2 +- src/Umbraco.Web/UmbracoInjectedModule.cs | 6 +- src/Umbraco.Web/UriUtility.cs | 6 +- ...EnsureUserPermissionForContentAttribute.cs | 2 +- .../EnsureUserPermissionForMediaAttribute.cs | 2 +- .../WebApi/MemberAuthorizeAttribute.cs | 4 +- 97 files changed, 310 insertions(+), 167 deletions(-) create mode 100644 src/Umbraco.Core/Constants-CharArrays.cs diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 5ad1e43580..394d9480ae 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Composing var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions]; return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); + : s.Split(Constants.CharArrays.Comma).Select(x => x.Trim()).ToArray(); } } diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index bc76caacee..e8fea7f27d 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Configuration var path = globalSettings.Path; if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518 path = path.Substring(SystemDirectories.Root.Length); - return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower(); + return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-').Trim().ToLower(); } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs index 82cc5928cf..b3980d236a 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { if (contentConfig == null) throw new ArgumentNullException(nameof(contentConfig)); if (extension == null) return false; - extension = extension.TrimStart('.'); + extension = extension.TrimStart(Constants.CharArrays.Period); return contentConfig.ImageFileTypes.InvariantContains(extension); } diff --git a/src/Umbraco.Core/Constants-CharArrays.cs b/src/Umbraco.Core/Constants-CharArrays.cs new file mode 100644 index 0000000000..2f8292b4a4 --- /dev/null +++ b/src/Umbraco.Core/Constants-CharArrays.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Char Arrays to avoid allocations + /// + public static class CharArrays + { + /// + /// Char array containing only / + /// + public static readonly char[] ForwardSlash = new char[] { '/' }; + + /// + /// Char array containing only \ + /// + public static readonly char[] Backslash = new char[] { '\\' }; + + /// + /// Char array containing only ' + /// + public static readonly char[] SingleQuote = new char[] { '\'' }; + + /// + /// Char array containing only " + /// + public static readonly char[] DoubleQuote = new char[] { '\"' }; + + + /// + /// Char array containing ' " + /// + public static readonly char[] DoubleQuoteSingleQuote = new char[] { '\"', '\'' }; + + /// + /// Char array containing only _ + /// + public static readonly char[] Underscore = new char[] { '_' }; + + /// + /// Char array containing \n \r + /// + public static readonly char[] LineFeedCarriageReturn = new char[] { '\n', '\r' }; + + + /// + /// Char array containing \n + /// + public static readonly char[] LineFeed = new char[] { '\n' }; + + /// + /// Char array containing only , + /// + public static readonly char[] Comma = new char[] { ',' }; + + /// + /// Char array containing only & + /// + public static readonly char[] Ampersand = new char[] { '&' }; + + /// + /// Char array containing only \0 + /// + public static readonly char[] NullTerminator = new char[] { '\0' }; + + /// + /// Char array containing only . + /// + public static readonly char[] Period = new char[] { '.' }; + + /// + /// Char array containing only ~ + /// + public static readonly char[] Tilde = new char[] { '~' }; + /// + /// Char array containing ~ / + /// + public static readonly char[] TildeForwardSlash = new char[] { '~', '/' }; + + /// + /// Char array containing only ? + /// + public static readonly char[] QuestionMark = new char[] { '?' }; + + /// + /// Char array containing ? & + /// + public static readonly char[] QuestionMarkAmpersand = new char[] { '?', '&' }; + + /// + /// Char array containing XML 1.1 whitespace chars + /// + public static readonly char[] XmlWhitespaceChars = new char[] { ' ', '\t', '\r', '\n' }; + + /// + /// Char array containing only the Space char + /// + public static readonly char[] Space = new char[] { ' ' }; + + /// + /// Char array containing only ; + /// + public static readonly char[] Semicolon = new char[] { ';' }; + + /// + /// Char array containing a comma and a space + /// + public static readonly char[] CommaSpace = new char[] { ',', ' ' }; + + /// + /// Char array containing _ - + /// + public static readonly char[] UnderscoreDash = new char[] { '_', '-' }; + + /// + /// Char array containing = + /// + public static readonly char[] EqualsChar = new char[] { '=' }; + + /// + /// Char array containing > + /// + public static readonly char[] GreaterThan = new char[] { '>' }; + + /// + /// Char array containing | + /// + public static readonly char[] VerticalTab = new char[] { '|' }; + } + } +} diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs index d9e998dbd1..88a042dbd5 100644 --- a/src/Umbraco.Core/DictionaryExtensions.cs +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -253,7 +253,7 @@ namespace Umbraco.Core { builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); } - return builder.ToString().TrimEnd('&'); + return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand); } /// The get entry ignore case. diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs index 93f670cd01..3743f6f108 100644 --- a/src/Umbraco.Core/GuidUdi.cs +++ b/src/Umbraco.Core/GuidUdi.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core : base(uriValue) { Guid guid; - if (Guid.TryParse(uriValue.AbsolutePath.TrimStart('/'), out guid) == false) + if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out guid) == false) throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID."); Guid = guid; diff --git a/src/Umbraco.Core/HttpContextExtensions.cs b/src/Umbraco.Core/HttpContextExtensions.cs index 22eb4d1917..e9ac1aa861 100644 --- a/src/Umbraco.Core/HttpContextExtensions.cs +++ b/src/Umbraco.Core/HttpContextExtensions.cs @@ -50,7 +50,7 @@ namespace Umbraco.Core if (string.IsNullOrEmpty(ipAddress)) return request.UserHostAddress; - var addresses = ipAddress.Split(','); + var addresses = ipAddress.Split(Constants.CharArrays.Comma); if (addresses.Length != 0) return addresses[0]; diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 53aa5a8179..8661f73fb1 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.IO retval = virtualPath.Replace("~", SystemDirectories.Root); if (virtualPath.StartsWith("/") && virtualPath.StartsWith(SystemDirectories.Root) == false) - retval = SystemDirectories.Root + "/" + virtualPath.TrimStart('/'); + retval = SystemDirectories.Root + "/" + virtualPath.TrimStart(Constants.CharArrays.ForwardSlash); return retval; } @@ -98,11 +98,11 @@ namespace Umbraco.Core.IO if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) return HostingEnvironment.MapPath(path); else - return HostingEnvironment.MapPath("~/" + path.TrimStart('/')); + return HostingEnvironment.MapPath("~/" + path.TrimStart(Constants.CharArrays.ForwardSlash)); } var root = GetRootDirectorySafe(); - var newPath = path.TrimStart('~', '/').Replace('/', IOHelper.DirSepChar); + var newPath = path.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', IOHelper.DirSepChar); var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; return retval; @@ -121,7 +121,7 @@ namespace Umbraco.Core.IO if (string.IsNullOrEmpty(retval)) retval = standardPath; - return retval.TrimEnd('/'); + return retval.TrimEnd(Constants.CharArrays.ForwardSlash); } internal static string ReturnPath(string settingsKey, string standardPath) @@ -188,7 +188,7 @@ namespace Umbraco.Core.IO internal static bool VerifyFileExtension(string filePath, IEnumerable validFileExtensions) { var ext = Path.GetExtension(filePath); - return ext != null && validFileExtensions.Contains(ext.TrimStart('.')); + return ext != null && validFileExtensions.Contains(ext.TrimStart(Constants.CharArrays.Period)); } public static bool PathStartsWith(string path, string root, char separator) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 5a216e8554..a833ba43af 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.IO _rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar); _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/'); + _rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd(Constants.CharArrays.ForwardSlash); } public PhysicalFileSystem(string rootPath, string rootUrl) @@ -54,7 +54,7 @@ namespace Umbraco.Core.IO _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/'); + _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash); } /// @@ -259,12 +259,12 @@ namespace Umbraco.Core.IO // if it starts with the root URL, strip it and trim the starting slash to make it relative // eg "/Media/1234/img.jpg" => "1234/img.jpg" if (IOHelper.PathStartsWith(path, _rootUrl, '/')) - return path.Substring(_rootUrl.Length).TrimStart('/'); + return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); // if it starts with the root path, strip it and trim the starting slash to make it relative // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" if (IOHelper.PathStartsWith(path, _rootPathFwd, '/')) - return path.Substring(_rootPathFwd.Length).TrimStart('/'); + return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); // unchanged - what else? return path; @@ -326,7 +326,7 @@ namespace Umbraco.Core.IO /// All separators are forward-slashes. public string GetUrl(string path) { - path = EnsureUrlSeparatorChar(path).Trim('/'); + path = EnsureUrlSeparatorChar(path).Trim(Constants.CharArrays.ForwardSlash); return _rootUrl + "/" + path; } diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index 84ff1b428b..0e9390d13f 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -182,7 +182,7 @@ namespace Umbraco.Core.IO if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - var parts = normPath.Split('/'); + var parts = normPath.Split(Constants.CharArrays.ForwardSlash); for (var i = 0; i < parts.Length - 1; i++) { var dirPath = string.Join("/", parts.Take(i + 1)); @@ -297,7 +297,7 @@ namespace Umbraco.Core.IO if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - var parts = normPath.Split('/'); + var parts = normPath.Split(Constants.CharArrays.ForwardSlash); for (var i = 0; i < parts.Length - 1; i++) { var dirPath = string.Join("/", parts.Take(i + 1)); diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index 71faf42daf..fb9b8af46d 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -354,7 +354,7 @@ namespace Umbraco.Core.Migrations.Install var sqlCeDatabaseExists = false; if (dbIsSqlCe) { - var parts = databaseSettings.ConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var parts = databaseSettings.ConnectionString.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries); var dataSourcePart = parts.FirstOrDefault(x => x.InvariantStartsWith("Data Source=")); if (dataSourcePart != null) { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs index 605f8a9eed..423ba71401 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 protected int[] ConvertStringValues(string val) { - var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var splitVals = val.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); var intVals = splitVals .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) diff --git a/src/Umbraco.Core/Models/PathValidationExtensions.cs b/src/Umbraco.Core/Models/PathValidationExtensions.cs index 2db954b316..4ade88f621 100644 --- a/src/Umbraco.Core/Models/PathValidationExtensions.cs +++ b/src/Umbraco.Core/Models/PathValidationExtensions.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Models if (entity.Path.IsNullOrWhiteSpace()) throw new InvalidDataException($"The content item {entity.NodeId} has an empty path: {entity.Path} with parentID: {entity.ParentId}"); - var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (pathParts.Length < 2) { //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id @@ -53,7 +53,7 @@ namespace Umbraco.Core.Models if (entity.Path.IsNullOrWhiteSpace()) return false; - var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (pathParts.Length < 2) { //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs index 3553e224cb..4d1f12baaa 100644 --- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -522,7 +522,7 @@ namespace Umbraco.Core.Packaging && ((string)infoElement.Element("Master")).IsNullOrWhiteSpace()) { var alias = documentType.Element("Info").Element("Alias").Value; - var folders = foldersAttribute.Value.Split('/'); + var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash); var rootFolder = HttpUtility.UrlDecode(folders[0]); //level 1 = root level folders, there can only be one with the same name var current = _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); @@ -942,7 +942,7 @@ namespace Umbraco.Core.Packaging if (foldersAttribute != null) { var name = datatypeElement.Attribute("Name").Value; - var folders = foldersAttribute.Value.Split('/'); + var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash); var rootFolder = HttpUtility.UrlDecode(folders[0]); //there will only be a single result by name for level 1 (root) containers var current = _dataTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs index 50cb692530..1bba373218 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs @@ -44,13 +44,13 @@ namespace Umbraco.Core.Packaging Actions = xml.Element("actions")?.ToString(SaveOptions.None) ?? "", //take the entire outer xml value ContentNodeId = xml.Element("content")?.AttributeValue("nodeId") ?? string.Empty, ContentLoadChildNodes = xml.Element("content")?.AttributeValue("loadChildNodes") ?? false, - Macros = xml.Element("macros")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - Templates = xml.Element("templates")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - Stylesheets = xml.Element("stylesheets")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - DocumentTypes = xml.Element("documentTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - Languages = xml.Element("languages")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - DataTypes = xml.Element("datatypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Macros = xml.Element("macros")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Templates = xml.Element("templates")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Stylesheets = xml.Element("stylesheets")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DocumentTypes = xml.Element("documentTypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Languages = xml.Element("languages")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DataTypes = xml.Element("datatypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), Files = xml.Element("files")?.Elements("file").Select(x => x.Value).ToList() ?? new List() }; diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index 5925e58afc..fc3713a3a4 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -160,7 +160,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions if (string.IsNullOrEmpty(attribute.ForColumns) == false) { - var columns = attribute.ForColumns.Split(',').Select(p => p.Trim()); + var columns = attribute.ForColumns.Split(Constants.CharArrays.Comma).Select(p => p.Trim()); foreach (var column in columns) { definition.Columns.Add(new IndexColumnDefinition {Name = column, Direction = Direction.Ascending}); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index f879fd5224..a78a073554 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -514,7 +514,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement currentParentIds.Add(node.NodeId); // paths parts without the roots - var pathParts = node.Path.Split(',').Where(x => !rootIds.Contains(int.Parse(x))).ToArray(); + var pathParts = node.Path.Split(Constants.CharArrays.Comma).Where(x => !rootIds.Contains(int.Parse(x))).ToArray(); if (!prevParentIds.Contains(node.ParentId)) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 5bd3638f30..ed0c288691 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -1318,7 +1318,7 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", /// public bool HasContainerInPath(string contentPath) { - var ids = contentPath.Split(',').Select(int.Parse).ToArray(); + var ids = contentPath.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray(); return HasContainerInPath(ids); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index c230d9f894..09d41a49a0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -894,7 +894,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (content.ParentId == -1) return content.Published; - var ids = content.Path.Split(',').Skip(1).Select(int.Parse); + var ids = content.Path.Split(Constants.CharArrays.Comma).Skip(1).Select(int.Parse); var sql = SqlContext.Sql() .SelectCount(x => x.NodeId) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index be7b2cf069..7d0659dba8 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax if (tableName.Contains(".") == false) return $"[{tableName}]"; - var tableNameParts = tableName.Split(new[] { '.' }, 2); + var tableNameParts = tableName.Split(Constants.CharArrays.Period, 2); return $"[{tableNameParts[0]}].[{tableNameParts[1]}]"; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index ffdeb3bb2a..d37905f221 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -83,7 +83,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) ? GetQuotedColumnName(columnDefinition.Name) : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[]{',', ' '}, StringSplitOptions.RemoveEmptyEntries) + .Split(Constants.CharArrays.CommaSpace, StringSplitOptions.RemoveEmptyEntries) .Select(GetQuotedColumnName)); return string.Format(CreateConstraint, diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index b187ed0433..9d33a2169c 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -75,7 +75,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax private static VersionName MapProductVersion(string productVersion) { - var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split('.')[0]; + var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split(Constants.CharArrays.Period)[0]; switch (firstPart) { case "??": diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 8cd9932eaf..8570c49f69 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -403,7 +403,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) ? GetQuotedColumnName(columnDefinition.Name) : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Split(Constants.CharArrays.CommaSpace, StringSplitOptions.RemoveEmptyEntries) .Select(GetQuotedColumnName)); var primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs index 11502687b7..dc2a039bfe 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (IsRangeDataType(propertyType.DataType.Id)) { - var rangeRawValues = source.ToString().Split(','); + var rangeRawValues = source.ToString().Split(Constants.CharArrays.Comma); var minimumAttempt = rangeRawValues[0].TryConvertTo(); var maximumAttempt = rangeRawValues[1].TryConvertTo(); diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index dfe02ba690..1d980b036b 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Services var matches = AnchorRegex.Matches(rteContent); foreach (Match match in matches) { - result.Add(match.Value.Split('\"')[1]); + result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]); } return result; } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index b190e0c69a..a809b83f23 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -535,7 +535,7 @@ namespace Umbraco.Core.Services.Implement if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); var rootId = Constants.System.RootString; - var ids = content.Path.Split(',') + var ids = content.Path.Split(Constants.CharArrays.Comma) .Where(x => x != rootId && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); if (ids.Any() == false) return new List(); diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 3d4c109bfb..480501212f 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -879,7 +879,7 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetContainers(TItem item) { - var ancestorIds = item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => { var asInt = x.TryConvertTo(); diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs index 5a93fb91b1..7102d0eeeb 100644 --- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetContainers(IDataType dataType) { - var ancestorIds = dataType.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => { var asInt = x.TryConvertTo(); diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index ecd4cccc8d..ac9c83458d 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -477,7 +477,7 @@ namespace Umbraco.Core.Services.Implement if (media.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); var rootId = Constants.System.RootString; - var ids = media.Path.Split(',') + var ids = media.Path.Split(Constants.CharArrays.Comma) .Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture)) .Select(int.Parse) .ToArray(); diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 507da9cec3..3c5f91e932 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -78,7 +78,7 @@ namespace Umbraco.Core.Services.Implement if (entitiesL.Count == 0) return; //put all entity's paths into a list with the same indices - var paths = entitiesL.Select(x => x.Path.Split(',').Select(int.Parse).ToArray()).ToArray(); + var paths = entitiesL.Select(x => x.Path.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray()).ToArray(); // lazily get versions var prevVersionDictionary = new Dictionary(); @@ -176,7 +176,7 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable FilterUserNotificationsByPath(IEnumerable userNotifications, string path) { - var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + var pathParts = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); } diff --git a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs index ab9ea64292..4e3cd96012 100644 --- a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs @@ -54,7 +54,7 @@ namespace Umbraco.Core.Services.Implement { //Get all ids in the path for the content item and ensure they all // parse to ints that are not -1. - var ids = contentPath.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => int.TryParse(x, out int val) ? val : -1) .Where(x => x != -1) .ToList(); diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index c365f1ccc2..e817d7925a 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Services { public static EntityPermission GetPermissions(this IUserService userService, IUser user, string path) { - var ids = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ids = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.TryConvertTo()) .Where(x => x.Success) .Select(x => x.Result) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 57ef9c0d42..80ef81f36d 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core /// internal static int[] GetIdsFromPathReversed(this string path) { - var nodeIds = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var nodeIds = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.TryConvertTo()) .Where(x => x.Success) .Select(x => x.Result) @@ -256,7 +256,7 @@ namespace Umbraco.Core //remove any prefixed '&' or '?' for (var i = 0; i < queryStrings.Length; i++) { - queryStrings[i] = queryStrings[i].TrimStart('?', '&').TrimEnd('&'); + queryStrings[i] = queryStrings[i].TrimStart(Constants.CharArrays.QuestionMarkAmpersand).TrimEnd(Constants.CharArrays.Ampersand); } var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray(); @@ -315,7 +315,7 @@ namespace Umbraco.Core if (value == null) return null; - string[] parts = value.Split('\n'); + string[] parts = value.Split(Constants.CharArrays.LineFeed); StringBuilder decryptedValue = new StringBuilder(); @@ -1347,7 +1347,7 @@ namespace Umbraco.Core { return false; } - var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + var idCheckList = csv.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); return idCheckList.Contains(value); } @@ -1362,7 +1362,7 @@ namespace Umbraco.Core fileName = fileName.StripFileExtension(); // underscores and dashes to spaces - fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' '); + fileName = fileName.ReplaceMany(Constants.CharArrays.UnderscoreDash, ' '); // any other conversions ? @@ -1370,7 +1370,7 @@ namespace Umbraco.Core fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName); // Replace multiple consecutive spaces with a single space - fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + fileName = string.Join(" ", fileName.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries)); return fileName; } diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs index 7b32399599..77c55e6692 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Core/StringUdi.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core public StringUdi(Uri uriValue) : base(uriValue) { - Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart('/')); + Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash)); } private static string EscapeUriString(string s) @@ -46,7 +46,7 @@ namespace Umbraco.Core // we want to preserve the / and the unreserved // so... - return string.Join("/", s.Split('/').Select(Uri.EscapeDataString)); + return string.Join("/", s.Split(Constants.CharArrays.ForwardSlash).Select(Uri.EscapeDataString)); } /// diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs index 6f91906250..2c84e9adc5 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.Strings.Css { // since we already have a string builder in play here, we'll append to it the "hard" way // instead of using string interpolation (for increased performance) - foreach (var style in Styles.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var style in Styles.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries)) { sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine); } diff --git a/src/Umbraco.Core/Strings/Diff.cs b/src/Umbraco.Core/Strings/Diff.cs index 6cd4985ead..9aeeb487ef 100644 --- a/src/Umbraco.Core/Strings/Diff.cs +++ b/src/Umbraco.Core/Strings/Diff.cs @@ -232,7 +232,7 @@ namespace Umbraco.Core.Strings // strip off all cr, only use lf as text line separator. aText = aText.Replace("\r", ""); - var lines = aText.Split('\n'); + var lines = aText.Split(Constants.CharArrays.LineFeed); var codes = new int[lines.Length]; diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index 95b08c8377..52af734f1c 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Sync umbracoApplicationUrl = ApplicationUrlProvider?.Invoke(request); if (string.IsNullOrWhiteSpace(umbracoApplicationUrl) == false) { - umbracoApplicationUrl = umbracoApplicationUrl.TrimEnd('/'); + umbracoApplicationUrl = umbracoApplicationUrl.TrimEnd(Constants.CharArrays.ForwardSlash); logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: {UmbracoAppUrl} (provider)", umbracoApplicationUrl); return umbracoApplicationUrl; } @@ -61,7 +61,7 @@ namespace Umbraco.Core.Sync var url = settings.WebRouting.UmbracoApplicationUrl; if (url.IsNullOrWhiteSpace() == false) { - var umbracoApplicationUrl = url.TrimEnd('/'); + var umbracoApplicationUrl = url.TrimEnd(Constants.CharArrays.ForwardSlash); logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: {UmbracoAppUrl} (using web.routing/@umbracoApplicationUrl)", umbracoApplicationUrl); return umbracoApplicationUrl; } @@ -75,7 +75,7 @@ namespace Umbraco.Core.Sync url = serverRegistrar.GetCurrentServerUmbracoApplicationUrl(); if (url.IsNullOrWhiteSpace() == false) { - var umbracoApplicationUrl = url.TrimEnd('/'); + var umbracoApplicationUrl = url.TrimEnd(Constants.CharArrays.ForwardSlash); logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: {UmbracoAppUrl} (IServerRegistrar)", umbracoApplicationUrl); return umbracoApplicationUrl; } @@ -100,7 +100,7 @@ namespace Umbraco.Core.Sync var ssl = globalSettings.UseHttps ? "s" : ""; // force, whatever the first request var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco); - return url.TrimEnd('/'); + return url.TrimEnd(Constants.CharArrays.ForwardSlash); } } } diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index b11ce250ad..4fafbc0aeb 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -171,7 +171,7 @@ namespace Umbraco.Core throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); } - var path = uri.AbsolutePath.TrimStart('/'); + var path = uri.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash); if (udiType == UdiType.GuidUdi) { diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs index 5a5ccf5574..4eba1858a6 100644 --- a/src/Umbraco.Core/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -154,7 +154,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Stylesheet entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); } /// @@ -165,7 +165,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Script entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); } /// @@ -204,7 +204,7 @@ namespace Umbraco.Core ? Constants.UdiEntityType.PartialViewMacro : Constants.UdiEntityType.PartialView; - return new StringUdi(entityType, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(entityType, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); } /// diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs index b70cf43d18..857e818ce6 100644 --- a/src/Umbraco.Core/UdiRange.cs +++ b/src/Umbraco.Core/UdiRange.cs @@ -70,7 +70,7 @@ namespace Umbraco.Core } var udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) { Query = string.Empty }.Uri; - return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart('?')); + return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart(Constants.CharArrays.QuestionMark)); } public override string ToString() diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 39fd115d62..8a9398459e 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -128,6 +128,7 @@ --> + diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 1198b26e0f..60d9cd6ead 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -43,8 +43,8 @@ namespace Umbraco.Core { applicationPath = applicationPath ?? string.Empty; - var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'}); - var appPath = applicationPath.TrimStart(new[] {'/'}); + var fullUrlPath = url.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash); + var appPath = applicationPath.TrimStart(Constants.CharArrays.ForwardSlash); var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/'); //check if this is in the umbraco back office @@ -93,7 +93,7 @@ namespace Umbraco.Core // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a // plugin controller for the front-end. - if (urlPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries).Length >= 3) + if (urlPath.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries).Length >= 3) { return false; } diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index d6461ec8c6..f070e08d4f 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -53,7 +53,7 @@ namespace Umbraco.Core.Xml public static bool IsXmlWhitespace(string s) { // as per xml 1.1 specs - anything else is significant whitespace - s = s.Trim(' ', '\t', '\r', '\n'); + s = s.Trim(Constants.CharArrays.XmlWhitespaceChars); return s.Length == 0; } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs index 7ef8112d11..e59900bdc4 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs @@ -446,7 +446,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building { WriteNonGenericClrType(sb, type.Substring(0, p)); sb.Append("<"); - var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types + var args = type.Substring(p + 1).TrimEnd(Umbraco.Core.Constants.CharArrays.GreaterThan).Split(Umbraco.Core.Constants.CharArrays.Comma); // fixme will NOT work with nested generic types for (var i = 0; i < args.Length; i++) { if (i > 0) sb.Append(", "); diff --git a/src/Umbraco.Web/Cache/DistributedCacheBinder.cs b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs index 92ed7de881..5f8d77f3b2 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheBinder.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs @@ -39,7 +39,6 @@ namespace Umbraco.Web.Cache private static readonly Lazy CandidateHandlers = new Lazy(() => { - var underscore = new[] { '_' }; return typeof(DistributedCacheBinder) .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) @@ -47,7 +46,7 @@ namespace Umbraco.Web.Cache { if (x.Name.Contains("_") == false) return null; - var parts = x.Name.Split(underscore, StringSplitOptions.RemoveEmptyEntries).Length; + var parts = x.Name.Split(Constants.CharArrays.Underscore, StringSplitOptions.RemoveEmptyEntries).Length; if (parts != 2) return null; var parameters = x.GetParameters(); diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 18740d41fc..458c76b3ae 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -125,7 +125,7 @@ namespace Umbraco.Web.Editors return RedirectToAction("Default"); } - var parts = Server.UrlDecode(invite).Split('|'); + var parts = Server.UrlDecode(invite).Split(Constants.CharArrays.VerticalTab); if (parts.Length != 2) { diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 7cb940d88b..dd4dd67681 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -333,8 +333,8 @@ namespace Umbraco.Web.Editors "umbracoSettings", new Dictionary { {"umbracoPath", _globalSettings.Path}, - {"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd('/')}, - {"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd('/')}, + {"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd(Constants.CharArrays.ForwardSlash)}, + {"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd(Constants.CharArrays.ForwardSlash)}, { "imageFileTypes", string.Join(",", Current.Configs.Settings().Content.ImageFileTypes) @@ -353,7 +353,7 @@ namespace Umbraco.Web.Editors }, {"keepUserLoggedIn", Current.Configs.Settings().Security.KeepUserLoggedIn}, {"usernameIsEmail", Current.Configs.Settings().Security.UsernameIsEmail}, - {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, + {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd(Constants.CharArrays.ForwardSlash)}, {"allowPasswordReset", Current.Configs.Settings().Security.AllowPasswordReset}, {"loginBackgroundImage", Current.Configs.Settings().Content.LoginBackgroundImage}, {"loginLogoImage", Current.Configs.Settings().Content.LoginLogoImage }, diff --git a/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs b/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs index a017ae5afb..e0d39b5f65 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Editors.Binders { //The name that has been assigned in JS has 2 or more parts. The second part indicates the property id // for which the file belongs, the remaining parts are just metadata that can be used by the property editor. - var parts = file.Headers.ContentDisposition.Name.Trim('\"').Split('_'); + var parts = file.Headers.ContentDisposition.Name.Trim(Constants.CharArrays.DoubleQuote).Split(Constants.CharArrays.Underscore); if (parts.Length < 2) { var response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest); @@ -64,7 +64,7 @@ namespace Umbraco.Web.Editors.Binders // TODO: anything after 4 parts we can put in metadata - var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); + var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote); model.UploadedFiles.Add(new ContentPropertyFile { diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index 0e1c4b3e60..409cded781 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -23,6 +23,7 @@ using Umbraco.Web.WebApi.Filters; using Umbraco.Web.Trees; using Stylesheet = Umbraco.Core.Models.Stylesheet; using StylesheetRule = Umbraco.Web.Models.ContentEditing.StylesheetRule; +using CharArrays = Umbraco.Core.Constants.CharArrays; namespace Umbraco.Web.Editors { @@ -277,7 +278,7 @@ namespace Umbraco.Web.Editors if (id != Core.Constants.System.RootString) { - codeFileDisplay.VirtualPath += id.TrimStart("/").EnsureEndsWith("/"); + codeFileDisplay.VirtualPath += id.TrimStart(CharArrays.ForwardSlash).EnsureEndsWith("/"); //if it's not new then it will have a path, otherwise it won't codeFileDisplay.Path = Url.GetTreePathFromFilePath(id); } @@ -473,7 +474,7 @@ namespace Umbraco.Web.Editors data.Content = StylesheetHelper.ReplaceRule(data.Content, rule.Name, null); } - data.Content = data.Content.TrimEnd('\n', '\r'); + data.Content = data.Content.TrimEnd(CharArrays.LineFeedCarriageReturn); // now add all the posted rules if (data.Rules != null && data.Rules.Any()) diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index d426cb1f56..1803fa735c 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -629,7 +629,7 @@ namespace Umbraco.Web.Editors var model = new ContentTypeImportModel(); var file = result.FileData[0]; - var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); + var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote); var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); var destFileName = root + "\\" + fileName; diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 65d9305906..fe78b29123 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -174,7 +174,7 @@ namespace Umbraco.Web.Editors { var foundContent = GetResultForId(id, type); - return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + return foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } /// @@ -187,7 +187,7 @@ namespace Umbraco.Web.Editors { var foundContent = GetResultForKey(id, type); - return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + return foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } /// @@ -315,7 +315,7 @@ namespace Umbraco.Web.Editors getPath: nodeid => { var ent = Services.EntityService.Get(nodeid); - return ent.Path.Split(',').Reverse(); + return ent.Path.Split(Constants.CharArrays.Comma).Reverse(); }, publishedContentExists: i => Umbraco.Content(i) != null); } @@ -802,7 +802,7 @@ namespace Umbraco.Web.Editors { // TODO: Need to check for Object types that support hierarchic here, some might not. - var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); + var ids = Services.EntityService.Get(id).Path.Split(Constants.CharArrays.Comma).Select(int.Parse).Distinct().ToArray(); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId")); if (ignoreUserStartNodes == false) @@ -1114,7 +1114,7 @@ namespace Umbraco.Web.Editors { if (postFilter.IsNullOrWhiteSpace()) return entities; - var postFilterConditions = postFilter.Split('&'); + var postFilterConditions = postFilter.Split(Constants.CharArrays.Ampersand); foreach (var postFilterCondition in postFilterConditions) { @@ -1131,9 +1131,7 @@ namespace Umbraco.Web.Editors return entities; } - private static QueryCondition BuildQueryCondition(string postFilter) - { - var postFilterParts = postFilter.Split(new[] + private static readonly string[] _postFilterSplitStrings = new[] { "=", "==", @@ -1143,7 +1141,10 @@ namespace Umbraco.Web.Editors "<", ">=", "<=" - }, 2, StringSplitOptions.RemoveEmptyEntries); + }; + private static QueryCondition BuildQueryCondition(string postFilter) + { + var postFilterParts = postFilter.Split(_postFilterSplitStrings, 2, StringSplitOptions.RemoveEmptyEntries); if (postFilterParts.Length != 2) { diff --git a/src/Umbraco.Web/Editors/MacrosController.cs b/src/Umbraco.Web/Editors/MacrosController.cs index 38103400d9..ec54cfdc04 100644 --- a/src/Umbraco.Web/Editors/MacrosController.cs +++ b/src/Umbraco.Web/Editors/MacrosController.cs @@ -399,7 +399,7 @@ namespace Umbraco.Web.Editors files.AddRange( fileInfo.Select(file => - prefixVirtualPath.TrimEnd('/') + "/" + (path.Replace(orgPath, string.Empty).Trim('/') + "/" + file.Name).Trim('/'))); + prefixVirtualPath.TrimEnd(Constants.CharArrays.ForwardSlash) + "/" + (path.Replace(orgPath, string.Empty).Trim(Constants.CharArrays.ForwardSlash) + "/" + file.Name).Trim(Constants.CharArrays.ForwardSlash))); return files; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index dfe6939552..5fd59d0c9f 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -645,7 +645,7 @@ namespace Umbraco.Web.Editors if (result.FormData.ContainsKey("path")) { - var folders = result.FormData["path"].Split('/'); + var folders = result.FormData["path"].Split(Constants.CharArrays.ForwardSlash); for (int i = 0; i < folders.Length - 1; i++) { @@ -694,7 +694,7 @@ namespace Umbraco.Web.Editors //get the files foreach (var file in result.FileData) { - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); + var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd(); var safeFileName = fileName.ToSafeFileName(); var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 1030498734..1d8f02dbf9 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -24,7 +24,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using File = System.IO.File; using Notification = Umbraco.Web.Models.ContentEditing.Notification; - +using CharArrays = Umbraco.Core.Constants.CharArrays; namespace Umbraco.Web.Editors { /// @@ -151,7 +151,7 @@ namespace Umbraco.Web.Editors //get the files foreach (var file in result.FileData) { - var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); + var fileName = file.Headers.ContentDisposition.FileName.Trim(CharArrays.DoubleQuote); var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); if (ext.InvariantEquals("zip") || ext.InvariantEquals("umb")) diff --git a/src/Umbraco.Web/Editors/TinyMceController.cs b/src/Umbraco.Web/Editors/TinyMceController.cs index c6c1acd048..1d292c64fd 100644 --- a/src/Umbraco.Web/Editors/TinyMceController.cs +++ b/src/Umbraco.Web/Editors/TinyMceController.cs @@ -74,7 +74,7 @@ namespace Umbraco.Web.Editors // Really we should only have one file per request to this endpoint var file = result.FileData[0]; - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); + var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd(); var safeFileName = fileName.ToSafeFileName(); var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index 8991bcdd6a..943d5674a2 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -10,6 +10,7 @@ using Umbraco.Core.IO; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.Tour; +using CharArrays = Umbraco.Core.Constants.CharArrays; namespace Umbraco.Web.Editors { @@ -56,7 +57,7 @@ namespace Umbraco.Web.Editors { foreach (var plugin in Directory.EnumerateDirectories(appPlugins)) { - var pluginName = Path.GetFileName(plugin.TrimEnd('\\')); + var pluginName = Path.GetFileName(plugin.TrimEnd(CharArrays.Backslash)); var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)) .ToList(); @@ -138,7 +139,7 @@ namespace Umbraco.Web.Editors { return false; } - var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim()); + var contentTypes = x.ContentType.Split(CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim()); return contentTypes.Intersect(doctypeAliasWithCompositions).Any(); }); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index b022e6f27a..3f29203819 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web.Editors //get the file info var file = result.FileData[0]; - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); + var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd(); var safeFileName = fileName.ToSafeFileName(); var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); diff --git a/src/Umbraco.Web/FormDataCollectionExtensions.cs b/src/Umbraco.Web/FormDataCollectionExtensions.cs index 52f86dcc59..aabf13ac9b 100644 --- a/src/Umbraco.Web/FormDataCollectionExtensions.cs +++ b/src/Umbraco.Web/FormDataCollectionExtensions.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web { builder.Append(string.Format("{0}={1}&", i.Key, i.Value)); } - return builder.ToString().TrimEnd('&'); + return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand); } /// diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index ebb77bd4a4..d9294c7ff0 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -39,10 +39,10 @@ namespace Umbraco.Web if (cookiesHeaderValue == null) return null; - var cookieCollection = cookiesHeaderValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var cookieCollection = cookiesHeaderValue.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries); foreach (var cookieNameValue in cookieCollection) { - var parts = cookieNameValue.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + var parts = cookieNameValue.Split(Constants.CharArrays.EqualsChar, StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) continue; if (parts[0].Trim().Equals(cookieName, StringComparison.InvariantCultureIgnoreCase)) return parts[1].Trim(); diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index b4fd8c0d86..54ce640855 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -381,7 +381,7 @@ namespace Umbraco.Web.Macros if (attributeValue.StartsWith("[") == false) return attributeValue; - var tokens = attributeValue.Split(',').Select(x => x.Trim()).ToArray(); + var tokens = attributeValue.Split(Constants.CharArrays.Comma).Select(x => x.Trim()).ToArray(); // ensure we only process valid input ie each token must be [?x] and not eg a json array // like [1,2,3] which we don't want to parse - however the last one can be a literal, so diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 3bbc4a793b..b9f4650e36 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -100,7 +100,7 @@ namespace Umbraco.Web.Macros Elements.Add("createDate", createDate); Elements.Add("updateDate", updateDate); Elements.Add("path", path); - Elements.Add("splitpath", path.Split(',')); + Elements.Add("splitpath", path.Split(Constants.CharArrays.Comma)); } /// diff --git a/src/Umbraco.Web/Media/Exif/ExifPropertyFactory.cs b/src/Umbraco.Web/Media/Exif/ExifPropertyFactory.cs index 68769eb1f3..4373025eb9 100644 --- a/src/Umbraco.Web/Media/Exif/ExifPropertyFactory.cs +++ b/src/Umbraco.Web/Media/Exif/ExifPropertyFactory.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using Umbraco.Core; namespace Umbraco.Web.Media.Exif { @@ -44,7 +45,7 @@ namespace Umbraco.Web.Media.Exif return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value)); else if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f) - return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd('\0')); + return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd(Constants.CharArrays.NullTerminator)); } else if (ifd == IFD.EXIF) { @@ -75,7 +76,7 @@ namespace Umbraco.Web.Media.Exif hasenc = false; } - string val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim('\0'); + string val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim(Constants.CharArrays.NullTerminator); return new ExifEncodedString(ExifTag.UserComment, val, enc); } diff --git a/src/Umbraco.Web/Media/Exif/MathEx.cs b/src/Umbraco.Web/Media/Exif/MathEx.cs index 94cbccfbda..7489c21878 100644 --- a/src/Umbraco.Web/Media/Exif/MathEx.cs +++ b/src/Umbraco.Web/Media/Exif/MathEx.cs @@ -1,6 +1,6 @@ using System; using System.Text; - +using Umbraco.Core; namespace Umbraco.Web.Media.Exif { /// @@ -694,7 +694,7 @@ namespace Umbraco.Web.Media.Exif if (s == null) throw new ArgumentNullException("s"); - string[] sa = s.Split('/'); + string[] sa = s.Split(Constants.CharArrays.ForwardSlash); int numerator = 1; int denominator = 1; @@ -1322,7 +1322,7 @@ namespace Umbraco.Web.Media.Exif if (s == null) throw new ArgumentNullException("s"); - string[] sa = s.Split('/'); + string[] sa = s.Split(Constants.CharArrays.ForwardSlash); uint numerator = 1; uint denominator = 1; diff --git a/src/Umbraco.Web/Media/UploadAutoFillProperties.cs b/src/Umbraco.Web/Media/UploadAutoFillProperties.cs index 01ced179d6..60de03b450 100644 --- a/src/Umbraco.Web/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Web/Media/UploadAutoFillProperties.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.Media { using (var filestream = _mediaFileSystem.OpenFile(filepath)) { - var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); + var extension = (Path.GetExtension(filepath) ?? "").TrimStart(Constants.CharArrays.Period); var size = _contentSection.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } @@ -101,7 +101,7 @@ namespace Umbraco.Web.Media } else { - var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); + var extension = (Path.GetExtension(filepath) ?? "").TrimStart(Constants.CharArrays.Period); var size = _contentSection.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index d03472ef5e..e224a6122b 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -107,7 +107,7 @@ namespace Umbraco.Web //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 + .Select(x => x.Split(Constants.CharArrays.Period)) //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 @@ -145,7 +145,7 @@ namespace Umbraco.Web .Select(x => { // Format "_" - var cs = x.Split(new[] { '_' }); + var cs = x.Split(Constants.CharArrays.Underscore); return (culture: cs[0], segment: cs[1]); }) .Where(x => !x.culture.IsNullOrWhiteSpace()) diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index 983172f8e1..16465914cb 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -253,7 +253,7 @@ namespace Umbraco.Web.Models.Mapping if (parent == null) return false; - var pathParts = parent.Path.Split(',').Select(x => int.TryParse(x, out var i) ? i : 0).ToList(); + var pathParts = parent.Path.Split(Constants.CharArrays.Comma).Select(x => int.TryParse(x, out var i) ? i : 0).ToList(); // reduce the path parts so we exclude top level content items that // are higher up than a user's start nodes diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index f67a5df56c..7028518794 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -561,7 +561,7 @@ namespace Umbraco.Web.Models.Mapping return Enumerable.Empty(); var aliases = new List(); - var ancestorIds = parent.Path.Split(',').Select(int.Parse); + var ancestorIds = parent.Path.Split(Constants.CharArrays.Comma).Select(int.Parse); // loop through all content types and return ordered aliases of ancestors var allContentTypes = _contentTypeService.GetAll().ToArray(); foreach (var ancestorId in ancestorIds) diff --git a/src/Umbraco.Web/Models/Trees/TreeNode.cs b/src/Umbraco.Web/Models/Trees/TreeNode.cs index 10684b519e..fd7ef0d19d 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNode.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNode.cs @@ -110,7 +110,7 @@ namespace Umbraco.Web.Models.Trees //absolute path with or without tilde if (Icon.StartsWith("~") || Icon.StartsWith("/")) - return IOHelper.ResolveUrl("~" + Icon.TrimStart('~')); + return IOHelper.ResolveUrl("~" + Icon.TrimStart(Constants.CharArrays.Tilde)); //legacy icon path return string.Format("{0}images/umbraco/{1}", Current.Configs.Global().Path.EnsureEndsWith("/"), Icon); diff --git a/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs index 5f81ced3f0..712cb8fb72 100644 --- a/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs @@ -6,7 +6,7 @@ using Umbraco.Core; using Umbraco.Web.Security; using Umbraco.Core.Composing; using Current = Umbraco.Web.Composing.Current; - +using CharArrays = Umbraco.Core.Constants.CharArrays; namespace Umbraco.Web.Mvc { /// @@ -40,7 +40,7 @@ namespace Umbraco.Web.Mvc AllowType = ""; var members = new List(); - foreach (var s in AllowMembers.Split(',')) + foreach (var s in AllowMembers.Split(CharArrays.Comma)) { if (int.TryParse(s, out var id)) { @@ -49,7 +49,7 @@ namespace Umbraco.Web.Mvc } var helper = Current.Factory.GetInstance(); - return helper.IsMemberAuthorized(AllowType.Split(','), AllowGroup.Split(','), members); + return helper.IsMemberAuthorized(AllowType.Split(CharArrays.Comma), AllowGroup.Split(CharArrays.Comma), members); } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 28f2449187..b15a00a33e 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -253,7 +253,7 @@ namespace Umbraco.Web.Mvc //the template Alias should always be already saved with a safe name. //if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed // with the action name attribute. - var templateName = request.TemplateAlias.Split('.')[0].ToSafeAlias(); + var templateName = request.TemplateAlias.Split(Umbraco.Core.Constants.CharArrays.Period)[0].ToSafeAlias(); def.ActionName = templateName; } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index fa8060bd15..493fc96f76 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.PropertyEditors if (string.IsNullOrEmpty(asString)) yield break; - foreach (var udiStr in asString.Split(',')) + foreach (var udiStr in asString.Split(Constants.CharArrays.Comma)) { if (Udi.TryParse(udiStr, out var udi)) yield return new UmbracoEntityReference(udi); diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index fd7f735e68..1630e706db 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.PropertyEditors { var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); - var udiPaths = asString.Split(','); + var udiPaths = asString.Split(Constants.CharArrays.Comma); foreach (var udiPath in udiPaths) if (Udi.TryParse(udiPath, out var udi)) yield return new UmbracoEntityReference(udi); diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index fd7e8694a3..6066bf7dfb 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.PropertyEditors if (string.IsNullOrWhiteSpace(value) == false) { - return value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + return value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); } return null; diff --git a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs index 8d7bd29889..b92141db9c 100644 --- a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.PropertyEditors yield break; } - var fileNames = selectedFiles?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var fileNames = selectedFiles?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (fileNames == null || !fileNames.Any()) yield break; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs index a56630d7c5..3f15eca0cc 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (source == null) return null; var nodeIds = source.ToString() - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(Udi.Parse) .ToArray(); return nodeIds; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index 47f8797295..9f92d6493e 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -54,7 +54,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) { var nodeIds = source.ToString() - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(Udi.Parse) .ToArray(); return nodeIds; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index a0224f90ea..dbb427422d 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -79,7 +79,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos)); - var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + var parts = path.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries); IPublishedContent content; diff --git a/src/Umbraco.Web/RoutableDocumentFilter.cs b/src/Umbraco.Web/RoutableDocumentFilter.cs index 507a40e471..6096429db6 100644 --- a/src/Umbraco.Web/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web/RoutableDocumentFilter.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web // use uri.AbsolutePath, not path, 'cos path has been lowercased httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo - uri.Query.TrimStart('?')); + uri.Query.TrimStart(Constants.CharArrays.QuestionMark)); maybeDoc = false; } @@ -101,7 +101,7 @@ namespace Umbraco.Web // add URLs and paths to a new list var newReservedList = new HashSet(); foreach (var reservedUrlTrimmed in reservedUrlsCache - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim().ToLowerInvariant()) .Where(x => x.IsNullOrWhiteSpace() == false) .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/")) @@ -110,7 +110,7 @@ namespace Umbraco.Web newReservedList.Add(reservedUrlTrimmed); } - foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries))) + foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries))) { newReservedList.Add(reservedPathTrimmed); } @@ -127,7 +127,7 @@ namespace Umbraco.Web //The URL should be cleaned up before checking: // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/' // * We shouldn't be comparing the query at all - var pathPart = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant(); + var pathPart = url.Split(Constants.CharArrays.QuestionMark, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant(); if (pathPart.Contains(".") == false) { pathPart = pathPart.EnsureEndsWith('/'); diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index b3b56a18e7..12828f06d7 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -84,7 +84,7 @@ namespace Umbraco.Web.Routing yield break; var umbracoUrlName = node.Value(Constants.Conventions.Content.UrlAlias); - var aliases = umbracoUrlName?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (aliases == null || aliases.Any() == false) yield break; @@ -111,7 +111,7 @@ namespace Umbraco.Web.Routing ? node.Value(Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture.Name) : node.Value(Constants.Conventions.Content.UrlAlias); - var aliases = umbracoUrlName?.Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries); + var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (aliases == null || aliases.Any() == false) continue; @@ -132,8 +132,8 @@ namespace Umbraco.Web.Routing string CombinePaths(string path1, string path2) { - string path = path1.TrimEnd('/') + path2; - return path == "/" ? path : path.TrimEnd('/'); + string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; + return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash); } #endregion diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index 9340c6c6de..0ee5f3d42d 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web.Routing const string propertyAlias = Constants.Conventions.Content.UrlAlias; - var test1 = alias.TrimStart('/') + ","; + var test1 = alias.TrimStart(Constants.CharArrays.ForwardSlash) + ","; var test2 = ",/" + test1; // test2 is ",/alias," test1 = "," + test1; // test1 is ",alias," diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 291ac67882..f26f05c36c 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core; namespace Umbraco.Web.Routing { @@ -172,8 +173,8 @@ namespace Umbraco.Web.Routing string CombinePaths(string path1, string path2) { - string path = path1.TrimEnd('/') + path2; - return path == "/" ? path : path.TrimEnd('/'); + string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; + return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash); } #endregion diff --git a/src/Umbraco.Web/Routing/DomainUtilities.cs b/src/Umbraco.Web/Routing/DomainUtilities.cs index fb0c56b28d..9f5ab32acb 100644 --- a/src/Umbraco.Web/Routing/DomainUtilities.cs +++ b/src/Umbraco.Web/Routing/DomainUtilities.cs @@ -327,7 +327,7 @@ namespace Umbraco.Web.Routing { var stopNodeId = rootNodeId ?? -1; - return path.Split(',') + return path.Split(Constants.CharArrays.Comma) .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) @@ -348,7 +348,7 @@ namespace Umbraco.Web.Routing { var stopNodeId = rootNodeId ?? -1; - return path.Split(',') + return path.Split(Constants.CharArrays.Comma) .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) diff --git a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs index 38ecb09b2b..9fefeeb843 100644 --- a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs @@ -96,7 +96,7 @@ namespace Umbraco.Web.Routing getPath: nodeid => { var ent = entityService.Get(nodeid); - return ent.Path.Split(',').Reverse(); + return ent.Path.Split(Constants.CharArrays.Comma).Reverse(); }, publishedContentExists: i => publishedContentQuery.Content(i) != null); diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index afa0bc96cc..3bd9d985a6 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -164,7 +164,7 @@ namespace Umbraco.Web.Routing private static bool DetectCollision(IContent content, string url, string culture, UmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, out UrlInfo urlInfo) { // test for collisions on the 'main' URL - var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); + var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute); if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); uri = UriUtility.UriToUmbraco(uri); var pcr = publishedRouter.CreateRequest(umbracoContext, uri); diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index 9a22c59566..2dcf9a24a7 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -62,7 +62,7 @@ namespace Umbraco.Web.Scheduling return true; // repeat } - keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd('/')); + keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd(Constants.CharArrays.ForwardSlash)); } var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl); diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index a22e2a6f6a..410b654e32 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -196,7 +196,7 @@ namespace Umbraco.Web.Search if (surroundedByQuotes) { //strip quotes, escape string, the replace again - query = query.Trim('\"', '\''); + query = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote); query = Lucene.Net.QueryParsers.QueryParser.Escape(query); @@ -230,7 +230,7 @@ namespace Umbraco.Web.Search } else { - var trimmed = query.Trim(new[] { '\"', '\'' }); + var trimmed = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote); //nothing to search if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace()) @@ -243,7 +243,7 @@ namespace Umbraco.Web.Search { query = Lucene.Net.QueryParsers.QueryParser.Escape(query); - var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var querywords = query.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries); sb.Append("+("); diff --git a/src/Umbraco.Web/Services/DashboardService.cs b/src/Umbraco.Web/Services/DashboardService.cs index 794c6fa671..19944a9cb1 100644 --- a/src/Umbraco.Web/Services/DashboardService.cs +++ b/src/Umbraco.Web/Services/DashboardService.cs @@ -86,7 +86,7 @@ namespace Umbraco.Web.Services if (grantBySectionRules.Length > 0) { var allowedSections = sectionService.GetAllowedSections(user.Id).Select(x => x.Alias).ToArray(); - var wantedSections = grantBySectionRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + var wantedSections = grantBySectionRules.SelectMany(g => g.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)).ToArray(); if (wantedSections.Intersect(allowedSections).Any()) hasAccess = true; @@ -97,7 +97,7 @@ namespace Umbraco.Web.Services if (hasAccess == false && grantRules.Any()) { assignedUserGroups = user.Groups.Select(x => x.Alias).ToArray(); - var wantedUserGroups = grantRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + var wantedUserGroups = grantRules.SelectMany(g => g.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)).ToArray(); if (wantedUserGroups.Intersect(assignedUserGroups).Any()) hasAccess = true; @@ -111,7 +111,7 @@ namespace Umbraco.Web.Services // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will // be denied to see it no matter what assignedUserGroups = assignedUserGroups ?? user.Groups.Select(x => x.Alias).ToArray(); - var deniedUserGroups = denyRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + var deniedUserGroups = denyRules.SelectMany(g => g.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)).ToArray(); if (deniedUserGroups.Intersect(assignedUserGroups).Any()) hasAccess = false; diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 663af43643..087b25b9a7 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -175,7 +175,7 @@ namespace Umbraco.Web.Trees var nodeMenu = GetAllNodeMenuItems(item); //if the content node is in the recycle bin, don't have a default menu, just show the regular menu - if (item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) + if (item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) { nodeMenu.DefaultMenuAlias = null; nodeMenu = GetNodeMenuItemsForDeletedContent(item); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 95de72b7bf..e9c34608a2 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -369,7 +369,7 @@ namespace Umbraco.Web.Trees if (startNodes.Any(x => { - var pathParts = x.Path.Split(','); + var pathParts = x.Path.Split(Constants.CharArrays.Comma); return pathParts.Contains(e.Id.ToInvariantString()); })) { diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index 6e21c8114f..cad59619df 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -62,7 +62,7 @@ namespace Umbraco.Web.Trees if (Extensions.Contains("*")) return true; - return extension != null && Extensions.Contains(extension.Trim('.'), StringComparer.InvariantCultureIgnoreCase); + return extension != null && Extensions.Contains(extension.Trim(Constants.CharArrays.Period), StringComparer.InvariantCultureIgnoreCase); }); foreach (var file in files) diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index df44a809c9..aacd285823 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -125,7 +125,7 @@ namespace Umbraco.Web.Trees //if the media item is in the recycle bin, we don't have a default menu and we need to show a limited menu - if (item.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) + if (item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) { menu.Items.Add(Services.TextService, opensDialog: true); menu.Items.Add(Services.TextService, opensDialog: true); diff --git a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs index 3221023ca2..864d43583c 100644 --- a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Trees var sb = new StringBuilder("-1"); //split the virtual path and iterate through it - var pathPaths = virtualPath.Split('/'); + var pathPaths = virtualPath.Split(Constants.CharArrays.ForwardSlash); for (var p = 0; p < pathPaths.Length; p++) { diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 92fee22983..cc130a7b56 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -249,7 +249,7 @@ namespace Umbraco.Web private void RewriteToBackOfficeHandler(HttpContextBase context) { // GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any) - var rewritePath = _globalSettings.Path.TrimEnd('/') + "/Default"; + var rewritePath = _globalSettings.Path.TrimEnd(Constants.CharArrays.ForwardSlash) + "/Default"; // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc) context.RewritePath(rewritePath, "", "", false); @@ -279,10 +279,10 @@ namespace Umbraco.Web // rewritten URL, but this is not what we want! // read: http://forums.iis.net/t/1146511.aspx - var query = pcr.Uri.Query.TrimStart('?'); + var query = pcr.Uri.Query.TrimStart(Constants.CharArrays.QuestionMark); // GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any) - var rewritePath = _globalSettings.Path.TrimEnd('/') + "/RenderMvc"; + var rewritePath = _globalSettings.Path.TrimEnd(Constants.CharArrays.ForwardSlash) + "/RenderMvc"; // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc) context.RewritePath(rewritePath, "", query, false); diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs index ae0b001917..730a50bd61 100644 --- a/src/Umbraco.Web/UriUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web public static string ToAbsolute(string url) { //return ResolveUrl(url); - url = url.TrimStart('~'); + url = url.TrimStart(Constants.CharArrays.Tilde); return _appPathPrefix + url; } @@ -102,7 +102,7 @@ namespace Umbraco.Web } if (path != "/") { - path = path.TrimEnd('/'); + path = path.TrimEnd(Constants.CharArrays.ForwardSlash); } //if any part of the path contains .aspx, replace it with nothing. @@ -216,7 +216,7 @@ namespace Umbraco.Web var pos = Math.Min(pos1, pos2); var path = pos > 0 ? uri.Substring(0, pos) : uri; - path = path.TrimEnd('/'); + path = path.TrimEnd(Constants.CharArrays.ForwardSlash); if (pos > 0) path += uri.Substring(pos); diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 28f09b46b7..550b4dcdf1 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.WebApi.Filters int nodeId; if (_nodeId.HasValue == false) { - var parts = _paramName.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries); + var parts = _paramName.Split(Constants.CharArrays.Period, StringSplitOptions.RemoveEmptyEntries); if (actionContext.ActionArguments[parts[0]] == null) { diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs index 60e2889fd5..3c60f43be0 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs @@ -91,7 +91,7 @@ namespace Umbraco.Web.WebApi.Filters int nodeId; if (_nodeId.HasValue == false) { - var parts = _paramName.Split(new [] { '.' }, StringSplitOptions.RemoveEmptyEntries); + var parts = _paramName.Split(Constants.CharArrays.Period, StringSplitOptions.RemoveEmptyEntries); if (actionContext.ActionArguments[parts[0]] == null) { diff --git a/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs index bc1e9a4318..65f34b05fb 100644 --- a/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs @@ -38,7 +38,7 @@ namespace Umbraco.Web.WebApi AllowType = ""; var members = new List(); - foreach (var s in AllowMembers.Split(',')) + foreach (var s in AllowMembers.Split(Constants.CharArrays.Comma)) { if (int.TryParse(s, out var id)) { @@ -47,7 +47,7 @@ namespace Umbraco.Web.WebApi } var helper = Current.Factory.GetInstance(); - return helper.IsMemberAuthorized(AllowType.Split(','), AllowGroup.Split(','), members); + return helper.IsMemberAuthorized(AllowType.Split(Constants.CharArrays.Comma), AllowGroup.Split(Constants.CharArrays.Comma), members); } } From 061655182cd327d016492e23a72b4bcc72c37321 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 25 Jan 2021 09:27:16 +0100 Subject: [PATCH 27/86] Updated code of conduct --- .github/CODE_OF_CONDUCT.md | 92 ++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 1526c54656..10b8fd64a6 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,32 +1,88 @@ -# Code Of Conduct - -Our informal code of conduct concentrates on the values we, as Umbraco HQ, have set for ourselves and for our community. We expect you to be a friend. -Instead of listing out all the exact "do's" and "don't's" we want to challenge you to think about our values and apply them: +# Umbraco Code of Conduct -If there's a need to talk to Umbraco HQ about anything, please make sure to send a mail to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk). +## Preamble -## Be a Friend +We are the friendly CMS. And our friendliness stems from our values. That's why we have set for ourselves, Umbraco HQ, and the community, five values to guide us in everything we do: -We welcome and thank you for registering at Our Umbraco. Find below the values that govern Umbraco and which you accept by using Our Umbraco. +* Trust — We believe in and empower people +* Respect — We treat others as we would like to be treated +* Open — We share our thoughts and knowledge +* Hungry — We want to do things better, best is next +* Friendly — We want to build long-lasting relationships -## Trust +With these values in mind, we want to offer the Umbraco community a code of conduct that specifies a baseline standard of behavior so that people with different social values and communication styles can work together. -Assume positive intent and try to understand before being understood. +This code of conduct is based on the widely used Contributor Covenant, as described in [https://www.contributor-covenant.org/](https://www.contributor-covenant.org/) -## Respect +## Our Pledge -Treat others as you would like to be treated. +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. -This also goes for treating the HQ with respect. For example: don’t promote products on [our.umbraco.com](https://our.umbraco.com) that directly compete with our commercial offerings which enables us to work for a sustainable Umbraco. +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. -## Open +## Our Standards +Examples of behavior that contributes to a positive environment for our community include: -Be honest and straightforward. Tell it as it is. Share thoughts and knowledge and engage in collaboration. +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community -## Hungry +Examples of unacceptable behavior include: -Don't rest on your laurels and never accept the status quo. Contribute and give back to fellow Umbracians. +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others’ private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting -## Friendly +## Enforcement Responsibilities -Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and build long lasting relationships. \ No newline at end of file +Community leaders (e.g. Meetup & festival organizers, moderators, maintainers, ...) are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope +This Code of Conduct applies within all community spaces and events supported by Umbraco HQ or using the Umbraco name. It also applies when an individual is officially representing the community in public spaces. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior, may be reported at [conduct@umbraco.com](mailto:conduct@umbraco.com). All complaints will be reviewed and investigated promptly and fairly. + +Or alternatively, you can reach out directly to any of the team members behind the address above: + +* Sebastiaan Janssen (He, Him - Languages spoken: English, Dutch, Danish(Read)) [sebastiaan@umbraco.com](mailto:sebastiaan@umbraco.com) +* Ilham Boulghallat (She, Her - Languages spoken: English, French, Arabic) [ilham@umbraco.com](mailto:ilham@umbraco.com) +* Arnold Visser (He, Him - Languages spoken: English, Dutch) [arnold@umbraco.com](mailto:arnold@umbraco.com) + +The review process is done with full respect for the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +**1. Correction** +Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +**2. Warning** +Community Impact: A violation through a single incident or series of actions. + +Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +**3. Temporary Ban** +Community Impact: A serious violation of community standards, including sustained inappropriate behavior. + +Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +**4. Permanent Ban** +Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +Consequence: A permanent ban from any sort of public interaction within the community. + +## Attribution +This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). + +This Code of Conduct will be maintained and reviewed by the team listed above. \ No newline at end of file From fe2c80fc2beb9a9866642d1650df93ed5693f57d Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 25 Jan 2021 09:57:13 +0100 Subject: [PATCH 28/86] Fix character encoding issues --- .github/CODE_OF_CONDUCT.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 10b8fd64a6..7a81989037 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -4,11 +4,11 @@ We are the friendly CMS. And our friendliness stems from our values. That's why we have set for ourselves, Umbraco HQ, and the community, five values to guide us in everything we do: -* Trust — We believe in and empower people -* Respect — We treat others as we would like to be treated -* Open — We share our thoughts and knowledge -* Hungry — We want to do things better, best is next -* Friendly — We want to build long-lasting relationships +* Trust - We believe in and empower people +* Respect - We treat others as we would like to be treated +* Open - We share our thoughts and knowledge +* Hungry - We want to do things better, best is next +* Friendly - We want to build long-lasting relationships With these values in mind, we want to offer the Umbraco community a code of conduct that specifies a baseline standard of behavior so that people with different social values and communication styles can work together. @@ -34,7 +34,7 @@ Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others’ private information, such as a physical or email address, without their explicit permission +* Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -52,7 +52,7 @@ Instances of abusive, harassing, or otherwise unacceptable behavior, may be repo Or alternatively, you can reach out directly to any of the team members behind the address above: -* Sebastiaan Janssen (He, Him - Languages spoken: English, Dutch, Danish(Read)) [sebastiaan@umbraco.com](mailto:sebastiaan@umbraco.com) +* Sebastiaan Janssen (He, Him - Languages spoken: English, Dutch, Danish(Read)) [sebastiaan@umbraco.com](mailto:sebastiaan@umbraco.com) * Ilham Boulghallat (She, Her - Languages spoken: English, French, Arabic) [ilham@umbraco.com](mailto:ilham@umbraco.com) * Arnold Visser (He, Him - Languages spoken: English, Dutch) [arnold@umbraco.com](mailto:arnold@umbraco.com) From 3cbae5e2fed8a3b29105eb90264b003446295a91 Mon Sep 17 00:00:00 2001 From: thomashdk Date: Tue, 26 Jan 2021 21:03:14 +0100 Subject: [PATCH 29/86] Update da.xml (#9660) * Update da.xml Found som missing translations * Update src/Umbraco.Web.UI/Umbraco/config/lang/da.xml Co-authored-by: Anders Bjerner * Update src/Umbraco.Web.UI/Umbraco/config/lang/da.xml * Update da.xml Co-authored-by: Anders Bjerner --- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 37da5697ef..dd44f1c90d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -264,6 +264,8 @@ Titel (valgfri) Alternativ tekst (valgfri) Type + Hvilke varianter vil du udgive? + Vælg hvilke varianter, der skal gemmes. Afpublicér Afpubliceret Ikke oprettet From 935a8792e6b9d093ce386b1f56a82c6c52745fc3 Mon Sep 17 00:00:00 2001 From: "Lars G. Sehested" Date: Tue, 26 Jan 2021 21:16:45 +0100 Subject: [PATCH 30/86] Fix typos in Danish translation (#9702) Fixes couple of typos in the Danish translations --- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index dd44f1c90d..3897e139ba 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -328,7 +328,7 @@ Kopiering af mediet fejlede Oprettelse af mappen under parent med id %0% fejlede Omdøbning af mappen med id %0% fejlede - Træk dine filer ind i dropzonen for, at uploade dem til mediebiblioteketet. + Træk dine filer ind i dropzonen for, at uploade dem til mediebiblioteket. Opret et nyt medlem @@ -529,7 +529,7 @@ felter Indexet skal bygges igen, for at kunne læses Processen tager længere tid end forventet. Kontrollér Umbraco loggen for at se om der er sket fejl under operationen - Dette index kan ikke genbygess for det ikke har nogen + Dette index kan ikke genbygges for det ikke har nogen IIndexPopulator From 1c65662c5fec297672a7c7f8978e7ca5eef7930d Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Wed, 27 Jan 2021 21:41:58 +0000 Subject: [PATCH 31/86] Replaced with umb-icon directive in languages overview (#9557) * replaced with umb-icon directive in languages overview * removed aria-hidden --- src/Umbraco.Web.UI.Client/src/less/main.less | 5 +++++ src/Umbraco.Web.UI.Client/src/views/languages/overview.html | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 7c5ed4c9bb..f9bd179caf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -666,3 +666,8 @@ input[type=checkbox]:checked + .input-label--small { background-color: @green-l3; text-decoration: none; } + +.language-icon { + color: #BBBABF; + margin-right: 5px; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html index c0a94e3dad..95cb7c535f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html @@ -38,7 +38,7 @@ - + {{ language.name }} From a40769de41180f224664556d40e9d6d03abd2225 Mon Sep 17 00:00:00 2001 From: SteveVaneeckhout Date: Sat, 31 Oct 2020 23:19:59 +0100 Subject: [PATCH 32/86] Add ellipsis to the Sort action in Content and Media --- src/Umbraco.Web/Trees/ContentTreeController.cs | 4 ++-- src/Umbraco.Web/Trees/MediaTreeController.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 087b25b9a7..ead265e7c5 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -135,7 +135,7 @@ namespace Umbraco.Web.Trees //these two are the standard items menu.Items.Add(Services.TextService, opensDialog: true); - menu.Items.Add(Services.TextService, true); + menu.Items.Add(Services.TextService, true, opensDialog: true); //filter the standard items FilterUserAllowedMenuItems(menu, nodeActions); @@ -235,7 +235,7 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu, opensDialog: true); AddActionNode(item, menu, true, opensDialog: true); AddActionNode(item, menu, opensDialog: true); - AddActionNode(item, menu, true); + AddActionNode(item, menu, true, opensDialog: true); AddActionNode(item, menu, opensDialog: true); AddActionNode(item, menu, opensDialog: true); AddActionNode(item, menu, true, opensDialog: true); diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index aacd285823..cc71b92d15 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -101,7 +101,7 @@ namespace Umbraco.Web.Trees // root actions menu.Items.Add(Services.TextService, opensDialog: true); - menu.Items.Add(Services.TextService, true); + menu.Items.Add(Services.TextService, true, opensDialog: true); menu.Items.Add(new RefreshNode(Services.TextService, true)); return menu; } @@ -141,7 +141,7 @@ namespace Umbraco.Web.Trees menu.Items.Add(Services.TextService, opensDialog: true); menu.Items.Add(Services.TextService, opensDialog: true); menu.Items.Add(Services.TextService, opensDialog: true); - menu.Items.Add(Services.TextService); + menu.Items.Add(Services.TextService, opensDialog: true); menu.Items.Add(new RefreshNode(Services.TextService, true)); //set the default to create From 7297bd5838de3c975a96b55c1f8a8853fdaa93b3 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 28 Jan 2021 00:33:25 +0100 Subject: [PATCH 33/86] Adjust minimum width of main content (#9656) * Adjust minimum width of main content * Change min-width a bit further. --- src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index 197a5eb176..0045bed140 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -278,7 +278,7 @@ flex: 1 1 auto; margin-right: 20px; width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling - min-width: 500px; + min-width: 480px; } .umb-package-details__sidebar { From abb469a404eac06cd00a87ac6ad4295e50dbde46 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Jan 2021 11:47:10 +0100 Subject: [PATCH 34/86] Revert "Updated NPoco from 3.9.4 -> 4.0.2 (#9061)" This reverts commit d9c6584d90955f57752e6dbe535dc3e79957df92. --- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Examine/Umbraco.Examine.csproj | 1 + src/Umbraco.Tests/Testing/TestDatabase.cs | 36 +--------------------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 5 files changed, 5 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 94e61c6aa5..c32b3247a3 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -92,6 +92,7 @@ + 2.8.0 diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 91a2fdda99..a207556cb7 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -57,6 +57,7 @@ + 3.3.0 runtime; build; native; contentfiles; analyzers diff --git a/src/Umbraco.Tests/Testing/TestDatabase.cs b/src/Umbraco.Tests/Testing/TestDatabase.cs index 7567f2a5fa..b1ddbd08d4 100644 --- a/src/Umbraco.Tests/Testing/TestDatabase.cs +++ b/src/Umbraco.Tests/Testing/TestDatabase.cs @@ -266,26 +266,6 @@ namespace Umbraco.Tests.Testing throw new NotImplementedException(); } - public Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null) - { - throw new NotImplementedException(); - } - - public Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null) - { - throw new NotImplementedException(); - } - - public IAsyncUpdateQueryProvider UpdateManyAsync() - { - throw new NotImplementedException(); - } - - public int UpdateBatch(IEnumerable> pocos, BatchOptions options = null) - { - throw new NotImplementedException(); - } - public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { throw new NotImplementedException(); @@ -381,11 +361,6 @@ namespace Umbraco.Tests.Testing throw new NotImplementedException(); } - public IAsyncDeleteQueryProvider DeleteManyAsync() - { - throw new NotImplementedException(); - } - public void Save(T poco) { throw new NotImplementedException(); @@ -773,21 +748,12 @@ namespace Umbraco.Tests.Testing #endregion #region Stuff + public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) { throw new NotImplementedException(); } - public IAsyncQueryProviderWithIncludes QueryAsync() - { - throw new NotImplementedException(); - } - - int IDatabase.InsertBatch(IEnumerable pocos, BatchOptions options) - { - throw new NotImplementedException(); - } - #endregion } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index aa34e29efc..a05eb19ad2 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -102,6 +102,7 @@ + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7d758ced24..80179720b5 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -92,6 +92,7 @@ + 3.3.0 runtime; build; native; contentfiles; analyzers From 2999a48a59b9c887d369ff8c9b450353e0b9f627 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Jan 2021 11:48:32 +0100 Subject: [PATCH 35/86] Bump version to 8.11.1 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 2110ddf4e0..3ecfd20f03 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.11.0")] -[assembly: AssemblyInformationalVersion("8.11.0")] +[assembly: AssemblyFileVersion("8.11.1")] +[assembly: AssemblyInformationalVersion("8.11.1")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 149630b7cf..01d029cae0 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -347,9 +347,9 @@ False True - 8110 + 8111 / - http://localhost:8110 + http://localhost:8111 False False From e62cef6151fd6de1e1fce53ad4c1d19467da4068 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Jan 2021 12:03:02 +0100 Subject: [PATCH 36/86] Revert "Fixes wrong dependency in nuspec file" This reverts commit 8642b61449ac3cd39b63bcee80ab4615c8c2eac0. --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 - src/Umbraco.Examine/Umbraco.Examine.csproj | 1 - src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - src/Umbraco.Web/Umbraco.Web.csproj | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 3f19be5f8a..fce15eb487 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -43,7 +43,7 @@ - + diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c32b3247a3..465ddee6ee 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -91,7 +91,6 @@ - diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index a207556cb7..0e0ee62139 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -56,7 +56,6 @@ all - 3.3.0 diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a05eb19ad2..2ac28aa7d7 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -101,7 +101,6 @@ - diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 80179720b5..8890d9cf25 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -91,7 +91,6 @@ - 3.3.0 From 3404f47cfebbf632b25c49593a6aee1312ca3074 Mon Sep 17 00:00:00 2001 From: Mike Chambers Date: Sun, 31 Jan 2021 23:26:09 +0000 Subject: [PATCH 37/86] Update umb-tree-item.less (#9718) * Update umb-tree-item.less Icon sizing inconsistencies in tree * Update umb-tree-item.less * Revert "Update umb-tree-item.less" This reverts commit 4729c39837b7f5b5f936ec8e197bb8d2eb90274e. * Revert "Update umb-tree-item.less" This reverts commit a3d1136c9de5430d4c3ed1c4bf321ab3142c26c7. * Flex-shrink on umb-tree-icon not umb-tree-item-icon --- src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index ffbe2224d9..8234618393 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -105,6 +105,7 @@ body.touch .umb-tree { .umb-tree-icon { color: @ui-option-type-hover; + flex-shrink: 0; } } } From 6e1c29232a243e966bca392cee8b1f3966b04bec Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sun, 10 Jan 2021 17:10:56 +0100 Subject: [PATCH 38/86] Hide rollback button for content that hasn't been created yet --- .../src/views/components/content/umb-content-node-info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 9a4af15b8c..044d51e532 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -45,7 +45,7 @@ Date: Tue, 2 Feb 2021 00:33:31 +0100 Subject: [PATCH 39/86] Fix for applying searchResultFormatter on found searchresults (#9738) * Fix for applying searchResultFormatter on found searchresults We found out that when the searchbox in the linkpicker is used, the URL of a page is not being shown anymore since Umbraco 8.7 and later. This is because of the searchResultFormatter wasn't applied correct. * Changed ES6 arrow syntax as with the other functions. Changed ES6 arrow syntax as with the other functions. * Changed single qoutes to double qoutes for type --- .../src/common/services/search.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 8e9525af84..eda36a5fce 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -67,10 +67,11 @@ angular.module('umbraco.services') } return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeKey) - _.each(data, function (item) { + .then(data => { data.forEach(item => searchResultFormatter.configureContentResult(item)); return data; }); + }, /** From 63cf3a489df5fa9ce9c6002e540fab80b134beb1 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Wed, 13 Jan 2021 10:30:35 +1000 Subject: [PATCH 40/86] fix history box label, destructure localization response --- .../content/umbcontentnodeinfo.directive.js | 18 +++++++++--------- .../content/umb-content-node-info.html | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 18ba264c94..22b85b8b39 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -54,15 +54,15 @@ localizationService.localizeMany(keys) .then(function (data) { - labels.deleted = data[0]; - labels.unpublished = data[1]; //aka draft - labels.published = data[2]; - labels.publishedPendingChanges = data[3]; - labels.notCreated = data[4]; - labels.unsavedChanges = data[5]; - labels.doctypeChangeWarning = data[6]; - labels.notPublished = data[7]; - scope.chooseLabel = data[8]; + [labels.deleted, + labels.unpublished, + labels.published, + labels.publishedPendingChanges, + labels.notCreated, + labels.unsavedChanges, + labels.doctypeChangeWarning, + labels.notPublished, + scope.chooseLabel] = data; setNodePublishStatus(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 044d51e532..5f8967c419 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -43,7 +43,7 @@ - + Date: Tue, 2 Feb 2021 16:17:59 +0100 Subject: [PATCH 41/86] Add the ability to change the SQL Write Lock TimeOut (#9744) * Add the ability to change the SQL Write Lock TimeOut * Needed to add a default setting to to the settings for tests --- .../Configuration/GlobalSettings.cs | 23 +++++++++++++++++++ .../Configuration/IGlobalSettings.cs | 5 ++++ src/Umbraco.Core/Constants-AppSettings.cs | 8 +++++++ .../SqlSyntax/SqlCeSyntaxProvider.cs | 5 +++- .../SqlSyntax/SqlServerSyntaxProvider.cs | 5 +++- .../TestHelpers/SettingsForTests.cs | 3 ++- 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 2c79c00086..1d1ccaf7b4 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -391,5 +391,28 @@ namespace Umbraco.Core.Configuration } } } + + + /// + /// An int value representing the time in milliseconds to lock the database for a write operation + /// + /// + /// The default value is 1800 milliseconds + /// + /// The timeout in milliseconds. + public int SqlWriteLockTimeOut + { + get + { + try + { + return int.Parse(ConfigurationManager.AppSettings[Constants.AppSettings.SqlWriteLockTimeOut]); + } + catch + { + return 1800; + } + } + } } } diff --git a/src/Umbraco.Core/Configuration/IGlobalSettings.cs b/src/Umbraco.Core/Configuration/IGlobalSettings.cs index fd5140042c..483829f85f 100644 --- a/src/Umbraco.Core/Configuration/IGlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/IGlobalSettings.cs @@ -72,5 +72,10 @@ /// Gets the location of temporary files. /// string LocalTempPath { get; } + + /// + /// Gets the write lock timeout. + /// + int SqlWriteLockTimeOut { get; } } } diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index cb3b0d48d0..0182034011 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -140,6 +140,14 @@ namespace Umbraco.Core /// public const string DatabaseFactoryServerVersion = "Umbraco.Core.Debug.DatabaseFactoryServerVersion"; } + + /// + /// An int value representing the time in milliseconds to lock the database for a write operation + /// + /// + /// The default value is 1800 milliseconds + /// + public const string SqlWriteLockTimeOut = "Umbraco.Core.SqlWriteLockTimeOut"; } } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index d37905f221..046f54405a 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Data; using System.Data.SqlServerCe; using System.Linq; using NPoco; +using Umbraco.Core.Composing; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -163,7 +165,8 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault() if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead) throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); - db.Execute(@"SET LOCK_TIMEOUT 1800;"); + var timeOut = Current.Configs.Global().SqlWriteLockTimeOut; + db.Execute(@"SET LOCK_TIMEOUT " + timeOut + ";"); // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks foreach (var lockId in lockIds) { diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 9d33a2169c..3fc5e36f6e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Linq; using NPoco; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Scoping; @@ -254,7 +256,8 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) public override void WriteLock(IDatabase db, params int[] lockIds) { - WriteLock(db, TimeSpan.FromMilliseconds(1800), lockIds); + var timeOut = Current.Configs.Global().SqlWriteLockTimeOut; + WriteLock(db, TimeSpan.FromMilliseconds(timeOut), lockIds); } public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds) diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 572d572ab7..1121a48823 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -23,7 +23,8 @@ namespace Umbraco.Tests.TestHelpers settings.LocalTempStorageLocation == LocalTempStorage.Default && settings.LocalTempPath == IOHelper.MapPath("~/App_Data/TEMP") && settings.ReservedPaths == (GlobalSettings.StaticReservedPaths + "~/umbraco") && - settings.ReservedUrls == GlobalSettings.StaticReservedUrls); + settings.ReservedUrls == GlobalSettings.StaticReservedUrls && + settings.SqlWriteLockTimeOut == 1800); return config; } From 55124fff81b077770ea9d4ca1b36c6fedbf65b29 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sun, 7 Feb 2021 02:50:20 +0100 Subject: [PATCH 42/86] Update icons in umb-tree-search-results component (#9757) * Avoid use of ng-if * Green class is not necessary * Ensure button element use full width * Left align text inside button element --- .../src/less/components/tree/umb-tree.less | 3 ++- .../components/tree/umb-tree-search-results.html | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 8234618393..6ab663a8b2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -132,7 +132,6 @@ body.touch .umb-tree { .umb-tree .umb-search-group { position: inherit; display: inherit; - list-style: none; h6 { @@ -155,6 +154,8 @@ body.touch .umb-tree { &-link { display: block; + width: 100%; + text-align: left; } &-name { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html index 1264d68c76..f472251624 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html @@ -7,13 +7,14 @@
      • -
        -
    • public const string DataType = "icon-autofill"; + /// + /// System dictionary icon + /// + public const string Dictionary = "icon-book-alt"; + + /// + /// System generic folder icon + /// + public const string Folder = "icon-folder"; + + /// + /// System language icon + /// + public const string Language = "icon-globe"; + + /// + /// System logviewer icon + /// + public const string LogViewer = "icon-box-alt"; + /// /// System list view icon /// @@ -69,6 +89,11 @@ ///
      public const string MemberType = "icon-users"; + /// + /// System packages icon + /// + public const string Packages = "icon-box"; + /// /// System property editor icon /// diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 6a7fb7f5ad..8dd41e19bf 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt, Constants.ObjectTypes.DataType, id, queryStrings, "icon-folder", dt.HasChildren); + var node = CreateTreeNode(dt, Constants.ObjectTypes.DataType, id, queryStrings, Constants.Icons.Folder, dt.HasChildren); node.Path = dt.Path; node.NodeType = "container"; // TODO: This isn't the best way to ensure a no operation process for clicking a node but it works for now. diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs index f21f0b2868..6540eb4a63 100644 --- a/src/Umbraco.Web/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.Trees id, queryStrings, x.ItemKey, - "icon-book-alt", + Constants.Icons.Dictionary, Services.LocalizationService.GetDictionaryItemChildren(x.Key).Any()))); } else @@ -83,7 +83,7 @@ namespace Umbraco.Web.Trees id, queryStrings, x.ItemKey, - "icon-book-alt", + Constants.Icons.Dictionary, Services.LocalizationService.GetDictionaryItemChildren(x.Key).Any()))); } diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index ac2c0571e0..863f94a9f5 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Trees //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.Languages}/overview"; - root.Icon = "icon-globe"; + root.Icon = Constants.Icons.Language; root.HasChildren = false; root.MenuUrl = null; diff --git a/src/Umbraco.Web/Trees/LogViewerTreeController.cs b/src/Umbraco.Web/Trees/LogViewerTreeController.cs index 7452828d00..964852d4bd 100644 --- a/src/Umbraco.Web/Trees/LogViewerTreeController.cs +++ b/src/Umbraco.Web/Trees/LogViewerTreeController.cs @@ -33,8 +33,8 @@ namespace Umbraco.Web.Trees var root = base.CreateRootNode(queryStrings); //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.LogViewer, "overview"); - root.Icon = "icon-box-alt"; + root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.LogViewer}/overview"; + root.Icon = Constants.Icons.LogViewer; root.HasChildren = false; root.MenuUrl = null; diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs index 9b1bf98823..b6921771b6 100644 --- a/src/Umbraco.Web/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Trees //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Packages}/{Constants.Trees.Packages}/repo"; - root.Icon = "icon-box"; + root.Icon = Constants.Icons.Packages; root.HasChildren = false; return root; From fcd802ba47b4001fb0da003b504dddb28afddb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 8 Feb 2021 15:16:25 +0100 Subject: [PATCH 50/86] V8/bugfix/9074 current urls segments (#9075) --- .../content/umbcontentnodeinfo.directive.js | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 22b85b8b39..c20c2a368d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -1,4 +1,4 @@ -(function () { +(function () { 'use strict'; function ContentNodeInfoDirective($timeout, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource, overlayService, entityResource) { @@ -159,7 +159,7 @@ } scope.openTemplate = function () { - var template = _.findWhere(scope.allTemplates, {alias: scope.node.template}) + var template = _.findWhere(scope.allTemplates, { alias: scope.node.template }) if (!template) { return; } @@ -200,7 +200,7 @@ //don't load this if it's already done if (auditTrailLoaded && !forceReload) { - return; + return; } scope.loadingAuditTrail = true; @@ -251,7 +251,7 @@ function setAuditTrailLogTypeColor(auditTrail) { angular.forEach(auditTrail, function (item) { - + switch (item.logType) { case "Save": item.logTypeColor = "primary"; @@ -263,7 +263,7 @@ case "Unpublish": case "UnpublishVariant": item.logTypeColor = "warning"; - break; + break; case "Delete": item.logTypeColor = "danger"; break; @@ -318,14 +318,9 @@ return; } - // find the URLs for the currently selected language - if (scope.node.variants.length > 1) { - // nodes with variants - scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language && scope.currentVariant.language.culture === url.culture)); - } else { - // invariant nodes - scope.currentUrls = scope.node.urls; - } + // find the urls for the currently selected language + // when there is no selected language (allow vary by culture == false), show all urls of the node. + scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language == null || scope.currentVariant.language.culture === url.culture)); // figure out if multiple cultures apply across the content URLs scope.currentUrlsHaveMultipleCultures = _.keys(_.groupBy(scope.currentUrls, url => url.culture)).length > 1; From f8de0fca8fbd649a2eef225deb328a7101539637 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Tue, 9 Feb 2021 11:43:59 +1000 Subject: [PATCH 51/86] 5596 allow editing alt text in grid (#9681) * languages * add service method for media crop details, might be useful elsewhere * update UI to add buttons for changing image / editing existing * update mediapicker controller to use named editor service method * Update media.controller.js undefined should be null, consistent use of arrow func * move buttons to the top right - close to the grid toolbar, so more obvious --- .../src/common/services/editor.service.js | 20 ++- .../src/less/components/umb-grid.less | 14 +- src/Umbraco.Web.UI.Client/src/less/main.less | 3 + .../mediapicker/mediapicker.controller.js | 5 +- .../overlays/mediacropdetails.controller.js | 25 ++-- .../overlays/mediacropdetails.html | 9 +- .../grid/editors/media.controller.js | 135 ++++++++++-------- .../propertyeditors/grid/editors/media.html | 27 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 1 + 11 files changed, 149 insertions(+), 92 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 08cd67460e..326123f797 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -592,6 +592,23 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#mediaCropDetails + * @methodOf umbraco.services.editorService + * + * @description + * Opens the media crop details editor in infinite editing, the submit callback returns the updated media object. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object + */ + function mediaCropDetails(editor) { + editor.view = "views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#iconPicker @@ -1054,7 +1071,8 @@ When building a custom infinite editor view you can use the same components as a macroPicker: macroPicker, memberGroupPicker: memberGroupPicker, memberPicker: memberPicker, - memberEditor: memberEditor + memberEditor: memberEditor, + mediaCropDetails }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index e1fc5573e5..3b084c9905 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -475,9 +475,19 @@ } } +// Control states +.umb-grid-media--controls { + display:none; + position: absolute; + top:0.5rem; + right:0.5rem; +} - - +.umb-grid .umb-row .umb-control.-active { + .umb-grid-media--controls { + display:flex; + } +} // Title bar and tools .umb-grid .umb-row-title-bar { diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index f9bd179caf..31bb8484c4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -606,6 +606,9 @@ table thead button:focus{ display: inline; } +.relative { + position:relative; +} // Input label styles // @Simon: not sure where to put this part yet diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 7214e0b0ea..6f9ce6ee34 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -373,12 +373,11 @@ angular.module("umbraco") function openDetailsDialog() { const dialog = { - view: "views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html", size: "small", cropSize: $scope.cropSize, target: $scope.target, disableFocalPoint: $scope.disableFocalPoint, - submit: function (model) { + submit: function () { $scope.model.selection.push($scope.target); $scope.model.submit($scope.model); @@ -392,7 +391,7 @@ angular.module("umbraco") localizationService.localize("defaultdialogs_editSelectedMedia").then(value => { dialog.title = value; - editorService.open(dialog); + editorService.mediaCropDetails(dialog); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js index 1c7b2a7520..c6927cbaa9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js @@ -7,8 +7,9 @@ vm.submit = submit; vm.close = close; vm.hasCrops = cropSet() === true; - + vm.focalPointChanged = focalPointChanged; vm.disableFocalPoint = false; + if(typeof $scope.model.disableFocalPoint === "boolean") { vm.disableFocalPoint = $scope.model.disableFocalPoint } @@ -20,25 +21,17 @@ $scope.model.target.focalPoint = { left: .5, top: .5 }; } - vm.shouldShowUrl = shouldShowUrl; - vm.focalPointChanged = focalPointChanged; - if (!$scope.model.target.image) { $scope.model.target.image = $scope.model.target.url; } - function shouldShowUrl() { - if (!$scope.model.target) { - return false; - } - if ($scope.model.target.id) { - return false; - } - if ($scope.model.target.url && $scope.model.target.url.toLower().indexOf("blob:") === 0) { - return false; - } - return true; - } + if (!$scope.model.target + || $scope.model.target.id + || ($scope.model.target.url && $scope.model.target.url.toLowerCase().startsWith("blob:"))) { + vm.shouldShowUrl = false; + } else { + vm.shouldShowUrl = true; + } /** * Called when the umbImageGravity component updates the focal point value diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html index da6e3f439c..de936da163 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html @@ -10,7 +10,7 @@ -
      +
      @@ -24,6 +24,13 @@
      +
      +
      + +
      + +
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index c2c4d8081c..716ca405c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,10 +1,8 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", - function ($scope, $timeout, userService, editorService) { - - - $scope.thumbnailUrl = getThumbnailUrl(); + function ($scope, userService, editorService, localizationService) { + $scope.thumbnailUrl = getThumbnailUrl(); if (!$scope.model.config.startNodeId) { if ($scope.model.config.ignoreUserStartNodes === true) { @@ -12,101 +10,118 @@ angular.module("umbraco") $scope.model.config.startNodeIsVirtual = true; } else { - userService.getCurrentUser().then(function (userData) { + userService.getCurrentUser().then(userData => { $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; }); } } - - $scope.setImage = function(){ - var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; - var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - var value = $scope.control.value; - var target = value - ? { - udi: value.udi, - url: value.image, - image: value.image, - focalPoint: value.focalPoint, - coordinates: value.coordinates - } - : null; + + $scope.setImage = function() { + var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : null; + var mediaPicker = { startNodeId: startNodeId, - startNodeIsVirtual: startNodeIsVirtual, - cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined, + startNodeIsVirtual: startNodeId ? $scope.model.config.startNodeIsVirtual : null, + cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : null, showDetails: true, disableFolderSelect: true, onlyImages: true, dataTypeKey: $scope.model.dataTypeKey, - currentTarget: target, - submit: function(model) { - var selectedImage = model.selection[0]; - - $scope.control.value = { - focalPoint: selectedImage.focalPoint, - coordinates: selectedImage.coordinates, - id: selectedImage.id, - udi: selectedImage.udi, - image: selectedImage.image, - caption: selectedImage.altText - }; - + submit: model => { + updateControlValue(model.selection[0]); editorService.close(); }, - close: function() { - editorService.close(); - } - } - + close: () => editorService.close() + }; + editorService.mediaPicker(mediaPicker); }; + + $scope.editImage = function() { + + const mediaCropDetailsConfig = { + size: 'small', + target: $scope.control.value, + submit: model => { + updateControlValue(model.target); + editorService.close(); + }, + close: () => editorService.close() + }; + + localizationService.localize('defaultdialogs_editSelectedMedia').then(value => { + mediaCropDetailsConfig.title = value; + editorService.mediaCropDetails(mediaCropDetailsConfig); + }); + } - $scope.$watch('control.value', function(newValue, oldValue) { - if(Utilities.equals(newValue, oldValue)){ - return; // simply skip that - } - - $scope.thumbnailUrl = getThumbnailUrl(); - }, true); - + /** + * + */ function getThumbnailUrl() { - if($scope.control.value && $scope.control.value.image) { + if ($scope.control.value && $scope.control.value.image) { var url = $scope.control.value.image; - if($scope.control.editor.config && $scope.control.editor.config.size){ + if ($scope.control.editor.config && $scope.control.editor.config.size){ if ($scope.control.value.coordinates) { // New way, crop by percent must come before width/height. var coords = $scope.control.value.coordinates; - url += "?crop=" + coords.x1 + "," + coords.y1 + "," + coords.x2 + "," + coords.y2 + "&cropmode=percentage"; + url += `?crop=${coords.x1},${coords.y1},${coords.x2},${coords.y2}&cropmode=percentage`; } else { // Here in order not to break existing content where focalPoint were used. // For some reason width/height have to come first when mode=crop. if ($scope.control.value.focalPoint) { - url += "?center=" + $scope.control.value.focalPoint.top + "," + $scope.control.value.focalPoint.left; - url += "&mode=crop"; + url += `?center=${$scope.control.value.focalPoint.top},${$scope.control.value.focalPoint.left}`; + url += '&mode=crop'; } else { // Prevent black padding and no crop when focal point not set / changed from default - url += "?center=0.5,0.5&mode=crop"; + url += '?center=0.5,0.5&mode=crop'; } } - url += "&width=" + $scope.control.editor.config.size.width; - url += "&height=" + $scope.control.editor.config.size.height; - url += "&animationprocessmode=first"; + url += '&width=' + $scope.control.editor.config.size.width; + url += '&height=' + $scope.control.editor.config.size.height; + url += '&animationprocessmode=first'; } // set default size if no crop present (moved from the view) - if (url.indexOf('?') == -1) + if (url.includes('?') === false) { - url += "?width=800&upscale=false&animationprocessmode=false" + url += '?width=800&upscale=false&animationprocessmode=false' } + return url; } return null; - }; + } -}); + /** + * + * @param {object} selectedImage + */ + function updateControlValue(selectedImage) { + + const doGetThumbnail = $scope.control.value.focalPoint !== selectedImage.focalPoint + || $scope.control.value.image !== selectedImage.image; + + // we could apply selectedImage directly to $scope.control.value, + // but this allows excluding fields in future if needed + $scope.control.value = { + focalPoint: selectedImage.focalPoint, + coordinates: selectedImage.coordinates, + id: selectedImage.id, + udi: selectedImage.udi, + image: selectedImage.image, + caption: selectedImage.caption, + altText: selectedImage.altText + }; + + + if (doGetThumbnail) { + $scope.thumbnailUrl = getThumbnailUrl(); + } + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html index 2ab42807fd..2f73dc82ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html @@ -6,14 +6,23 @@ -
      - - - -
      +
      + + +
      + + + + +
      +
      diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 97fc223cc3..bc2f4c0c1e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -268,6 +268,7 @@ Statistics Title (optional) Alternative text (optional) + Caption (optional) Type Unpublish Unpublished 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 1456cce9f0..94645eaa85 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -272,6 +272,7 @@ Statistics Title (optional) Alternative text (optional) + Caption (optional) Type Unpublish Draft diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 75377f54c9..88c6458a31 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -263,6 +263,7 @@ Statistiques Titre (optionnel) Texte alternatif (optionnel) + Légende (optionnel) Type Dépublier Dépublié From 3b3d55ca263af995251db360de701442b0ae4c5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 9 Feb 2021 13:43:28 +1100 Subject: [PATCH 52/86] Fixes issue with broken caches used for user permissions Calculating start nodes is expensive and this is supposed to be cached but the cache was not working. --- src/Umbraco.Core/Cache/CacheKeys.cs | 5 + .../Models/Identity/IdentityMapDefinition.cs | 15 +- src/Umbraco.Core/Models/Membership/User.cs | 8 +- src/Umbraco.Core/Models/UserExtensions.cs | 172 +++++++++--------- .../Security/BackOfficeUserStore.cs | 22 ++- .../Security/ContentPermissionsHelper.cs | 21 ++- .../Models/UserExtensionsTests.cs | 3 +- .../Controllers/ContentControllerUnitTests.cs | 27 +-- .../Controllers/MediaControllerUnitTests.cs | 15 +- .../UserEditorAuthorizationHelperTests.cs | 37 ++-- src/Umbraco.Web/Cache/UserCacheRefresher.cs | 7 + src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 8 +- .../Filters/ContentSaveValidationAttribute.cs | 11 +- .../MediaItemSaveValidationAttribute.cs | 9 +- .../UserGroupAuthorizationAttribute.cs | 3 +- .../UserGroupEditorAuthorizationHelper.cs | 9 +- src/Umbraco.Web/Editors/MediaController.cs | 11 +- .../Editors/UserEditorAuthorizationHelper.cs | 13 +- .../Editors/UserGroupsController.cs | 6 +- src/Umbraco.Web/Editors/UsersController.cs | 6 +- .../Models/Mapping/ContentMapDefinition.cs | 22 ++- .../Models/Mapping/UserMapDefinition.cs | 8 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 38 +++- .../Trees/ContentTreeController.cs | 4 +- .../Trees/ContentTreeControllerBase.cs | 12 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 4 +- .../Filters/AdminUsersAuthorizeAttribute.cs | 2 +- .../CheckIfUserTicketDataIsStaleAttribute.cs | 4 +- ...EnsureUserPermissionForContentAttribute.cs | 4 +- .../EnsureUserPermissionForMediaAttribute.cs | 1 + .../FilterAllowedOutgoingContentAttribute.cs | 34 ++-- .../FilterAllowedOutgoingMediaAttribute.cs | 2 +- 33 files changed, 325 insertions(+), 220 deletions(-) diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index b8ee0e97c4..0e9a9a3862 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -12,5 +12,10 @@ public const string MacroContentCacheKey = "macroContent_"; // used in MacroRenderers public const string MacroFromAliasCacheKey = "macroFromAlias_"; + + public const string UserAllContentStartNodesPrefix = "AllContentStartNodes"; + public const string UserAllMediaStartNodesPrefix = "AllMediaStartNodes"; + public const string UserMediaStartNodePathsPrefix = "MediaStartNodePaths"; + public const string UserContentStartNodePathsPrefix = "ContentStartNodePaths"; } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs index 57e1c9ee5c..47d33f3014 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Mapping; using Umbraco.Core.Models.Membership; @@ -11,7 +12,9 @@ namespace Umbraco.Core.Models.Identity private readonly ILocalizedTextService _textService; private readonly IEntityService _entityService; private readonly IGlobalSettings _globalSettings; + private readonly AppCaches _appCaches; + [Obsolete("Use constructor specifying all dependencies")] public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings) { _textService = textService; @@ -19,6 +22,14 @@ namespace Umbraco.Core.Models.Identity _globalSettings = globalSettings; } + public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings, AppCaches appCaches) + { + _textService = textService; + _entityService = entityService; + _globalSettings = globalSettings; + _appCaches = appCaches; + } + public void DefineMaps(UmbracoMapper mapper) { mapper.Define( @@ -46,8 +57,8 @@ namespace Umbraco.Core.Models.Identity target.Groups = source.Groups.ToArray(); */ - target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService); - target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService); + target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); + target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService, _appCaches); target.Email = source.Email; target.UserName = source.Username; target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime(); diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 3d071b0a18..dc39463925 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; using Umbraco.Core.Composing; @@ -384,11 +385,10 @@ namespace Umbraco.Core.Models.Membership #endregion - /// - /// This is used as an internal cache for this entity - specifically for calculating start nodes so we don't re-calculated all of the time - /// [IgnoreDataMember] [DoNotClone] + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This should not be used, it's currently used for only a single edge case - should probably be removed for netcore")] internal IDictionary AdditionalData { get @@ -402,6 +402,8 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] [DoNotClone] + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Not used, will be removed in future versions")] internal object AdditionalDataLock => _additionalDataLock; protected override void PerformDeepClone(object clone) diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 5be66bac47..fdb833a821 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -150,48 +150,40 @@ namespace Umbraco.Core.Models } } - internal static bool HasContentRootAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); - } + internal static bool HasContentRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - internal static bool HasContentBinAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); - } + internal static bool HasContentBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); - } + internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); - } + internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService) + internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, AppCaches appCaches) { if (content == null) throw new ArgumentNullException(nameof(content)); - return ContentPermissionsHelper.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + return ContentPermissionsHelper.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); } - internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService) + internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService, AppCaches appCaches) { if (media == null) throw new ArgumentNullException(nameof(media)); - return ContentPermissionsHelper.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + return ContentPermissionsHelper.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); } - internal static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService) + internal static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) { if (entity == null) throw new ArgumentNullException(nameof(entity)); - return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); } - internal static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService) + internal static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) { if (entity == null) throw new ArgumentNullException(nameof(entity)); - return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); } /// @@ -204,84 +196,92 @@ namespace Umbraco.Core.Models return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); } - // calc. start nodes, combining groups' and user's, and excluding what's in the bin + [Obsolete("Use the overload specifying all parameters instead")] public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService) - { - const string cacheKey = "AllContentStartNodes"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; + => CalculateContentStartNodeIds(user, entityService, Current.AppCaches); - var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray(); - var usn = user.StartContentIds; - var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); - ToUserCache(user, cacheKey, vals); - return vals; + /// + /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin + /// + /// + /// + /// + /// + public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => + { + var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray(); + var usn = user.StartContentIds; + var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); + return vals; + }, TimeSpan.FromMinutes(2), true); + + return result; } - // calc. start nodes, combining groups' and user's, and excluding what's in the bin + [Obsolete("Use the overload specifying all parameters instead")] public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService) - { - const string cacheKey = "AllMediaStartNodes"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; + => CalculateMediaStartNodeIds(user, entityService, Current.AppCaches); - var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray(); - var usn = user.StartMediaIds; - var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); - ToUserCache(user, cacheKey, vals); - return vals; + /// + /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin + /// + /// + /// + /// + /// + public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => + { + var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray(); + var usn = user.StartMediaIds; + var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); + return vals; + }, TimeSpan.FromMinutes(2), true); + + return result; } + [Obsolete("Use the overload specifying all parameters instead")] public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService) - { - const string cacheKey = "MediaStartNodePaths"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; + => GetMediaStartNodePaths(user, entityService, Current.AppCaches); - var startNodeIds = user.CalculateMediaStartNodeIds(entityService); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); - ToUserCache(user, cacheKey, vals); - return vals; + public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => + { + var startNodeIds = user.CalculateMediaStartNodeIds(entityService, appCaches); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); + return vals; + }, TimeSpan.FromMinutes(2), true); + + return result; } + [Obsolete("Use the overload specifying all parameters instead")] public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService) + => GetContentStartNodePaths(user, entityService, Current.AppCaches); + + public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) { - const string cacheKey = "ContentStartNodePaths"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; - - var startNodeIds = user.CalculateContentStartNodeIds(entityService); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); - ToUserCache(user, cacheKey, vals); - return vals; - } - - private static T FromUserCache(IUser user, string cacheKey) - where T: class - { - if (!(user is User entityUser)) return null; - - lock (entityUser.AdditionalDataLock) + var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => { - return entityUser.AdditionalData.TryGetValue(cacheKey, out var allContentStartNodes) - ? allContentStartNodes as T - : null; - } - } + var startNodeIds = user.CalculateContentStartNodeIds(entityService, appCaches); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); + return vals; + }, TimeSpan.FromMinutes(2), true); - private static void ToUserCache(IUser user, string cacheKey, T vals) - where T: class - { - if (!(user is User entityUser)) return; - - lock (entityUser.AdditionalDataLock) - { - entityUser.AdditionalData[cacheKey] = vals; - } + return result; } private static bool StartsWithPath(string test, string path) diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 7df328b5b7..fe1673bca6 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Threading.Tasks; using System.Web.Security; using Microsoft.AspNet.Identity; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -38,20 +40,26 @@ namespace Umbraco.Core.Security private readonly IExternalLoginService _externalLoginService; private readonly IGlobalSettings _globalSettings; private readonly UmbracoMapper _mapper; + private readonly AppCaches _appCaches; private bool _disposed = false; + [Obsolete("Use the constructor specifying all dependencies")] public BackOfficeUserStore(IUserService userService, IMemberTypeService memberTypeService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, MembershipProviderBase usersMembershipProvider, UmbracoMapper mapper) + : this(userService, memberTypeService, entityService, externalLoginService, globalSettings, usersMembershipProvider, mapper, Current.AppCaches) { } + + public BackOfficeUserStore(IUserService userService, IMemberTypeService memberTypeService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, MembershipProviderBase usersMembershipProvider, UmbracoMapper mapper, AppCaches appCaches) { + if (userService == null) throw new ArgumentNullException("userService"); + if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider"); + if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); + _userService = userService; _memberTypeService = memberTypeService; _entityService = entityService; _externalLoginService = externalLoginService; - _globalSettings = globalSettings; - if (userService == null) throw new ArgumentNullException("userService"); - if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider"); - if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); + _globalSettings = globalSettings; _mapper = mapper; - + _appCaches = appCaches; _userService = userService; _externalLoginService = externalLoginService; @@ -775,8 +783,8 @@ namespace Umbraco.Core.Security } //we should re-set the calculated start nodes - identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService); - identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService); + identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches); + identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches); //reset all changes identityUser.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Security/ContentPermissionsHelper.cs b/src/Umbraco.Core/Security/ContentPermissionsHelper.cs index 1a329fcdcb..8c3a138f6a 100644 --- a/src/Umbraco.Core/Security/ContentPermissionsHelper.cs +++ b/src/Umbraco.Core/Security/ContentPermissionsHelper.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -25,6 +26,7 @@ namespace Umbraco.Core.Security IUser user, IUserService userService, IEntityService entityService, + AppCaches appCaches, params char[] permissionsToCheck) { if (user == null) throw new ArgumentNullException("user"); @@ -33,7 +35,7 @@ namespace Umbraco.Core.Security if (content == null) return ContentAccess.NotFound; - var hasPathAccess = user.HasPathAccess(content, entityService); + var hasPathAccess = user.HasPathAccess(content, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; @@ -52,6 +54,7 @@ namespace Umbraco.Core.Security IUser user, IUserService userService, IEntityService entityService, + AppCaches appCaches, params char[] permissionsToCheck) { if (user == null) throw new ArgumentNullException("user"); @@ -60,7 +63,7 @@ namespace Umbraco.Core.Security if (entity == null) return ContentAccess.NotFound; - var hasPathAccess = user.HasContentPathAccess(entity, entityService); + var hasPathAccess = user.HasContentPathAccess(entity, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; @@ -89,6 +92,7 @@ namespace Umbraco.Core.Security IUser user, IUserService userService, IEntityService entityService, + AppCaches appCaches, out IUmbracoEntity entity, params char[] permissionsToCheck) { @@ -100,16 +104,16 @@ namespace Umbraco.Core.Security entity = null; if (nodeId == Constants.System.Root) - hasPathAccess = user.HasContentRootAccess(entityService); + hasPathAccess = user.HasContentRootAccess(entityService, appCaches); else if (nodeId == Constants.System.RecycleBinContent) - hasPathAccess = user.HasContentBinAccess(entityService); + hasPathAccess = user.HasContentBinAccess(entityService, appCaches); if (hasPathAccess.HasValue) return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; entity = entityService.Get(nodeId, UmbracoObjectTypes.Document); if (entity == null) return ContentAccess.NotFound; - hasPathAccess = user.HasContentPathAccess(entity, entityService); + hasPathAccess = user.HasContentPathAccess(entity, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; @@ -140,6 +144,7 @@ namespace Umbraco.Core.Security IUserService userService, IContentService contentService, IEntityService entityService, + AppCaches appCaches, out IContent contentItem, params char[] permissionsToCheck) { @@ -152,16 +157,16 @@ namespace Umbraco.Core.Security contentItem = null; if (nodeId == Constants.System.Root) - hasPathAccess = user.HasContentRootAccess(entityService); + hasPathAccess = user.HasContentRootAccess(entityService, appCaches); else if (nodeId == Constants.System.RecycleBinContent) - hasPathAccess = user.HasContentBinAccess(entityService); + hasPathAccess = user.HasContentBinAccess(entityService, appCaches); if (hasPathAccess.HasValue) return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; contentItem = contentService.GetById(nodeId); if (contentItem == null) return ContentAccess.NotFound; - hasPathAccess = user.HasPathAccess(contentItem, entityService); + hasPathAccess = user.HasPathAccess(contentItem, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; diff --git a/src/Umbraco.Tests/Models/UserExtensionsTests.cs b/src/Umbraco.Tests/Models/UserExtensionsTests.cs index bf76031b59..18ece0c841 100644 --- a/src/Umbraco.Tests/Models/UserExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/UserExtensionsTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -34,7 +35,7 @@ namespace Umbraco.Tests.Models .Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns((type, ids) => new[] { new TreeEntityPath { Id = startNodeId, Path = startNodePath } }); - Assert.AreEqual(outcome, user.HasPathAccess(content, esmock.Object)); + Assert.AreEqual(outcome, user.HasPathAccess(content, esmock.Object, AppCaches.Disabled)); } [TestCase("", "1", "1")] // single user start, top level diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs index f55a4e593b..26aea30c26 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs @@ -2,6 +2,7 @@ using System.Web.Http; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -34,7 +35,7 @@ namespace Umbraco.Tests.Web.Controllers var userService = userServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -62,7 +63,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.NotFound, result); @@ -93,7 +94,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -124,7 +125,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -156,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -178,7 +179,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -200,7 +201,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -224,7 +225,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -248,7 +249,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -278,7 +279,7 @@ namespace Umbraco.Tests.Web.Controllers //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent, new[] { 'A' }); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'A' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -306,7 +307,7 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent, new[] { 'B' }); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'B' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -336,7 +337,7 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent, new[] { 'A' }); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'A' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -364,7 +365,7 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent, new[] { 'B' }); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'B' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); diff --git a/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs index f409d81a2d..c347d81288 100644 --- a/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs @@ -2,6 +2,7 @@ using System.Web.Http; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -31,7 +32,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, 1234); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, 1234); //assert Assert.IsTrue(result); @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act/assert - Assert.Throws(() => MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, 1234)); + Assert.Throws(() => MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, 1234)); } [Test] @@ -77,7 +78,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, 1234); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, 1234); //assert Assert.IsFalse(result); @@ -97,7 +98,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -1); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -1); //assert Assert.IsTrue(result); @@ -119,7 +120,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -1); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -1); //assert Assert.IsFalse(result); @@ -139,7 +140,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -21); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -21); //assert Assert.IsTrue(result); @@ -161,7 +162,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -21); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -21); //assert Assert.IsFalse(result); diff --git a/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs b/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs index 04694b21ee..4478b59085 100644 --- a/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -32,7 +33,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); @@ -54,7 +56,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); @@ -79,7 +82,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] {"FunGroup"}); @@ -104,7 +108,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "test" }); @@ -141,7 +146,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234, 5555 }, new int[0], new string[0]); @@ -179,7 +185,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]); @@ -217,7 +224,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 1234 but currentUser doesn't have access to it ... nope var result = authHelper.IsAuthorized(currentUser, savingUser, new []{1234}, new int[0], new string[0]); @@ -255,7 +263,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... ok var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 5555 }, new int[0], new string[0]); @@ -293,7 +302,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 1234 but currentUser doesn't have access to it ... nope var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] {1234}, new string[0]); @@ -331,7 +341,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... ok var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 5555 }, new string[0]); @@ -369,7 +380,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234, 5555 }, new string[0]); @@ -407,7 +419,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]); diff --git a/src/Umbraco.Web/Cache/UserCacheRefresher.cs b/src/Umbraco.Web/Cache/UserCacheRefresher.cs index 922a9df385..ce2cbbf754 100644 --- a/src/Umbraco.Web/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserCacheRefresher.cs @@ -42,7 +42,14 @@ namespace Umbraco.Web.Cache { var userCache = AppCaches.IsolatedCaches.Get(); if (userCache) + { userCache.Result.Clear(RepositoryCacheKeys.GetKey(id)); + userCache.Result.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id); + userCache.Result.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id); + userCache.Result.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id); + userCache.Result.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id); + } + base.Remove(id); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6f85a08751..7093edf23d 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1168,7 +1168,7 @@ namespace Umbraco.Web.Editors //if this item's path has already been denied or if the user doesn't have access to it, add to the deny list if (denied.Any(x => c.Path.StartsWith($"{x.Path},")) || (ContentPermissionsHelper.CheckPermissions(c, - Security.CurrentUser, Services.UserService, Services.EntityService, + Security.CurrentUser, Services.UserService, Services.EntityService, AppCaches, ActionPublish.ActionLetter) == ContentPermissionsHelper.ContentAccess.Denied)) { denied.Add(c); diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 65d9305906..456e0e20f2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -670,9 +670,9 @@ namespace Umbraco.Web.Editors switch (type) { case UmbracoEntityTypes.Document: - return Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + return Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches); case UmbracoEntityTypes.Media: - return Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + return Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches); default: return Array.Empty(); } @@ -811,10 +811,10 @@ namespace Umbraco.Web.Editors switch (entityType) { case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches); break; case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches); break; } diff --git a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs index fbce2d0414..c0e190989a 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs @@ -7,6 +7,7 @@ using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Security; @@ -29,11 +30,12 @@ namespace Umbraco.Web.Editors.Filters private readonly IContentService _contentService; private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService) + public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { } - public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService) + public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService, AppCaches appCaches) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); @@ -41,6 +43,7 @@ namespace Umbraco.Web.Editors.Filters _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + _appCaches = appCaches; } public override void OnActionExecuting(HttpActionContext actionContext) @@ -195,13 +198,13 @@ namespace Umbraco.Web.Editors.Filters accessResult = ContentPermissionsHelper.CheckPermissions( contentToCheck, webSecurity.CurrentUser, - _userService, _entityService, permissionToCheck.ToArray()); + _userService, _entityService, _appCaches, permissionToCheck.ToArray()); } else { accessResult = ContentPermissionsHelper.CheckPermissions( contentIdToCheck, webSecurity.CurrentUser, - _userService, _contentService, _entityService, + _userService, _contentService, _entityService, _appCaches, out contentToCheck, permissionToCheck.ToArray()); if (contentToCheck != null) diff --git a/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs index 449ef95675..af973d0662 100644 --- a/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -23,18 +24,20 @@ namespace Umbraco.Web.Editors.Filters private readonly ILocalizedTextService _textService; private readonly IMediaService _mediaService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService) + public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService, Current.AppCaches) { } - public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService) + public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService, AppCaches appCaches) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _textService = textService ?? throw new ArgumentNullException(nameof(textService)); _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + _appCaches = appCaches; } public override void OnActionExecuting(HttpActionContext actionContext) @@ -91,7 +94,7 @@ namespace Umbraco.Web.Editors.Filters if (MediaController.CheckPermissions( actionContext.Request.Properties, _umbracoContextAccessor.UmbracoContext.Security.CurrentUser, - _mediaService, _entityService, + _mediaService, _entityService, _appCaches, contentIdToCheck, contentToCheck) == false) { actionContext.Response = actionContext.Request.CreateUserNoAccessResponse(); diff --git a/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs b/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs index e1d6626055..e94098db82 100644 --- a/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs @@ -54,7 +54,8 @@ namespace Umbraco.Web.Editors.Filters Current.Services.UserService, Current.Services.ContentService, Current.Services.MediaService, - Current.Services.EntityService); + Current.Services.EntityService, + Current.AppCaches); return authHelper.AuthorizeGroupAccess(currentUser, intIds); } } diff --git a/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs index 985c42bbbf..ea403758d0 100644 --- a/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -13,13 +14,15 @@ namespace Umbraco.Web.Editors.Filters private readonly IContentService _contentService; private readonly IMediaService _mediaService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - public UserGroupEditorAuthorizationHelper(IUserService userService, IContentService contentService, IMediaService mediaService, IEntityService entityService) + public UserGroupEditorAuthorizationHelper(IUserService userService, IContentService contentService, IMediaService mediaService, IEntityService entityService, AppCaches appCaches) { _userService = userService; _contentService = contentService; _mediaService = mediaService; _entityService = entityService; + _appCaches = appCaches; } /// @@ -111,7 +114,7 @@ namespace Umbraco.Web.Editors.Filters var content = _contentService.GetById(proposedContentStartId.Value); if (content != null) { - if (currentUser.HasPathAccess(content, _entityService) == false) + if (currentUser.HasPathAccess(content, _entityService, _appCaches) == false) return Attempt.Fail("Current user doesn't have access to the content path " + content.Path); } } @@ -121,7 +124,7 @@ namespace Umbraco.Web.Editors.Filters var media = _mediaService.GetById(proposedMediaStartId.Value); if (media != null) { - if (currentUser.HasPathAccess(media, _entityService) == false) + if (currentUser.HasPathAccess(media, _entityService, _appCaches) == false) return Attempt.Fail("Current user doesn't have access to the media path " + media.Path); } } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index dfe6939552..022512e2a4 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -239,7 +239,7 @@ namespace Umbraco.Web.Editors protected int[] UserStartNodes { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } + get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches)); } } /// @@ -835,6 +835,7 @@ namespace Umbraco.Web.Editors Security.CurrentUser, Services.MediaService, Services.EntityService, + AppCaches, intParentId) == false) { throw new HttpResponseException(Request.CreateResponse( @@ -919,7 +920,7 @@ namespace Umbraco.Web.Editors /// The content to lookup, if the contentItem is not specified /// Specifies the already resolved content item to check against, setting this ignores the nodeId /// - internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, int nodeId, IMedia media = null) + internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, AppCaches appCaches, int nodeId, IMedia media = null) { if (storage == null) throw new ArgumentNullException("storage"); if (user == null) throw new ArgumentNullException("user"); @@ -940,10 +941,10 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasMediaRootAccess(entityService) + ? user.HasMediaRootAccess(entityService, appCaches) : (nodeId == Constants.System.RecycleBinMedia) - ? user.HasMediaBinAccess(entityService) - : user.HasPathAccess(media, entityService); + ? user.HasMediaBinAccess(entityService, appCaches) + : user.HasPathAccess(media, entityService, appCaches); return hasPathAccess; } diff --git a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs index 320580aaf9..f666b6d5a3 100644 --- a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; @@ -14,13 +15,15 @@ namespace Umbraco.Web.Editors private readonly IMediaService _mediaService; private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IUserService userService, IEntityService entityService) + public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IUserService userService, IEntityService entityService, AppCaches appCaches) { _contentService = contentService; _mediaService = mediaService; _userService = userService; _entityService = entityService; + _appCaches = appCaches; } /// @@ -114,7 +117,7 @@ namespace Umbraco.Web.Editors { if (contentId == Constants.System.Root) { - var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService), Constants.System.RecycleBinContent); + var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinContent); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the content root"); } @@ -122,7 +125,7 @@ namespace Umbraco.Web.Editors { var content = _contentService.GetById(contentId); if (content == null) continue; - var hasAccess = currentUser.HasPathAccess(content, _entityService); + var hasAccess = currentUser.HasPathAccess(content, _entityService, _appCaches); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the content path " + content.Path); } @@ -135,7 +138,7 @@ namespace Umbraco.Web.Editors { if (mediaId == Constants.System.Root) { - var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService), Constants.System.RecycleBinMedia); + var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinMedia); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the media root"); } @@ -143,7 +146,7 @@ namespace Umbraco.Web.Editors { var media = _mediaService.GetById(mediaId); if (media == null) continue; - var hasAccess = currentUser.HasPathAccess(media, _entityService); + var hasAccess = currentUser.HasPathAccess(media, _entityService, _appCaches); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the media path " + media.Path); } diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index b081ca6137..77f7a305af 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -28,7 +28,11 @@ namespace Umbraco.Web.Editors //authorize that the user has access to save this user group var authHelper = new UserGroupEditorAuthorizationHelper( - Services.UserService, Services.ContentService, Services.MediaService, Services.EntityService); + Services.UserService, + Services.ContentService, + Services.MediaService, + Services.EntityService, + AppCaches); var isAuthorized = authHelper.AuthorizeGroupAccess(Security.CurrentUser, userGroupSave.Alias); if (isAuthorized == false) diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index b022e6f27a..21a32ef12f 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -304,7 +304,7 @@ namespace Umbraco.Web.Editors CheckUniqueEmail(userSave.Email, null); //Perform authorization here to see if the current user can actually save this user with the info being requested - var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService, AppCaches); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, null, null, null, userSave.UserGroups); if (canSaveUser == false) { @@ -398,7 +398,7 @@ namespace Umbraco.Web.Editors } //Perform authorization here to see if the current user can actually save this user with the info being requested - var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService, AppCaches); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups); if (canSaveUser == false) { @@ -573,7 +573,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); //Perform authorization here to see if the current user can actually save this user with the info being requested - var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService, AppCaches); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); if (canSaveUser == false) { diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index 983172f8e1..ec3367d4d3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -29,14 +31,25 @@ namespace Umbraco.Web.Models.Mapping private readonly ILogger _logger; private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; private readonly TabsAndPropertiesMapper _tabsAndPropertiesMapper; private readonly ContentSavedStateMapper _stateMapper; private readonly ContentBasicSavedStateMapper _basicStateMapper; private readonly ContentVariantMapper _contentVariantMapper; - public ContentMapDefinition(CommonMapper commonMapper, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService, - IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger, - IUserService userService, IEntityService entityService) + public ContentMapDefinition( + CommonMapper commonMapper, + ILocalizedTextService localizedTextService, + IContentService contentService, + IContentTypeService contentTypeService, + IFileService fileService, + IUmbracoContextAccessor umbracoContextAccessor, + IPublishedRouter publishedRouter, + ILocalizationService localizationService, + ILogger logger, + IUserService userService, + IEntityService entityService, + AppCaches appCaches) { _commonMapper = commonMapper; _localizedTextService = localizedTextService; @@ -49,6 +62,7 @@ namespace Umbraco.Web.Models.Mapping _logger = logger; _userService = userService; _entityService = entityService; + _appCaches = appCaches; _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(localizedTextService); _stateMapper = new ContentSavedStateMapper(); _basicStateMapper = new ContentBasicSavedStateMapper(); @@ -238,7 +252,7 @@ namespace Umbraco.Web.Models.Mapping // false here. if (context.HasItems && context.Items.TryGetValue("CurrentUser", out var usr) && usr is IUser currentUser) { - userStartNodes = currentUser.CalculateContentStartNodeIds(_entityService); + userStartNodes = currentUser.CalculateContentStartNodeIds(_entityService, _appCaches); if (!userStartNodes.Contains(Constants.System.Root)) { // return false if this is the user's actual start node, the node will be rendered in the tree diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index aa158799cb..6c58309f57 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -273,8 +273,8 @@ namespace Umbraco.Web.Models.Mapping { target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName); target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache); - target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService), UmbracoObjectTypes.Document, "content/contentRoot", context); - target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService), UmbracoObjectTypes.Media, "media/mediaRoot", context); + target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document, "content/contentRoot", context); + target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media, "media/mediaRoot", context); target.CreateDate = source.CreateDate; target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); target.Email = source.Email; @@ -327,8 +327,8 @@ namespace Umbraco.Web.Models.Mapping target.Email = source.Email; target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash(); target.Name = source.Name; - target.StartContentIds = source.CalculateContentStartNodeIds(_entityService); - target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService); + target.StartContentIds = source.CalculateContentStartNodeIds(_entityService, _appCaches); + target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); target.UserId = source.Id; //we need to map the legacy UserType diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index a22e2a6f6a..6d777287d6 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -5,6 +5,8 @@ using System.Text; using System.Text.RegularExpressions; using Examine; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -30,14 +32,29 @@ namespace Umbraco.Web.Search private readonly UmbracoMapper _mapper; private readonly ISqlContext _sqlContext; private readonly IUmbracoTreeSearcherFields _umbracoTreeSearcherFields; + private readonly AppCaches _appCaches; - - public UmbracoTreeSearcher(IExamineManager examineManager, + [Obsolete("Use constructor specifying all dependencies instead")] + public UmbracoTreeSearcher( + IExamineManager examineManager, UmbracoContext umbracoContext, ILocalizationService languageService, IEntityService entityService, UmbracoMapper mapper, - ISqlContext sqlContext,IUmbracoTreeSearcherFields umbracoTreeSearcherFields) + ISqlContext sqlContext, + IUmbracoTreeSearcherFields umbracoTreeSearcherFields) + : this(examineManager, umbracoContext, languageService, entityService, mapper, sqlContext, umbracoTreeSearcherFields, Current.AppCaches) + { } + + public UmbracoTreeSearcher( + IExamineManager examineManager, + UmbracoContext umbracoContext, + ILocalizationService languageService, + IEntityService entityService, + UmbracoMapper mapper, + ISqlContext sqlContext, + IUmbracoTreeSearcherFields umbracoTreeSearcherFields, + AppCaches appCaches) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoContext = umbracoContext; @@ -46,6 +63,7 @@ namespace Umbraco.Web.Search _mapper = mapper; _sqlContext = sqlContext; _umbracoTreeSearcherFields = umbracoTreeSearcherFields; + _appCaches = appCaches; } /// @@ -112,13 +130,13 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: type = "media"; fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeMediaFields()); - var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); + var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches); AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeDocumentFields()); - var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); + var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches); AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; default: @@ -462,11 +480,13 @@ namespace Umbraco.Web.Search var defaultLang = _languageService.GetDefaultLanguageIsoCode(); foreach (var result in results) { - var entity = _mapper.Map(result, context => { - if(culture != null) { - context.SetCulture(culture); - } + var entity = _mapper.Map(result, context => + { + if (culture != null) + { + context.SetCulture(culture); } + } ); var intId = entity.Id.TryConvertTo(); diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 663af43643..4bf5be4008 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees private int[] _userStartNodes; protected override int[] UserStartNodes - => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService)); + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches)); public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { @@ -165,7 +165,7 @@ namespace Umbraco.Web.Trees } //if the user has no path access for this node, all they can do is refresh - if (!Security.CurrentUser.HasContentPathAccess(item, Services.EntityService)) + if (!Security.CurrentUser.HasContentPathAccess(item, Services.EntityService, AppCaches)) { var menu = new MenuItemCollection(); menu.Items.Add(new RefreshNode(Services.TextService, true)); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 95de72b7bf..afb38dea07 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -126,12 +126,12 @@ namespace Umbraco.Web.Trees switch (RecycleBinId) { case Constants.System.RecycleBinMedia: - startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService); + startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches); + startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService, AppCaches); break; case Constants.System.RecycleBinContent: - startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService); + startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches); + startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService, AppCaches); break; default: throw new NotSupportedException("Path access is only determined on content or media"); @@ -291,8 +291,8 @@ namespace Umbraco.Web.Trees { if (entity == null) return false; return RecycleBinId == Constants.System.RecycleBinContent - ? Security.CurrentUser.HasContentPathAccess(entity, Services.EntityService) - : Security.CurrentUser.HasMediaPathAccess(entity, Services.EntityService); + ? Security.CurrentUser.HasContentPathAccess(entity, Services.EntityService, AppCaches) + : Security.CurrentUser.HasMediaPathAccess(entity, Services.EntityService, AppCaches); } /// diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index df44a809c9..2ad3c2af0a 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -50,7 +50,7 @@ namespace Umbraco.Web.Trees private int[] _userStartNodes; protected override int[] UserStartNodes - => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches)); /// /// Creates a tree node for a content item based on an UmbracoEntity @@ -117,7 +117,7 @@ namespace Umbraco.Web.Trees } //if the user has no path access for this node, all they can do is refresh - if (!Security.CurrentUser.HasMediaPathAccess(item, Services.EntityService)) + if (!Security.CurrentUser.HasMediaPathAccess(item, Services.EntityService, AppCaches)) { menu.Items.Add(new RefreshNode(Services.TextService, true)); return menu; diff --git a/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs index 96226622b0..3494a5c0c7 100644 --- a/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs @@ -50,7 +50,7 @@ namespace Umbraco.Web.WebApi.Filters if (userIds.Length == 0) return base.IsAuthorized(actionContext); var users = Current.Services.UserService.GetUsersById(userIds); - var authHelper = new UserEditorAuthorizationHelper(Current.Services.ContentService, Current.Services.MediaService, Current.Services.UserService, Current.Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Current.Services.ContentService, Current.Services.MediaService, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches); return users.All(user => authHelper.IsAuthorized(Current.UmbracoContext.Security.CurrentUser, user, null, null, null) != false); } } diff --git a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index 2a57ec10b2..3bbee7ca41 100644 --- a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -85,12 +85,12 @@ namespace Umbraco.Web.WebApi.Filters () => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false, () => { - var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, Current.Services.EntityService); + var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, Current.Services.EntityService, Current.AppCaches); return startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false; }, () => { - var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, Current.Services.EntityService); + var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, Current.Services.EntityService, Current.AppCaches); return startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false; } }; diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 28f09b46b7..fcf8cf1dd6 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -115,11 +115,13 @@ namespace Umbraco.Web.WebApi.Filters nodeId = _nodeId.Value; } - var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId, + var permissionResult = ContentPermissionsHelper.CheckPermissions( + nodeId, Current.UmbracoContext.Security.CurrentUser, Current.Services.UserService, Current.Services.ContentService, Current.Services.EntityService, + Current.AppCaches, out var contentItem, _permissionToCheck.HasValue ? new[] { _permissionToCheck.Value } : null); diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs index 60e2889fd5..ef99aa4778 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs @@ -124,6 +124,7 @@ namespace Umbraco.Web.WebApi.Filters Current.UmbracoContext.Security.CurrentUser, Current.Services.MediaService, Current.Services.EntityService, + Current.AppCaches, nodeId)) { base.OnActionExecuting(actionContext); diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs index 31e0b22ce1..23512aa144 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs @@ -9,7 +9,7 @@ using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Core.Models; using Umbraco.Web.Actions; - +using Umbraco.Core.Cache; namespace Umbraco.Web.WebApi.Filters { @@ -21,52 +21,48 @@ namespace Umbraco.Web.WebApi.Filters { private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; private readonly char _permissionToCheck; public FilterAllowedOutgoingContentAttribute(Type outgoingType) - : this(outgoingType, Current.Services.UserService, Current.Services.EntityService) + : this(outgoingType, ActionBrowse.ActionLetter, string.Empty, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { - _permissionToCheck = ActionBrowse.ActionLetter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck) - : this(outgoingType, Current.Services.UserService, Current.Services.EntityService) + : this(outgoingType, permissionToCheck, string.Empty, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { - _permissionToCheck = permissionToCheck; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName) - : this(outgoingType, propertyName, Current.Services.UserService, Current.Services.EntityService) + : this(outgoingType, ActionBrowse.ActionLetter, propertyName, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { - _permissionToCheck = ActionBrowse.ActionLetter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, IUserService userService, IEntityService entityService) - : base(outgoingType) + : this(outgoingType, ActionBrowse.ActionLetter, string.Empty, userService, entityService, Current.AppCaches) { - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); - _permissionToCheck = ActionBrowse.ActionLetter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, IUserService userService, IEntityService entityService) - : base(outgoingType) + : this(outgoingType, permissionToCheck, string.Empty, userService, entityService, Current.AppCaches) { - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); - _userService = userService; - _entityService = entityService; - _permissionToCheck = permissionToCheck; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName, IUserService userService, IEntityService entityService) + : this(outgoingType, ActionBrowse.ActionLetter, propertyName, userService, entityService, Current.AppCaches) + { + } + + private FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, string propertyName, IUserService userService, IEntityService entityService, AppCaches appCaches) : base(outgoingType, propertyName) { _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + _appCaches = appCaches; _userService = userService; _entityService = entityService; - _permissionToCheck = ActionBrowse.ActionLetter; + _permissionToCheck = permissionToCheck; } protected override void FilterItems(IUser user, IList items) @@ -78,7 +74,7 @@ namespace Umbraco.Web.WebApi.Filters protected override int[] GetUserStartNodes(IUser user) { - return user.CalculateContentStartNodeIds(_entityService); + return user.CalculateContentStartNodeIds(_entityService, _appCaches); } protected override int RecycleBinId diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 21dc60e6cc..5e308bd3c1 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.WebApi.Filters protected virtual int[] GetUserStartNodes(IUser user) { - return user.CalculateMediaStartNodeIds(Current.Services.EntityService); + return user.CalculateMediaStartNodeIds(Current.Services.EntityService, Current.AppCaches); } protected virtual int RecycleBinId => Constants.System.RecycleBinMedia; From 6b4f99394d952deb347e1d8c6ac2ade947f52fc6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 9 Feb 2021 15:30:39 +1100 Subject: [PATCH 53/86] Fix mini profiler so that it actually profiles the Content APIs in the back office --- src/Umbraco.Web/Logging/WebProfiler.cs | 10 +++++++--- src/Umbraco.Web/Logging/WebProfilerProvider.cs | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Logging/WebProfiler.cs b/src/Umbraco.Web/Logging/WebProfiler.cs index 512edb2296..740afba6a7 100755 --- a/src/Umbraco.Web/Logging/WebProfiler.cs +++ b/src/Umbraco.Web/Logging/WebProfiler.cs @@ -26,12 +26,16 @@ namespace Umbraco.Web.Logging _provider = new WebProfilerProvider(); //see https://miniprofiler.com/dotnet/AspDotNet - MiniProfiler.Configure(new MiniProfilerOptions + var options = new MiniProfilerOptions { SqlFormatter = new SqlServerFormatter(), - StackMaxLength = 5000, + StackMaxLength = 5000, ProfilerProvider = _provider - }); + }; + // this is a default path and by default it performs a 'contains' check which will match our content controller + // (and probably other requests) and ignore them. + options.IgnoredPaths.Remove("/content/"); + MiniProfiler.Configure(options); } public void UmbracoApplicationBeginRequest(object sender, EventArgs e) diff --git a/src/Umbraco.Web/Logging/WebProfilerProvider.cs b/src/Umbraco.Web/Logging/WebProfilerProvider.cs index dbb81cf232..f38a606745 100755 --- a/src/Umbraco.Web/Logging/WebProfilerProvider.cs +++ b/src/Umbraco.Web/Logging/WebProfilerProvider.cs @@ -85,7 +85,11 @@ namespace Umbraco.Web.Logging public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options) { var first = Interlocked.Exchange(ref _first, 1) == 0; - if (first == false) return base.Start(profilerName, options); + if (first == false) + { + var profiler = base.Start(profilerName, options); + return profiler; + } _startupProfiler = new MiniProfiler("StartupProfiler", options); CurrentProfiler = _startupProfiler; From 4a10c9971767e2031ae68c3380f70ce7cd99ea8c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Feb 2021 13:17:28 +0100 Subject: [PATCH 54/86] Adds CoC enforcement document --- .github/CODE_OF_CONDUCT.md | 2 + .github/CODE_OF_CONDUCT_ENFORCEMENT.md | 57 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/CODE_OF_CONDUCT_ENFORCEMENT.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 7a81989037..cdb57cd7c9 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -43,6 +43,8 @@ Community leaders (e.g. Meetup & festival organizers, moderators, maintainers, . Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. +Specific enforcement steps are listed in the [Code of Conduct Enforcement Guidelines](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT_ENFORCEMENT.md) document which is an appendix of this document, updated and maintained by the Code of Conduct Team. + ## Scope This Code of Conduct applies within all community spaces and events supported by Umbraco HQ or using the Umbraco name. It also applies when an individual is officially representing the community in public spaces. diff --git a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md new file mode 100644 index 0000000000..ac2e812528 --- /dev/null +++ b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md @@ -0,0 +1,57 @@ +# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder + +These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member. + +This is an appendix to the Code of Conduct and is updated and maintained by the Code of Conduct Team. + +To make sure that all reports will be reviewed and investigated promptly and fairly, as highlighted in the Umbraco Code of Conduct, we are following [Mozilla’s Consequence Ladder approach](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md). + +This approach helps the Team enforce the Code of Conduct in a structured manner and can be used as a way of communicating escalation. Each time the Team takes an action (warning, ban) the individual is made aware of future consequences. The Team can either follow the order of the levels in the ladder or decide to jump levels. When needed, the team can go directly to a permanent ban. + +**Level 0: No Action** +Recommendations do not indicate a violation of the Code of Conduct. + +**Level 1: Simple Warning Issued** +A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. + +**Level 2: Warning** +A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* Communication of next-level consequences if behaviors are repeated (according to this ladder). + +**Level 3: Warning + Mandatory Cooling Off Period (Access Retained)** +A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. +* Require they do not interact with others in the report, or those who they suspect are involved in the report. +* Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved. + +**Level 4: Temporary Ban (Access Revoked)** +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* 3-6 months imposed break. +* All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable). +* Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. +* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process) +* No attendance at Umbraco events during the ban period. +* Not allowed to enter Umbraco HQ offices during the ban period. +* Permission to use the MVP title, if applicable, is revoked during this ban period. +* The community leaders running events and other initiatives are informed of the ban. + +**Level 5: Permanent Ban** +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* All accounts deactivated permanently. +* No attendance at Umbraco events going forward. +* Not allowed to enter Umbraco HQ offices permanently. +* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) permanently suspended. +* Permission to use the MVP title, if applicable, revoked. +* The community leaders running events and other initiatives are informed of the ban. + + +Sources: +* [Mozilla Code of Conduct - Enforcement Consequence Ladder](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md) +* [Drupal Conflict Resolution Policy and Process](https://www.drupal.org/conflict-resolution) +* [Django Code of Conduct - Enforcement Manual](https://www.djangoproject.com/conduct/enforcement-manual/) From 6694690a1114694c5c94d512758adca79dbceeae Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Feb 2021 13:20:32 +0100 Subject: [PATCH 55/86] Duh forgot to save latest version --- .github/CODE_OF_CONDUCT_ENFORCEMENT.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md index ac2e812528..2bb45644c2 100644 --- a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md +++ b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md @@ -1,4 +1,4 @@ -# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder +# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member. @@ -17,23 +17,23 @@ A private, written warning from the Code of Conduct Team, with clarity of violat **Level 2: Warning** A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: -* Communication of next-level consequences if behaviors are repeated (according to this ladder). +* Communication of next-level consequences if behaviors are repeated (according to this ladder). **Level 3: Warning + Mandatory Cooling Off Period (Access Retained)** A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: * Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). - * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. * Require they do not interact with others in the report, or those who they suspect are involved in the report. * Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved. **Level 4: Temporary Ban (Access Revoked)** -Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: * 3-6 months imposed break. * All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable). * Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). - * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. * All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process) * No attendance at Umbraco events during the ban period. * Not allowed to enter Umbraco HQ offices during the ban period. From deb38d3f79dbdd945a356e5d949ffbdd77b8c37b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Feb 2021 14:42:55 +0100 Subject: [PATCH 56/86] Clarification of the CoC --- .github/CODE_OF_CONDUCT.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index cdb57cd7c9..e97b03e7e3 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -60,6 +60,8 @@ Or alternatively, you can reach out directly to any of the team members behind t The review process is done with full respect for the privacy and security of the reporter of any incident. +People with a conflict of interest should exclude themselves or if necessary be excluded by the other team members. + ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: From e9429cfe44507e028ab714c9e2bc1740c746e004 Mon Sep 17 00:00:00 2001 From: Jakob Bagterp <25110864+jakob-bagterp@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:54:01 +0100 Subject: [PATCH 57/86] Separate remove button and label for settings and styles in Grid Layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's inconvenient that the click area for the remove button laps over the label of settings and styles in the Grid Layout property editor. Simply as the editor may remove the style/setting by accident – and especially since there isn't an undo option. Known issue: Linebreak of long text labels doesn't appear pretty, but this is cosmetic and can be fixed. --- .../src/views/propertyeditors/grid/grid.prevalues.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index 9bf32675e3..f1f1a90ef4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -117,11 +117,10 @@
    • - + {{configValue.label}}
    @@ -145,12 +144,10 @@
  • - - + {{style.label}}
From 230bf1052cde249587d4ce06a2fb23859094f414 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Wed, 10 Feb 2021 23:25:38 +0000 Subject: [PATCH 58/86] V8/feature/super doctype create menu (#9174) * WIP - added options from superdoctypecreate package resolve merge conflict in create html file * fiddling with the wording but it will never be perfect * looks like ng-if should be ng-show * Update data-elements for uniqueness (I'm not sure what these are used for!) * pass 'iscomposition' on the querystring when choosing the 'composition' option from the menu Why? - in the future we could hide the 'Compositions...' button when choosing to create a composition * Remove PostCreateCollection implementation now the Create Collection is removed in this PR from the DocType Create menu resolve merge conflict in Remove PostCreateCollection implementation now the Create Collection is removed in this PR from the DocType Create menu * Add translations * Put back PostSave method. It's useful. Co-authored-by: Kenn Jacobsen Co-authored-by: Nathan Woulfe --- .../common/resources/contenttype.resource.js | 35 +------ .../views/documenttypes/create.controller.js | 75 ++++---------- .../src/views/documenttypes/create.html | 99 ++++++------------- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 10 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 10 ++ .../Umbraco/config/lang/en_us.xml | 10 ++ .../Editors/ContentTypeController.cs | 86 +++------------- 7 files changed, 96 insertions(+), 229 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 01b6360d07..6acf702546 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -511,41 +511,8 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateContainer", { parentId: parentId, name: encodeURIComponent(name) })), 'Failed to create a folder under parent id ' + parentId); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#createCollection - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Create a collection of a content types - * - * ##usage - *
-        * contentTypeResource.createCollection(1244,"testcollectionname",true,"collectionItemName",true,"icon-name","icon-name")
-        *    .then(function() {
-        *       Do stuff..
-        *    });
-        * 
- * - * @param {Int} parentId the ID of the parent content type underneath which to create the collection - * @param {String} collectionName the name of the collection - * @param {Boolean} collectionCreateTemplate true/false to specify whether to create a default template for the collection - * @param {String} collectionItemName the name of the collection item - * @param {Boolean} collectionItemCreateTemplate true/false to specify whether to create a default template for the collection item - * @param {String} collectionIcon the icon for the collection - * @param {String} collectionItemIcon the icon for the collection item - * @returns {Promise} resourcePromise object. - * - */ - createCollection: function (parentId, collectionName, collectionCreateTemplate, collectionItemName, collectionItemCreateTemplate, collectionIcon, collectionItemIcon) { - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateCollection", { parentId: parentId, collectionName: collectionName, collectionCreateTemplate: collectionCreateTemplate, collectionItemName: collectionItemName, collectionItemCreateTemplate: collectionItemCreateTemplate, collectionIcon: collectionIcon, collectionItemIcon: collectionItemIcon})), - 'Failed to create collection under ' + parentId); - - }, + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 71eee085ee..23173a404d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -11,8 +11,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.model = { allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === "container", folderName: "", - creatingFolder: false, - creatingDoctypeCollection: false + creatingFolder: false }; var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; @@ -24,12 +23,6 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.model.creatingFolder = true; }; - $scope.showCreateDocTypeCollection = function () { - $scope.model.creatingDoctypeCollection = true; - $scope.model.collectionCreateTemplate = !$scope.model.disableTemplates; - $scope.model.collectionItemCreateTemplate = !$scope.model.disableTemplates; - }; - $scope.createContainer = function () { if (formHelper.submitForm({ scope: $scope, formCtrl: $scope.createFolderForm })) { @@ -58,55 +51,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con }); } - }; - - $scope.createCollection = function () { - - if (formHelper.submitForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm, statusMessage: "Creating Doctype Collection..." })) { - - // see if we can find matching icons - var collectionIcon = "icon-folders", collectionItemIcon = "icon-document"; - iconHelper.getIcons().then(function (icons) { - - for (var i = 0; i < icons.length; i++) { - // for matching we'll require a full match for collection, partial match for item - if (icons[i].substring(5) == $scope.model.collectionName.toLowerCase()) { - collectionIcon = icons[i]; - } else if (icons[i].substring(5).indexOf($scope.model.collectionItemName.toLowerCase()) > -1) { - collectionItemIcon = icons[i]; - } - } - - contentTypeResource.createCollection(node.id, $scope.model.collectionName, $scope.model.collectionCreateTemplate, $scope.model.collectionItemName, $scope.model.collectionItemCreateTemplate, collectionIcon, collectionItemIcon) - .then(function (collectionData) { - - navigationService.hideMenu(); - $location.search('create', null); - $location.search('notemplate', null); - - formHelper.resetForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm }); - - var section = appState.getSectionState("currentSection"); - - // redirect to the item id - $location.path("/" + section + "/documenttypes/edit/" + collectionData.containerId); - - }, function (err) { - - formHelper.resetForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm, hasErrors: true }); - $scope.error = err; - - //show any notifications - if (Utilities.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - }); - }); - } - - }; + }; // Disabling logic for creating document type with template if disableTemplates is set to true if (!disableTemplates) { @@ -125,6 +70,22 @@ function DocumentTypesCreateController($scope, $location, navigationService, con navigationService.hideMenu(); }; + $scope.createComposition = function () { + $location.search('create', null); + $location.search('notemplate', null); + $location.search('iscomposition', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true").search("iscomposition", "true"); + navigationService.hideMenu(); + }; + + $scope.createElement = function () { + $location.search('create', null); + $location.search('notemplate', null); + $location.search('iselement', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true").search("iselement", "true"); + navigationService.hideMenu(); + }; + $scope.close = function() { const showMenu = true; navigationService.hideDialog(showMenu); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 4ebb96c09b..208b4eaeda 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -1,16 +1,15 @@
- diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html index 46b51b5f34..673b90ef85 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html @@ -1,47 +1,53 @@ -