From 708308be8c51dfeb6472a7c7a9b4c714bfe98737 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Thu, 15 Jul 2021 17:40:18 +0200 Subject: [PATCH 01/34] Update Media.cs rename contentType parameter to mediaType --- src/Umbraco.Core/Models/Media.cs | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index f5b3574be7..7a0264caf3 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models @@ -15,9 +15,9 @@ namespace Umbraco.Cms.Core.Models /// /// name of the Media object /// Parent object - /// MediaType for the current Media object - public Media(string name, IMedia parent, IMediaType contentType) - : this(name, parent, contentType, new PropertyCollection()) + /// MediaType for the current Media object + public Media(string name, IMedia parent, IMediaType mediaType) + : this(name, parent, mediaType, new PropertyCollection()) { } /// @@ -25,10 +25,10 @@ namespace Umbraco.Cms.Core.Models /// /// name of the Media object /// Parent object - /// MediaType for the current Media object + /// MediaType for the current Media object /// Collection of properties - public Media(string name, IMedia parent, IMediaType contentType, IPropertyCollection properties) - : base(name, parent, contentType, properties) + public Media(string name, IMedia parent, IMediaType mediaType, IPropertyCollection properties) + : base(name, parent, mediaType, properties) { } /// @@ -36,9 +36,9 @@ namespace Umbraco.Cms.Core.Models /// /// name of the Media object /// Id of the Parent IMedia - /// MediaType for the current Media object - public Media(string name, int parentId, IMediaType contentType) - : this(name, parentId, contentType, new PropertyCollection()) + /// MediaType for the current Media object + public Media(string name, int parentId, IMediaType mediaType) + : this(name, parentId, mediaType, new PropertyCollection()) { } /// @@ -46,10 +46,10 @@ namespace Umbraco.Cms.Core.Models /// /// Name of the Media object /// Id of the Parent IMedia - /// MediaType for the current Media object + /// MediaType for the current Media object /// Collection of properties - public Media(string name, int parentId, IMediaType contentType, IPropertyCollection properties) - : base(name, parentId, contentType, properties) + public Media(string name, int parentId, IMediaType mediaType, IPropertyCollection properties) + : base(name, parentId, mediaType, properties) { } /// @@ -57,9 +57,9 @@ namespace Umbraco.Cms.Core.Models /// /// New MediaType for this Media /// Leaves PropertyTypes intact after change - internal void ChangeContentType(IMediaType contentType) + internal void ChangeContentType(IMediaType mediaType) { - ChangeContentType(contentType, false); + ChangeContentType(mediaType, false); } /// @@ -68,14 +68,14 @@ namespace Umbraco.Cms.Core.Models /// /// New MediaType for this Media /// Boolean indicating whether to clear PropertyTypes upon change - internal void ChangeContentType(IMediaType contentType, bool clearProperties) + internal void ChangeContentType(IMediaType mediaType, bool clearProperties) { - ChangeContentType(new SimpleContentType(contentType)); + ChangeContentType(new SimpleContentType(mediaType)); if (clearProperties) - Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes); + Properties.EnsureCleanPropertyTypes(mediaType.CompositionPropertyTypes); else - Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); + Properties.EnsurePropertyTypes(mediaType.CompositionPropertyTypes); Properties.ClearCollectionChangedEvents(); // be sure not to double add Properties.CollectionChanged += PropertiesChanged; From 2c885f7a86df0248d4633d1be42654017ec346f3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 12 Aug 2021 14:30:10 +0200 Subject: [PATCH 02/34] Bump version to 8.14.3 --- 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 625cbe3fb7..b9bbd54c8e 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.14.2")] -[assembly: AssemblyInformationalVersion("8.14.2")] +[assembly: AssemblyFileVersion("8.14.3")] +[assembly: AssemblyInformationalVersion("8.14.3")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ce465c6dc5..95ba9de0ca 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,9 +348,9 @@ False True - 8142 + 8143 / - http://localhost:8142 + http://localhost:8143 8131 / http://localhost:8131 From a5ff1830153b22012bbbdf0b8f942d45050d5cb0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 12 Aug 2021 14:48:15 +0200 Subject: [PATCH 03/34] Bump version to 8.15.2 --- 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 2dac2d8791..ff286570c3 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.15.1")] -[assembly: AssemblyInformationalVersion("8.15.1")] +[assembly: AssemblyFileVersion("8.15.2")] +[assembly: AssemblyInformationalVersion("8.15.2")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ea238d42d1..56ed9483fe 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,9 +348,9 @@ False True - 8151 + 8152 / - http://localhost:8151 + http://localhost:8152 False False From 33feb808dd934cd4b1160ea9bd66b7783507d05f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 12 Aug 2021 14:56:55 +0200 Subject: [PATCH 04/34] Fix version dependencies --- build/NuSpecs/UmbracoCms.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index a223704617..db34b5e984 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -26,8 +26,8 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - - + + From d566d42c4b5470607635c985411ae6c0bbcdfe70 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 20 Aug 2021 14:20:46 +0200 Subject: [PATCH 05/34] Remove unintentional change from 15 months ago that causes you to end up in your windows users folder after each build --- build/build.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/build/build.ps1 b/build/build.ps1 index 15d455b976..9ff7a097fd 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -553,7 +553,6 @@ # run if (-not $get) { -cd if ($command.Length -eq 0) { $command = @( "Build" ) From 3030269bdef03af05028e709ab920df1b343ce89 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 20 Aug 2021 14:21:22 +0200 Subject: [PATCH 06/34] Create a cloud-specific zip that includes necessary config transforms for cloud --- build/NuSpecs/tools/Web.config.cloud.xdt | 29 ++++++++++++++++++++++++ build/build.ps1 | 9 ++++++++ 2 files changed, 38 insertions(+) create mode 100644 build/NuSpecs/tools/Web.config.cloud.xdt diff --git a/build/NuSpecs/tools/Web.config.cloud.xdt b/build/NuSpecs/tools/Web.config.cloud.xdt new file mode 100644 index 0000000000..988c741126 --- /dev/null +++ b/build/NuSpecs/tools/Web.config.cloud.xdt @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/build.ps1 b/build/build.ps1 index 9ff7a097fd..55ceb774ee 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -91,6 +91,7 @@ { $src = "$($this.SolutionRoot)\src" $log = "$($this.BuildTemp)\belle.log" + Write-Host "Compile Belle" Write-Host "Logging to $log" @@ -352,6 +353,14 @@ "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" ` > $null if (-not $?) { throw "Failed to zip UmbracoCms." } + + Write-Host "Zip cms cloud" + $this.CopyFile("$($this.SolutionRoot)\build\NuSpecs\tools\Web.config.cloud.xdt", "$tmp\WebApp\Web.config.install.xdt") + &$this.BuildEnv.Zip a -r "$out\UmbracoCms.$($this.Version.Semver).Cloud.zip" ` + "$tmp\WebApp\*" ` + "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" ` + > $null + if (-not $?) { throw "Failed to zip UmbracoCms." } }) $ubuild.DefineMethod("PrepareBuild", From 22c49398e39b3d8e7f6d4479a090ad12cfcb1802 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 20 Aug 2021 15:59:31 +0200 Subject: [PATCH 07/34] Hide backdrop when closing/select navigation (#10907) (cherry picked from commit 72d33b8f6845fb4f3c713dd5f463509235557c52) # Conflicts: # src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js # src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js --- .../components/tree/umbtree.directive.js | 16 +--------- .../src/common/services/navigation.service.js | 31 ++++++++++++------- 2 files changed, 21 insertions(+), 26 deletions(-) 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 7868f79809..a39475f4da 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 @@ -3,7 +3,7 @@ * @name umbraco.directives.directive:umbTree * @restrict E **/ -function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService, backdropService) { +function umbTreeDirective($q, treeService, notificationsService) { return { restrict: 'E', @@ -318,18 +318,6 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } - // Close any potential backdrop and remove the #leftcolumn modifier class - function closeBackdrop() { - var aboveClass = 'above-backdrop'; - var leftColumn = $('#leftcolumn'); - var isLeftColumnOnTop = leftColumn.hasClass(aboveClass); - - if(isLeftColumnOnTop){ - backdropService.close(); - leftColumn.removeClass(aboveClass); - } - } - /** Returns the css classses assigned to the node (div element) */ $scope.getNodeCssClass = function (node) { if (!node) { @@ -369,8 +357,6 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use defined on the tree */ $scope.select = function (n, ev) { - - closeBackdrop() if (n.metaData && n.metaData.noAccess === true) { ev.preventDefault(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index c628e3a5b1..c8553ec02a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -30,7 +30,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService var element = $(args.element); element.addClass('above-backdrop'); }); - + //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "csegment", "lq", "sr"]; @@ -118,19 +118,28 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } function closeBackdrop() { - var aboveClass = 'above-backdrop'; - var leftColumn = $('#leftcolumn'); - var isLeftColumnOnTop = leftColumn.hasClass(aboveClass); - if(isLeftColumnOnTop){ - backdropService.close(); - leftColumn.removeClass(aboveClass); + var tourIsOpen = document.body.classList.contains("umb-tour-is-visible"); + if (tourIsOpen) { + return; + } + + var aboveClass = "above-backdrop"; + var leftColumn = document.getElementById("leftcolumn"); + + if (leftColumn) { + var isLeftColumnOnTop = leftColumn.classList.contains(aboveClass); + + if (isLeftColumnOnTop) { + backdropService.close(); + leftColumn.classList.remove(aboveClass); + } } } function showBackdrop() { var backDropOptions = { - 'element': $('#leftcolumn')[0] + 'element': document.getElementById('leftcolumn') }; backdropService.open(backDropOptions); } @@ -328,7 +337,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService appState.setGlobalState("showTray", false); }, - /** + /** * @ngdoc method * @name umbraco.services.navigationService#syncTree * @methodOf umbraco.services.navigationService @@ -361,14 +370,14 @@ function navigationService($routeParams, $location, $q, $injector, eventsService }); }, - /** + /** * @ngdoc method * @name umbraco.services.navigationService#hasTree * @methodOf umbraco.services.navigationService * * @description * Checks if a tree with the given alias exists. - * + * * @param {String} treeAlias the tree alias to check */ hasTree: function (treeAlias) { From 01ee495367577a6e7d74e99d1e66e58ccad2eed6 Mon Sep 17 00:00:00 2001 From: Evan Moore Date: Fri, 20 Aug 2021 10:55:09 -0400 Subject: [PATCH 08/34] Fix PhysicalFileSystem.GetRelaitvePath for *nix --- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 8c35e80988..db10cae416 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -258,16 +258,17 @@ namespace Umbraco.Cms.Core.IO // test URL var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char + // 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" + // or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg" + if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/')) + return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); + // 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(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(Constants.CharArrays.ForwardSlash); - // unchanged - what else? return path; } From c72cb1683b9626c4a38a68c08a4c959ca85b1778 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 23 Aug 2021 11:01:53 +0200 Subject: [PATCH 09/34] Removes unneeded flex that caused an issue with the image cropper datatype Fixes #10876 --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 11d11c7e3a..15c62b014b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -600,7 +600,6 @@ display: inline-block; position: relative; vertical-align: top; - flex:0; } .umb-cropper-gravity .gravity-container { From 42c59a91cafb4acb0bd12a7a4efae7314ac310dd Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 23 Aug 2021 11:14:27 +0200 Subject: [PATCH 10/34] Bump version to 8.16.0 --- src/SolutionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 56e0376196..156b91187f 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.16.0")] -[assembly: AssemblyInformationalVersion("8.16.0-rc")] +[assembly: AssemblyInformationalVersion("8.16.0")] From 306ed56027210a767d7acc74e991b5885f0135ba Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 23 Aug 2021 14:28:44 +0200 Subject: [PATCH 11/34] Create a cache for value editors --- src/Umbraco.Core/Cache/IValueEditorCache.cs | 10 ++++ src/Umbraco.Core/Cache/ValueEditorCache.cs | 57 +++++++++++++++++++ .../DependencyInjection/UmbracoBuilder.cs | 6 ++ .../Implement/PropertyValidationService.cs | 11 +++- .../Umbraco.Core/Models/VariationTests.cs | 4 +- 5 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Core/Cache/IValueEditorCache.cs create mode 100644 src/Umbraco.Core/Cache/ValueEditorCache.cs diff --git a/src/Umbraco.Core/Cache/IValueEditorCache.cs b/src/Umbraco.Core/Cache/IValueEditorCache.cs new file mode 100644 index 0000000000..72b85746c4 --- /dev/null +++ b/src/Umbraco.Core/Cache/IValueEditorCache.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Core.Cache +{ + public interface IValueEditorCache + { + public IDataValueEditor GetValueEditor(IDataEditor dataEditor, IDataType dataType); + } +} diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs new file mode 100644 index 0000000000..81f9348d60 --- /dev/null +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Core.Cache +{ + public class ValueEditorCache : IValueEditorCache, + INotificationHandler, + INotificationHandler + { + private readonly Dictionary> _valueEditorCache = new(); + + public IDataValueEditor GetValueEditor(IDataEditor editor, IDataType dataType) + { + // Instead of creating a value editor immediately check if we've already created one and use that. + IDataValueEditor valueEditor; + if (_valueEditorCache.TryGetValue(editor.Alias, out Dictionary dataEditorCache)) + { + if (dataEditorCache.TryGetValue(dataType.Id, out valueEditor)) + { + return valueEditor; + } + + valueEditor = editor.GetValueEditor(dataType.Configuration); + dataEditorCache[dataType.Id] = valueEditor; + return valueEditor; + + } + + valueEditor = editor.GetValueEditor(dataType.Configuration); + _valueEditorCache[editor.Alias] = new Dictionary { [dataType.Id] = valueEditor }; + return valueEditor; + } + + public void Handle(DataTypeSavedNotification notification) => + ClearCache(notification.SavedEntities.Select(x => x.Id)); + + public void Handle(DataTypeDeletedNotification notification) => + ClearCache(notification.DeletedEntities.Select(x => x.Id)); + + private void ClearCache(IEnumerable dataTypeIds) + { + // If a datatype is saved or deleted we have to clear any value editors based on their ID from the cache, + // since it could mean that their configuration has changed. + foreach (var id in dataTypeIds) + { + foreach (Dictionary editors in _valueEditorCache.Values) + { + editors.Remove(id); + } + } + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 648af8f082..35239121c0 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -253,6 +253,12 @@ namespace Umbraco.Cms.Core.DependencyInjection // register a basic/noop published snapshot service to be replaced Services.AddSingleton(); + + // Register ValueEditorCache used for validation + Services.AddSingleton(); + Services + .AddNotificationHandler() + .AddNotificationHandler(); } } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs b/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs index 5fd7971976..70580153de 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; @@ -13,12 +14,18 @@ namespace Umbraco.Cms.Core.Services.Implement private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; private readonly ILocalizedTextService _textService; + private readonly IValueEditorCache _valueEditorCache; - public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) + public PropertyValidationService( + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + ILocalizedTextService textService, + IValueEditorCache valueEditorCache) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; _textService = textService; + _valueEditorCache = valueEditorCache; } /// @@ -58,7 +65,7 @@ namespace Umbraco.Cms.Core.Services.Implement _textService.Localize("validation", "invalidPattern"), }; - var valueEditor = editor.GetValueEditor(dataType.Configuration); + IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType); foreach (var validationResult in valueEditor.Validate(postedValue, isRequired, validationRegExp)) { // If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate(). diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs index 892ef696c3..c2d5b785c9 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -603,7 +604,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models return new PropertyValidationService( propertyEditorCollection, dataTypeService, - localizedTextService); + localizedTextService, + new ValueEditorCache()); } } } From 82fc05ac5125add0ae5f02ccdbfef2ddc073ff37 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 23 Aug 2021 14:48:49 +0200 Subject: [PATCH 12/34] Cannot copy page with Block List block with property that 'looks like' JSON #10879 (#10925) (cherry picked from commit cd6cb3737e54118d83fd3d76fe750e2689620a8a) --- .../Compose/BlockEditorComponent.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Compose/BlockEditorComponent.cs b/src/Umbraco.Web/Compose/BlockEditorComponent.cs index a8b4cfb8ca..ac92aa6918 100644 --- a/src/Umbraco.Web/Compose/BlockEditorComponent.cs +++ b/src/Umbraco.Web/Compose/BlockEditorComponent.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models.Blocks; using Umbraco.Core.PropertyEditors; @@ -17,6 +18,17 @@ namespace Umbraco.Web.Compose { private ComplexPropertyEditorContentEventHandler _handler; private readonly BlockListEditorDataConverter _converter = new BlockListEditorDataConverter(); + private readonly ILogger _logger; + + [Obsolete("Use the ctor injecting dependencies.")] + public BlockEditorComponent() : this(Current.Logger) + { + } + + public BlockEditorComponent(ILogger logger) + { + _logger = logger; + } public void Initialize() { @@ -116,8 +128,23 @@ namespace Umbraco.Web.Compose // this gets a little ugly because there could be some other complex editor that contains another block editor // and since we would have no idea how to parse that, all we can do is try JSON Path to find another block editor // of our type - var json = JToken.Parse(asString); - if (ProcessJToken(json, createGuid, out var result)) + JToken json = null; + try + { + json = JToken.Parse(asString); + } + catch (Exception e) + { + // See issue https://github.com/umbraco/Umbraco-CMS/issues/10879 + // We are detecting JSON data by seeing if a string is surrounded by [] or {} + // If people enter text like [PLACEHOLDER] JToken parsing fails, it's safe to ignore though + // Logging this just in case in the future we find values that are not safe to ignore + _logger.Warn( + "The property {PropertyAlias} on content type {ContentTypeKey} has a value of: {BlockItemValue} - this was recognized as JSON but could not be parsed", + data.Key, propertyAliasToBlockItemData.Key, asString); + } + + if (json != null && ProcessJToken(json, createGuid, out var result)) { // need to re-save this back to the RawPropertyValues data.RawPropertyValues[propertyAliasToBlockItemData.Key] = result; From c690d5f3bab8c67f47365f2b83f222633505a6e6 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 23 Aug 2021 15:06:47 +0200 Subject: [PATCH 13/34] Add clarifying comment in ValueEditorCache --- src/Umbraco.Core/Cache/ValueEditorCache.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index 81f9348d60..4ee41f9e55 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -15,7 +15,9 @@ namespace Umbraco.Cms.Core.Cache public IDataValueEditor GetValueEditor(IDataEditor editor, IDataType dataType) { - // Instead of creating a value editor immediately check if we've already created one and use that. + // We try and get the dictionary based on the IDataEditor alias, + // this is here just in case a data type can have more than one value data editor. + // If this is not the case this could be simplified quite a bit, by just using the inner dictionary only. IDataValueEditor valueEditor; if (_valueEditorCache.TryGetValue(editor.Alias, out Dictionary dataEditorCache)) { From d085b117524033d4293cb449ebd1667a7c2319d4 Mon Sep 17 00:00:00 2001 From: Chad Date: Tue, 24 Aug 2021 00:44:48 +1200 Subject: [PATCH 14/34] DIspose Process (#10918) (cherry picked from commit a2dab5b6caaf55e221fbbdea5b021c6d343c0000) --- src/Umbraco.Core/Diagnostics/MiniDump.cs | 28 ++++++++++--------- src/Umbraco.Core/Persistence/LocalDb.cs | 17 ++++++----- .../Sync/DatabaseServerMessenger.cs | 10 ++++++- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Diagnostics/MiniDump.cs b/src/Umbraco.Core/Diagnostics/MiniDump.cs index e8c2e82f94..ca9596b4ae 100644 --- a/src/Umbraco.Core/Diagnostics/MiniDump.cs +++ b/src/Umbraco.Core/Diagnostics/MiniDump.cs @@ -79,24 +79,26 @@ namespace Umbraco.Core.Diagnostics private static bool Write(SafeHandle fileHandle, Option options, bool withException = false) { - var currentProcess = Process.GetCurrentProcess(); - var currentProcessHandle = currentProcess.Handle; - var currentProcessId = (uint)currentProcess.Id; + using (var currentProcess = Process.GetCurrentProcess()) + { + var currentProcessHandle = currentProcess.Handle; + var currentProcessId = (uint)currentProcess.Id; - MiniDumpExceptionInformation exp; + MiniDumpExceptionInformation exp; - exp.ThreadId = GetCurrentThreadId(); - exp.ClientPointers = false; - exp.ExceptionPointers = IntPtr.Zero; + exp.ThreadId = GetCurrentThreadId(); + exp.ClientPointers = false; + exp.ExceptionPointers = IntPtr.Zero; - if (withException) - exp.ExceptionPointers = Marshal.GetExceptionPointers(); + if (withException) + exp.ExceptionPointers = Marshal.GetExceptionPointers(); - var bRet = exp.ExceptionPointers == IntPtr.Zero - ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) - : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, ref exp, IntPtr.Zero, IntPtr.Zero); + var bRet = exp.ExceptionPointers == IntPtr.Zero + ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) + : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero); - return bRet; + return bRet; + } } public static bool Dump(Option options = Option.WithFullMemory, bool withException = false) diff --git a/src/Umbraco.Core/Persistence/LocalDb.cs b/src/Umbraco.Core/Persistence/LocalDb.cs index 55d6565344..0e1da4c0d1 100644 --- a/src/Umbraco.Core/Persistence/LocalDb.cs +++ b/src/Umbraco.Core/Persistence/LocalDb.cs @@ -901,7 +901,7 @@ namespace Umbraco.Core.Persistence return -1; } - var p = new Process + using (var p = new Process { StartInfo = { @@ -913,13 +913,16 @@ namespace Umbraco.Core.Persistence CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden } - }; - p.Start(); - output = p.StandardOutput.ReadToEnd(); - error = p.StandardError.ReadToEnd(); - p.WaitForExit(); + }) + { + p.Start(); + output = p.StandardOutput.ReadToEnd(); + error = p.StandardError.ReadToEnd(); + p.WaitForExit(); - return p.ExitCode; + return p.ExitCode; + } + } /// diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 75ccf5e4f9..1cfca49160 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -535,10 +535,18 @@ namespace Umbraco.Core.Sync /// protected static readonly string LocalIdentity = NetworkHelper.MachineName // eg DOMAIN\SERVER + "/" + HttpRuntime.AppDomainAppId // eg /LM/S3SVC/11/ROOT - + " [P" + Process.GetCurrentProcess().Id // eg 1234 + + " [P" + GetProcessId() // eg 1234 + "/D" + AppDomain.CurrentDomain.Id // eg 22 + "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique + private static int GetProcessId() + { + using(var p = Process.GetCurrentProcess()) + { + return p.Id; + } + } + private string GetDistCacheFilePath(IGlobalSettings globalSettings) { var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt"; From 8b685a0a2c626c422f67616b73ea8d0e55a5d06f Mon Sep 17 00:00:00 2001 From: Chad Currie Date: Sun, 22 Aug 2021 15:40:44 +1200 Subject: [PATCH 15/34] Update Serilog.Enrichers.Process to 2.0.2 as it fixes a leak (cherry picked from commit dcc533fd8dba44bb4cd7feebd609bed40fe7d271) --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index c94def143b..a55eff33a9 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -33,7 +33,7 @@ - + diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 146197c4f8..7a65d75578 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -97,7 +97,7 @@ 2.10.0 - 2.0.1 + 2.0.2 3.1.0 From 14a94f0ba59c7c8131bfc5c3e9eb00b8bc0c4c3e Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 24 Aug 2021 06:41:57 +1000 Subject: [PATCH 16/34] Merge pull request #10847 from umbraco/v8/bugfix/backport-fix-for-10774-into-v8 Backport fix for #10774 to v8 (cherry picked from commit 88f17b7cbfdc9073ef20d15ca503ec76a2ebf6b0) --- .../NuCache/PublishedSnapshotService.cs | 69 ++----------------- 1 file changed, 5 insertions(+), 64 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index f3373dab6c..7678563a53 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -347,28 +347,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return path; } - private void DeleteLocalFilesForContent() - { - if (_isReady && _localContentDb != null) - throw new InvalidOperationException("Cannot delete local files while the cache uses them."); - - var path = GetLocalFilesPath(); - var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); - if (File.Exists(localContentDbPath)) - File.Delete(localContentDbPath); - } - - private void DeleteLocalFilesForMedia() - { - if (_isReady && _localMediaDb != null) - throw new InvalidOperationException("Cannot delete local files while the cache uses them."); - - var path = GetLocalFilesPath(); - var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - if (File.Exists(localMediaDbPath)) - File.Delete(localMediaDbPath); - } - #endregion #region Environment @@ -660,38 +638,13 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Handle Notifications - // note: if the service is not ready, ie _isReady is false, then notifications are ignored - - // SetUmbracoVersionStep issues a DistributedCache.Instance.RefreshAll...() call which should cause - // the entire content, media etc caches to reload from database -- and then the app restarts -- however, - // at the time SetUmbracoVersionStep runs, Umbraco is not fully initialized and therefore some property - // value converters, etc are not registered, and rebuilding the NuCache may not work properly. - // - // More details: ApplicationContext.IsConfigured being false, ApplicationEventHandler.ExecuteWhen... is - // called and in most cases events are skipped, so property value converters are not registered or - // removed, so PublishedPropertyType either initializes with the wrong converter, or throws because it - // detects more than one converter for a property type. - // - // It's not an issue for XmlStore - the app restart takes place *after* the install has refreshed the - // cache, and XmlStore just writes a new umbraco.config file upon RefreshAll, so that's OK. - // - // But for NuCache... we cannot rebuild the cache now. So it will NOT work and we are not fixing it, - // because now we should ALWAYS run with the database server messenger, and then the RefreshAll will - // be processed as soon as we are configured and the messenger processes instructions. - // note: notifications for content type and data type changes should be invoked with the // pure live model factory, if any, locked and refreshed - see ContentTypeCacheRefresher and // DataTypeCacheRefresher public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) { - // no cache, trash everything - if (_isReady == false) - { - DeleteLocalFilesForContent(); - draftChanged = publishedChanged = true; - return; - } + EnsureCaches(); using (_contentStore.GetScopedWriteLock(_scopeProvider)) { @@ -785,13 +738,7 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) { - // no cache, trash everything - if (_isReady == false) - { - DeleteLocalFilesForMedia(); - anythingChanged = true; - return; - } + EnsureCaches(); using (_mediaStore.GetScopedWriteLock(_scopeProvider)) { @@ -878,9 +825,7 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) { - // no cache, nothing we can do - if (_isReady == false) - return; + EnsureCaches(); foreach (var payload in payloads) _logger.Debug("Notified {ChangeTypes} for {ItemType} {ItemId}", payload.ChangeTypes, payload.ItemType, payload.Id); @@ -960,9 +905,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) { - // no cache, nothing we can do - if (_isReady == false) - return; + EnsureCaches(); var idsA = payloads.Select(x => x.Id).ToArray(); @@ -1000,9 +943,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(DomainCacheRefresher.JsonPayload[] payloads) { - // no cache, nothing we can do - if (_isReady == false) - return; + EnsureCaches(); // see note in LockAndLoadContent using (_domainStore.GetScopedWriteLock(_scopeProvider)) From 6c21cb30d3bf8f1563dc5a382242017b19b13de6 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Tue, 24 Aug 2021 10:38:38 +0200 Subject: [PATCH 17/34] Add locks for the dictionary. --- src/Umbraco.Core/Cache/ValueEditorCache.cs | 49 ++++++++++++++-------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index 4ee41f9e55..44a813f129 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -11,30 +11,40 @@ namespace Umbraco.Cms.Core.Cache INotificationHandler, INotificationHandler { - private readonly Dictionary> _valueEditorCache = new(); + private readonly Dictionary> _valueEditorCache; + private readonly object _dictionaryLocker; + + public ValueEditorCache() + { + _valueEditorCache = new Dictionary>(); + _dictionaryLocker = new object(); + } public IDataValueEditor GetValueEditor(IDataEditor editor, IDataType dataType) { - // We try and get the dictionary based on the IDataEditor alias, - // this is here just in case a data type can have more than one value data editor. - // If this is not the case this could be simplified quite a bit, by just using the inner dictionary only. - IDataValueEditor valueEditor; - if (_valueEditorCache.TryGetValue(editor.Alias, out Dictionary dataEditorCache)) + // Lock just in case multiple threads uses the cache at the same time. + lock (_dictionaryLocker) { - if (dataEditorCache.TryGetValue(dataType.Id, out valueEditor)) + // We try and get the dictionary based on the IDataEditor alias, + // this is here just in case a data type can have more than one value data editor. + // If this is not the case this could be simplified quite a bit, by just using the inner dictionary only. + IDataValueEditor valueEditor; + if (_valueEditorCache.TryGetValue(editor.Alias, out Dictionary dataEditorCache)) { + if (dataEditorCache.TryGetValue(dataType.Id, out valueEditor)) + { + return valueEditor; + } + + valueEditor = editor.GetValueEditor(dataType.Configuration); + dataEditorCache[dataType.Id] = valueEditor; return valueEditor; } valueEditor = editor.GetValueEditor(dataType.Configuration); - dataEditorCache[dataType.Id] = valueEditor; + _valueEditorCache[editor.Alias] = new Dictionary { [dataType.Id] = valueEditor }; return valueEditor; - } - - valueEditor = editor.GetValueEditor(dataType.Configuration); - _valueEditorCache[editor.Alias] = new Dictionary { [dataType.Id] = valueEditor }; - return valueEditor; } public void Handle(DataTypeSavedNotification notification) => @@ -45,13 +55,16 @@ namespace Umbraco.Cms.Core.Cache private void ClearCache(IEnumerable dataTypeIds) { - // If a datatype is saved or deleted we have to clear any value editors based on their ID from the cache, - // since it could mean that their configuration has changed. - foreach (var id in dataTypeIds) + lock (_dictionaryLocker) { - foreach (Dictionary editors in _valueEditorCache.Values) + // If a datatype is saved or deleted we have to clear any value editors based on their ID from the cache, + // since it could mean that their configuration has changed. + foreach (var id in dataTypeIds) { - editors.Remove(id); + foreach (Dictionary editors in _valueEditorCache.Values) + { + editors.Remove(id); + } } } } From 65b69dfa4af1417510e3f34eb84a28a5cf888ce2 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Tue, 24 Aug 2021 11:02:37 +0200 Subject: [PATCH 18/34] Create separate refresher for ValueEditorCache Notification handlers are scoped. --- src/Umbraco.Core/Cache/IValueEditorCache.cs | 4 +++- src/Umbraco.Core/Cache/ValueEditorCache.cs | 15 ++----------- .../Cache/ValueEditorCacheRefresher.cs | 21 +++++++++++++++++++ .../DependencyInjection/UmbracoBuilder.cs | 4 ++-- 4 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs diff --git a/src/Umbraco.Core/Cache/IValueEditorCache.cs b/src/Umbraco.Core/Cache/IValueEditorCache.cs index 72b85746c4..f283d730b5 100644 --- a/src/Umbraco.Core/Cache/IValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/IValueEditorCache.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Models; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Cache @@ -6,5 +7,6 @@ namespace Umbraco.Cms.Core.Cache public interface IValueEditorCache { public IDataValueEditor GetValueEditor(IDataEditor dataEditor, IDataType dataType); + public void ClearCache(IEnumerable dataTypeIds); } } diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index 44a813f129..44aa83d44d 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -1,15 +1,10 @@ using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Cache { - public class ValueEditorCache : IValueEditorCache, - INotificationHandler, - INotificationHandler + public class ValueEditorCache : IValueEditorCache { private readonly Dictionary> _valueEditorCache; private readonly object _dictionaryLocker; @@ -47,13 +42,7 @@ namespace Umbraco.Cms.Core.Cache } } - public void Handle(DataTypeSavedNotification notification) => - ClearCache(notification.SavedEntities.Select(x => x.Id)); - - public void Handle(DataTypeDeletedNotification notification) => - ClearCache(notification.DeletedEntities.Select(x => x.Id)); - - private void ClearCache(IEnumerable dataTypeIds) + public void ClearCache(IEnumerable dataTypeIds) { lock (_dictionaryLocker) { diff --git a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs new file mode 100644 index 0000000000..04330a1973 --- /dev/null +++ b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs @@ -0,0 +1,21 @@ +using System.Linq; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Cms.Core.Cache +{ + public class ValueEditorCacheRefresher : + INotificationHandler, + INotificationHandler + { + private readonly IValueEditorCache _valueEditorCache; + + public ValueEditorCacheRefresher(IValueEditorCache valueEditorCache) => _valueEditorCache = valueEditorCache; + + public void Handle(DataTypeSavedNotification notification) => + _valueEditorCache.ClearCache(notification.SavedEntities.Select(x => x.Id)); + + public void Handle(DataTypeDeletedNotification notification) => + _valueEditorCache.ClearCache(notification.DeletedEntities.Select(x => x.Id)); + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 35239121c0..3212787fdb 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -257,8 +257,8 @@ namespace Umbraco.Cms.Core.DependencyInjection // Register ValueEditorCache used for validation Services.AddSingleton(); Services - .AddNotificationHandler() - .AddNotificationHandler(); + .AddNotificationHandler() + .AddNotificationHandler(); } } } From 1c4893d3c8d0504f8a9dd43cd7970bc0effffe27 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Tue, 24 Aug 2021 11:02:51 +0200 Subject: [PATCH 19/34] Fix Tests --- .../Services/ContentServiceTests.cs | 9 +++++++-- .../Services/PropertyValidationServiceTests.cs | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index 60441a013d..75b2e62bf9 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -9,6 +9,7 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -72,13 +73,17 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services private IJsonSerializer Serializer => GetRequiredService(); + private IValueEditorCache ValueEditorCache => GetRequiredService(); + [SetUp] public void Setup() => ContentRepositoryBase.ThrowOnWarning = true; protected override void CustomTestSetup(IUmbracoBuilder builder) => builder .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler(); + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); [TearDown] public void Teardown() => ContentRepositoryBase.ThrowOnWarning = false; @@ -1162,7 +1167,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.IsFalse(content.HasIdentity); // content cannot publish values because they are invalid - var propertyValidationService = new PropertyValidationService(PropertyEditorCollection, DataTypeService, TextService); + var propertyValidationService = new PropertyValidationService(PropertyEditorCollection, DataTypeService, TextService, ValueEditorCache); bool isValid = propertyValidationService.IsPropertyDataValid(content, out IProperty[] invalidProperties, CultureImpact.Invariant); Assert.IsFalse(isValid); Assert.IsNotEmpty(invalidProperties); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs index bfbe6258cc..942ba8cf4a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs @@ -5,6 +5,7 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -45,7 +46,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor })); - validationService = new PropertyValidationService(propEditors, dataTypeService.Object, Mock.Of()); + validationService = new PropertyValidationService(propEditors, dataTypeService.Object, Mock.Of(), new ValueEditorCache()); } [Test] From 90bc927d669860fad24f2c0e53b5c3c99468e1b4 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Tue, 24 Aug 2021 14:15:18 +0200 Subject: [PATCH 20/34] Add unit tests --- .../Cache/ValueEditorCacheTests.cs | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ValueEditorCacheTests.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ValueEditorCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ValueEditorCacheTests.cs new file mode 100644 index 0000000000..94cd9924f9 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ValueEditorCacheTests.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache +{ + [TestFixture] + public class ValueEditorCacheTests + { + + [Test] + public void Caches_ValueEditor() + { + var sut = new ValueEditorCache(); + + var dataEditor = new FakeDataEditor("TestEditor"); + IDataType dataType = CreateDataTypeMock(1).Object; + + // Request the same value editor twice + IDataValueEditor firstEditor = sut.GetValueEditor(dataEditor, dataType); + IDataValueEditor secondEditor = sut.GetValueEditor(dataEditor, dataType); + + Assert.Multiple(() => + { + Assert.AreSame(firstEditor, secondEditor); + Assert.AreEqual(1, dataEditor.ValueEditorCount, "GetValueEditor invoked more than once."); + }); + } + + [Test] + public void Different_Data_Editors_Returns_Different_Value_Editors() + { + var sut = new ValueEditorCache(); + + var dataEditor1 = new FakeDataEditor("Editor1"); + var dataEditor2 = new FakeDataEditor("Editor2"); + IDataType dataType = CreateDataTypeMock(1).Object; + + IDataValueEditor firstEditor = sut.GetValueEditor(dataEditor1, dataType); + IDataValueEditor secondEditor = sut.GetValueEditor(dataEditor2, dataType); + + Assert.AreNotSame(firstEditor, secondEditor); + } + + [Test] + public void Different_Data_Types_Returns_Different_Value_Editors() + { + var sut = new ValueEditorCache(); + var dataEditor = new FakeDataEditor("Editor"); + IDataType dataType1 = CreateDataTypeMock(1).Object; + IDataType dataType2 = CreateDataTypeMock(2).Object; + + IDataValueEditor firstEditor = sut.GetValueEditor(dataEditor, dataType1); + IDataValueEditor secondEditor = sut.GetValueEditor(dataEditor, dataType2); + + Assert.AreNotSame(firstEditor, secondEditor); + } + + [Test] + public void Clear_Cache_Removes_Specific_Editors() + { + var sut = new ValueEditorCache(); + + var dataEditor1 = new FakeDataEditor("Editor 1"); + var dataEditor2 = new FakeDataEditor("Editor 2"); + + IDataType dataType1 = CreateDataTypeMock(1).Object; + IDataType dataType2 = CreateDataTypeMock(2).Object; + + // Load the editors into cache + IDataValueEditor editor1DataType1 = sut.GetValueEditor(dataEditor1, dataType1); + IDataValueEditor editor1Datatype2 = sut.GetValueEditor(dataEditor1, dataType2); + IDataValueEditor editor2DataType1 = sut.GetValueEditor(dataEditor2, dataType1); + IDataValueEditor editor2Datatype2 = sut.GetValueEditor(dataEditor2, dataType2); + + sut.ClearCache(new []{dataType1.Id}); + + // New value editor objects should be created after it's cleared + Assert.AreNotSame(editor1DataType1, sut.GetValueEditor(dataEditor1, dataType1), "Value editor was not cleared from cache"); + Assert.AreNotSame(editor2DataType1, sut.GetValueEditor(dataEditor2, dataType1), "Value editor was not cleared from cache"); + + // But the value editors for data type 2 should be the same + Assert.AreSame(editor1Datatype2, sut.GetValueEditor(dataEditor1, dataType2), "Too many editors was cleared from cache"); + Assert.AreSame(editor2Datatype2, sut.GetValueEditor(dataEditor2, dataType2), "Too many editors was cleared from cache"); + } + + + private Mock CreateDataTypeMock(int id) + { + var mock = new Mock(); + mock.Setup(x => x.Id).Returns(id); + return mock; + } + + /// + /// A fake IDataEditor + /// + /// + /// This is necessary to ensure that different objects are returned from GetValueEditor + /// + private class FakeDataEditor : IDataEditor + { + public FakeDataEditor(string alias) + { + Alias = alias; + } + + public string Alias { get; } + public EditorType Type { get; } + public string Name { get; } + public string Icon { get; } + public string Group { get; } + public bool IsDeprecated { get; } + + public IDataValueEditor GetValueEditor() + { + ValueEditorCount++; + return Mock.Of(); + } + public IDataValueEditor GetValueEditor(object configuration) => GetValueEditor(); + + public IDictionary DefaultConfiguration { get; } + public IConfigurationEditor GetConfigurationEditor() => throw new System.NotImplementedException(); + + public IPropertyIndexValueFactory PropertyIndexValueFactory { get; } + + public int ValueEditorCount; + } + } +} From e558d11fa9758041b2ad9ffbe28722c57c21bd57 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 24 Aug 2021 14:42:08 +0100 Subject: [PATCH 21/34] Revert "Optimization by returning the same instance" This reverts commit 990fc118cbd6df423be2107a75970a834ade1f76. When using the _reusableEditor field to create a BlockEditorPropertyEditor it doesn't matter that content type cache has been invalidated (memory cache) as the content types are also cached in a private field in the BlockEditorValues class. Closes #10910 (cherry picked from commit 94b0b252ece607c9ea6fff4e417635623ee1803e) --- src/Umbraco.Core/PropertyEditors/DataEditor.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index d927d10052..add523ecf6 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -19,7 +19,6 @@ namespace Umbraco.Core.PropertyEditors public class DataEditor : IDataEditor { private IDictionary _defaultConfiguration; - private IDataValueEditor _reusableEditor; /// /// Initializes a new instance of the class. @@ -91,8 +90,7 @@ namespace Umbraco.Core.PropertyEditors /// simple enough for now. /// // TODO: point of that one? shouldn't we always configure? - public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_reusableEditor ?? (_reusableEditor = CreateValueEditor())); - + public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor(); /// /// From fe7067e056cec213ab4b637886da437da8ca6b2d Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 24 Aug 2021 16:44:39 +0100 Subject: [PATCH 22/34] New project - simplify includes/excludes --- .../UmbracoProject/UmbracoProject.csproj | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj index 09e95b43b0..24b0e93831 100644 --- a/build/templates/UmbracoProject/UmbracoProject.csproj +++ b/build/templates/UmbracoProject/UmbracoProject.csproj @@ -1,7 +1,10 @@  + net5.0 Umbraco.Cms.Web.UI + $(DefaultItemExcludes);App_Plugins/**; + $(DefaultItemExcludes);umbraco/**; @@ -10,42 +13,18 @@ - + + - + - - - - - - - - - - - - - - - - - true - Always - - - true - Always - - - true - Always - - - - + + + + + @@ -53,4 +32,5 @@ false false + From 64f4d0ec93abd103e239725c124a017f326738c0 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 24 Aug 2021 16:58:05 +0100 Subject: [PATCH 23/34] also ignore wwwroot/media --- build/templates/UmbracoProject/UmbracoProject.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj index 24b0e93831..75db2dba9d 100644 --- a/build/templates/UmbracoProject/UmbracoProject.csproj +++ b/build/templates/UmbracoProject/UmbracoProject.csproj @@ -5,6 +5,7 @@ Umbraco.Cms.Web.UI $(DefaultItemExcludes);App_Plugins/**; $(DefaultItemExcludes);umbraco/**; + $(DefaultItemExcludes);wwwroot/media/**; From 9e73cfe0ec8942be9e8f97c4e6856f8c88c218d9 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Tue, 24 Aug 2021 17:44:24 +0200 Subject: [PATCH 24/34] Added url mode parameter to the GetCropUrl extension methods --- .../FriendlyImageCropperTemplateExtensions.cs | 33 ++++++++++++------- .../ImageCropperTemplateCoreExtensions.cs | 32 ++++++++++++------ .../Extensions/UrlHelperExtensions.cs | 14 ++++---- 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs index 38f6f47235..6bfc9469cf 100644 --- a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs @@ -22,16 +22,18 @@ namespace Umbraco.Extensions /// /// The IPublishedContent item. /// The crop alias e.g. thumbnail. + /// The url mode. /// /// The URL of the cropped image. /// public static string GetCropUrl( this IPublishedContent mediaItem, - string cropAlias) => - mediaItem.GetCropUrl(cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider); + string cropAlias, + UrlMode urlMode = UrlMode.Default) => + mediaItem.GetCropUrl(cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); - public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string cropAlias) - => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider); + public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string cropAlias, UrlMode urlMode = UrlMode.Default) + => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); /// /// Gets the crop URL by using only the specified . @@ -39,14 +41,16 @@ namespace Umbraco.Extensions /// The media item. /// The image cropper value. /// The crop alias. + /// The url mode. /// /// The image crop URL. /// public static string GetCropUrl( this IPublishedContent mediaItem, ImageCropperValue imageCropperValue, - string cropAlias) - => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, imageCropperValue, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider); + string cropAlias, + UrlMode urlMode = UrlMode.Default) + => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, imageCropperValue, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); /// /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper JSON data on the IPublishedContent item. @@ -54,17 +58,19 @@ namespace Umbraco.Extensions /// The IPublishedContent item. /// The property alias of the property containing the JSON data e.g. umbracoFile. /// The crop alias e.g. thumbnail. + /// The url mode. /// /// The URL of the cropped image. /// public static string GetCropUrl( this IPublishedContent mediaItem, string propertyAlias, - string cropAlias) => - mediaItem.GetCropUrl(propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider); + string cropAlias, + UrlMode urlMode = UrlMode.Default) => + mediaItem.GetCropUrl(propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); - public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string propertyAlias, string cropAlias) - => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider); + public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string propertyAlias, string cropAlias, UrlMode urlMode = UrlMode.Default) + => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaWithCrops, propertyAlias, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider, urlMode); /// /// Gets the underlying image processing service URL from the IPublishedContent item. @@ -84,6 +90,7 @@ namespace Umbraco.Extensions /// + /// The url mode. /// /// The URL of the cropped image. /// @@ -99,7 +106,8 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null) + string furtherOptions = null, + UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl( ImageUrlGenerator, PublishedValueFallback, @@ -114,7 +122,8 @@ namespace Umbraco.Extensions preferFocalPoint, useCropDimensions, cacheBuster, - furtherOptions + furtherOptions, + urlMode ); /// diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs index ae367b1cf9..a3e96ebebb 100644 --- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs @@ -20,6 +20,7 @@ namespace Umbraco.Extensions /// The image URL generator. /// The published value fallback. /// The published URL provider. + /// The url mode. /// /// The URL of the cropped image. /// @@ -28,14 +29,16 @@ namespace Umbraco.Extensions string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true); + IPublishedUrlProvider publishedUrlProvider, + UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); public static string GetCropUrl( this MediaWithCrops mediaWithCrops, string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true); + IPublishedUrlProvider publishedUrlProvider, + UrlMode urlMode = UrlMode.Default) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); /// /// Gets the crop URL by using only the specified . @@ -46,6 +49,7 @@ namespace Umbraco.Extensions /// The image URL generator. /// The published value fallback. /// The published URL provider. + /// The url mode.s /// /// The image crop URL. /// @@ -55,7 +59,8 @@ namespace Umbraco.Extensions string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true); + IPublishedUrlProvider publishedUrlProvider, + UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); /// /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper JSON data on the IPublishedContent item. @@ -66,6 +71,7 @@ namespace Umbraco.Extensions /// The image URL generator. /// The published value fallback. /// The published URL provider. + /// The url mode. /// /// The URL of the cropped image. /// @@ -75,14 +81,16 @@ namespace Umbraco.Extensions string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); + IPublishedUrlProvider publishedUrlProvider, + UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); public static string GetCropUrl(this MediaWithCrops mediaWithCrops, IPublishedValueFallback publishedValueFallback, IPublishedUrlProvider publishedUrlProvider, string propertyAlias, string cropAlias, - IImageUrlGenerator imageUrlGenerator) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); + IImageUrlGenerator imageUrlGenerator, + UrlMode urlMode = UrlMode.Default) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); /// /// Gets the underlying image processing service URL from the IPublishedContent item. @@ -105,6 +113,7 @@ namespace Umbraco.Extensions /// + /// The url mode. /// /// The URL of the cropped image. /// @@ -123,7 +132,8 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions); + string furtherOptions = null, + UrlMode urlMode = UrlMode.Default) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, urlMode); public static string GetCropUrl( this MediaWithCrops mediaWithCrops, @@ -140,14 +150,15 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null) + string furtherOptions = null, + UrlMode urlMode = UrlMode.Default) { if (mediaWithCrops == null) { throw new ArgumentNullException(nameof(mediaWithCrops)); } - return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions); + return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, urlMode); } private static string GetCropUrl( @@ -167,7 +178,8 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null) + string furtherOptions = null, + UrlMode urlMode = UrlMode.Default) { if (mediaItem == null) { @@ -179,7 +191,7 @@ namespace Umbraco.Extensions return null; } - var mediaItemUrl = mediaItem.MediaUrl(publishedUrlProvider, propertyAlias: propertyAlias); + var mediaItemUrl = mediaItem.MediaUrl(publishedUrlProvider, propertyAlias: propertyAlias, mode: urlMode); // Only get crops from media when required and used if (localCropsOnly == false && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) diff --git a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs index aefd102725..ee45db39c9 100644 --- a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -209,27 +208,27 @@ namespace Umbraco.Extensions return $"{version}.{runtimeMinifier.CacheBuster}".GenerateHash(); } - public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string cropAlias, bool htmlEncode = true) + public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string cropAlias, bool htmlEncode = true, UrlMode urlMode = UrlMode.Default) { if (mediaItem == null) { return HtmlString.Empty; } - var url = mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true); + var url = mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); return CreateHtmlString(url, htmlEncode); } private static IHtmlContent CreateHtmlString(string url, bool htmlEncode) => htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); - public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias, bool htmlEncode = true) + public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias, bool htmlEncode = true, UrlMode urlMode = UrlMode.Default) { if (mediaItem == null) { return HtmlString.Empty; } - var url = mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); + var url = mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true, urlMode: urlMode); return CreateHtmlString(url, htmlEncode); } @@ -246,7 +245,8 @@ namespace Umbraco.Extensions bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - bool htmlEncode = true) + bool htmlEncode = true, + UrlMode urlMode = UrlMode.Default) { if (mediaItem == null) { @@ -254,7 +254,7 @@ namespace Umbraco.Extensions } var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, urlMode); return CreateHtmlString(url, htmlEncode); } From 1e0d82ceeea8638a781098caa58b3a391b3f59c3 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 24 Aug 2021 20:35:14 +0100 Subject: [PATCH 25/34] More consistent globs --- build/templates/UmbracoProject/UmbracoProject.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj index 75db2dba9d..d1f40a2740 100644 --- a/build/templates/UmbracoProject/UmbracoProject.csproj +++ b/build/templates/UmbracoProject/UmbracoProject.csproj @@ -21,8 +21,8 @@ - - + + From 392eec237182c4cc669d23b797d7a4762d49693e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 25 Aug 2021 11:56:20 +0200 Subject: [PATCH 26/34] Fixed potential null reference exception in KeepAlive --- src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 40c250f86e..644b2c278f 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -33,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// /// Initializes a new instance of the class. /// - /// Accessor for the current request. + /// The current hosting environment /// Representation of the main application domain. /// The configuration for keep alive settings. /// The typed logger. @@ -86,7 +86,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices using (_profilingLogger.DebugDuration("Keep alive executing", "Keep alive complete")) { - var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl.ToString(); + var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl?.ToString(); if (umbracoAppUrl.IsNullOrWhiteSpace()) { _logger.LogWarning("No umbracoApplicationUrl for service (yet), skip."); From 622a03fa664eb77d06975c6a53e0896d880df716 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 27 Aug 2021 11:05:47 +0200 Subject: [PATCH 27/34] Added danish translations for treeSearch --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 1cfe21e0d1..3b3deb6c07 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -2049,4 +2049,8 @@ Mange hilsner fra Umbraco robotten Filskrivning Medie mappeoprettelse + + result + resultater + From aeaf935df80726da502c65796edaa69fa2fadfcc Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 27 Aug 2021 12:59:15 +0200 Subject: [PATCH 28/34] Update src/Umbraco.Web.UI/umbraco/config/lang/da.xml Co-authored-by: Mole --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 3b3deb6c07..80edf2bbd0 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -2050,7 +2050,7 @@ Mange hilsner fra Umbraco robotten Medie mappeoprettelse - result + resultat resultater From a73c7bb8a1f52402dbe0653c9befd9a1ea881faf Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 30 Aug 2021 09:25:04 +0200 Subject: [PATCH 29/34] Devcontainer: Update SMTP FROM address (#10970) --- .devcontainer/docker-compose.yml | 8 ++++---- .gitattributes | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 17312c6bd8..e88327b779 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: app: - build: + build: context: . dockerfile: Dockerfile args: @@ -18,7 +18,7 @@ services: volumes: - ..:/workspace:cached - + # Overrides default command so things don't shut down after the process ends. command: sleep infinity @@ -28,7 +28,7 @@ services: # Uncomment the next line to use a non-root user for all processes. # user: vscode - # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. # (Adding the "ports" property to this file will not forward from a Codespace.) # DotNetCore ENV Variables @@ -41,7 +41,7 @@ services: - Umbraco__CMS__Unattended__UnattendedUserPassword=password1234 - Umbraco__CMS__Global__Smtp__Host=smtp4dev - Umbraco__CMS__Global__Smtp__Port=25 - - Umbraco__CMS__Global__Smtp__From=warren-env@umbraco.com + - Umbraco__CMS__Global__Smtp__From=noreply@umbraco.test db: image: mcr.microsoft.com/mssql/server:2019-latest diff --git a/.gitattributes b/.gitattributes index 3241b6511c..8ac58cf5fe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ +* text=auto *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain @@ -44,6 +45,7 @@ *.json text=auto *.xml text=auto *.resx text=auto +*.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 *.csproj text=auto merge=union *.vbproj text=auto merge=union From d448ddd7dfb1b1ab2ae1829a32a0d0fdc03ee8d9 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 30 Aug 2021 10:45:47 +0200 Subject: [PATCH 30/34] Make ValueEditorCacheRefresher a distributed cache refresher --- .../Cache/ValueEditorCacheRefresher.cs | 54 +++++++++++++++---- .../DependencyInjection/UmbracoBuilder.cs | 3 -- .../Cache/DistributedCacheBinder_Handlers.cs | 2 + .../Cache/DistributedCacheExtensions.cs | 16 ++++++ 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs index 04330a1973..c815ca7a71 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs @@ -1,21 +1,57 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Serialization; namespace Umbraco.Cms.Core.Cache { - public class ValueEditorCacheRefresher : - INotificationHandler, - INotificationHandler + public sealed class ValueEditorCacheRefresher : PayloadCacheRefresherBase { private readonly IValueEditorCache _valueEditorCache; - public ValueEditorCacheRefresher(IValueEditorCache valueEditorCache) => _valueEditorCache = valueEditorCache; + public ValueEditorCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory, + IValueEditorCache valueEditorCache) : base(appCaches, serializer, eventAggregator, factory) + { + _valueEditorCache = valueEditorCache; + } - public void Handle(DataTypeSavedNotification notification) => - _valueEditorCache.ClearCache(notification.SavedEntities.Select(x => x.Id)); + public static readonly Guid UniqueId = Guid.Parse("D28A1DBB-2308-4918-9A92-2F8689B6CBFE"); + public override Guid RefresherUniqueId => UniqueId; + public override string Name => "ValueEditorCacheRefresher"; - public void Handle(DataTypeDeletedNotification notification) => - _valueEditorCache.ClearCache(notification.DeletedEntities.Select(x => x.Id)); + public override void Refresh(DataTypeCacheRefresher.JsonPayload[] payloads) + { + IEnumerable ids = payloads.Select(x => x.Id); + _valueEditorCache.ClearCache(ids); + } + + // these events should never trigger + // everything should be PAYLOAD/JSON + + public override void RefreshAll() + { + throw new NotSupportedException(); + } + + public override void Refresh(int id) + { + throw new NotSupportedException(); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); + } + + public override void Remove(int id) + { + throw new NotSupportedException(); + } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 3212787fdb..6f6a53df66 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -256,9 +256,6 @@ namespace Umbraco.Cms.Core.DependencyInjection // Register ValueEditorCache used for validation Services.AddSingleton(); - Services - .AddNotificationHandler() - .AddNotificationHandler(); } } } diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 35845f3cd0..6e6f549b03 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs @@ -130,6 +130,7 @@ namespace Umbraco.Cms.Core.Cache { _distributedCache.RefreshDataTypeCache(entity); } + _distributedCache.RefreshValueEditorCache(notification.SavedEntities); } public void Handle(DataTypeDeletedNotification notification) @@ -138,6 +139,7 @@ namespace Umbraco.Cms.Core.Cache { _distributedCache.RemoveDataTypeCache(entity); } + _distributedCache.RefreshValueEditorCache(notification.DeletedEntities); } #endregion diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs index a0ba0ff128..ceac767a8c 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; @@ -106,6 +107,21 @@ namespace Umbraco.Extensions #endregion + #region ValueEditorCache + + public static void RefreshValueEditorCache(this DistributedCache dc, IEnumerable dataTypes) + { + if (dataTypes is null) + { + return; + } + + var payloads = dataTypes.Select(x => new DataTypeCacheRefresher.JsonPayload(x.Id, x.Key, false)); + dc.RefreshByPayload(ValueEditorCacheRefresher.UniqueId, payloads); + } + + #endregion + #region ContentCache public static void RefreshAllContentCache(this DistributedCache dc) From 3d2dae075d2bd61ee73064caac6fb097f6f422de Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 30 Aug 2021 10:57:32 +0200 Subject: [PATCH 31/34] Remove notification registration for ValueEditorCacheRefresher --- .../Umbraco.Infrastructure/Services/ContentServiceTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index 75b2e62bf9..d10e269fad 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -81,9 +81,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services protected override void CustomTestSetup(IUmbracoBuilder builder) => builder .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler(); + .AddNotificationHandler(); [TearDown] public void Teardown() => ContentRepositoryBase.ThrowOnWarning = false; From a2b9146abf163b5c52a352cb51b0281c1ac1453c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard Date: Mon, 30 Aug 2021 12:44:42 +0200 Subject: [PATCH 32/34] Generate a local certificate for Kestrel https Fixes #10981 --- .devcontainer/Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c62ad1c494..0f5a12b34b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -42,4 +42,8 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # Needing to set unsafe-perm as root is the user setup # https://docs.npmjs.com/cli/v6/using-npm/config#unsafe-perm # Default: false if running as root, true otherwise (we are ROOT) -RUN npm -g config set user vscode && npm -g config set unsafe-perm \ No newline at end of file +RUN npm -g config set user vscode && npm -g config set unsafe-perm + +# Generate and trust a local developer certificate for Kestrel +# This is needed for Kestrel to bind on https +RUN dotnet dev-certs https --trust From e62dd04295cfc794d5f898b0272c6dc378367348 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 30 Aug 2021 17:18:42 +0000 Subject: [PATCH 33/34] Fixes #10956 - GetLocalCropUrl Obsolete message is misleading --- .../Extensions/FriendlyImageCropperTemplateExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs index 38f6f47235..eb34ab5867 100644 --- a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs @@ -215,7 +215,7 @@ namespace Umbraco.Extensions ); - [Obsolete("Use GetCrop to merge local and media crops, get automatic cache buster value and have more parameters.")] + [Obsolete("Use GetCropUrl to merge local and media crops, get automatic cache buster value and have more parameters.")] public static string GetLocalCropUrl( this MediaWithCrops mediaWithCrops, string alias, From 56f8e1c12b4bfee1cbaa4a48f99338eb3592f30c Mon Sep 17 00:00:00 2001 From: Zeegaan <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:26:47 +0200 Subject: [PATCH 34/34] Deleted obsolete Systemfiles class --- src/Umbraco.Core/IO/SystemFiles.cs | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 src/Umbraco.Core/IO/SystemFiles.cs diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs deleted file mode 100644 index 64199a1c51..0000000000 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.IO; -using Umbraco.Cms.Core.Hosting; - -namespace Umbraco.Cms.Core.IO -{ - public class SystemFiles - { - public static string TinyMceConfig => Constants.SystemDirectories.Config + "/tinyMceConfig.config"; - - // TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache - public static string GetContentCacheXml(IHostingEnvironment hostingEnvironment) - { - return Path.Combine(hostingEnvironment.LocalTempPath, "umbraco.config"); - } - } -}