From d580153c9ab9a40abacd23dc4be211395db437d3 Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 15 May 2019 13:51:07 +0200 Subject: [PATCH 01/96] Removing build-docs.ps1 script since it is integrated now in the build.ps1 --- build/build-docs.ps1 | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 build/build-docs.ps1 diff --git a/build/build-docs.ps1 b/build/build-docs.ps1 deleted file mode 100644 index 8cd3f090c7..0000000000 --- a/build/build-docs.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -$uenv=build/build.ps1 -get - -$src = "$($uenv.SolutionRoot)\src" -$tmp = $uenv.BuildTemp -$out = $uenv.BuildOutput -$DocFxJson = "$src\ApiDocs\docfx.json" -$DocFxSiteOutput = "$tmp\_site\*.*" - -################ Do the UI docs -$uenv.CompileBelle() - -"Moving to Umbraco.Web.UI.Client folder" -cd .\src\Umbraco.Web.UI.Client - -"Generating the docs and waiting before executing the next commands" -& gulp docs | Out-Null - -# change baseUrl -$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/" -$IndexPath = "./docs/api/index.html" -(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath - -# zip it -& $uenv.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*" - - -################ Do the c# docs - -# Build the solution in debug mode -$SolutionPath = Join-Path -Path $src -ChildPath "umbraco.sln" -#$uenv.CompileUmbraco() - -#restore nuget packages -$uenv.RestoreNuGet() - -# run DocFx -$DocFx = $uenv.BuildEnv.DocFx - -Write-Host "$DocFxJson" -& $DocFx metadata $DocFxJson -& $DocFx build $DocFxJson - -# zip it -& $uenv.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput From 1a54cdb58364355a1e7746c9810891396101e308 Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 15 May 2019 13:54:35 +0200 Subject: [PATCH 02/96] Functions for the API docs are refactored --- build/build.ps1 | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/build/build.ps1 b/build/build.ps1 index 55b686c98e..978d6b2c26 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -430,19 +430,16 @@ $this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput) }) - $ubuild.DefineMethod("PrepareCSharpDocs", + $ubuild.DefineMethod("BuildCSharpDocs", { - Write-Host "Prepare C# Documentation" + Write-Host "Building C# Documentation" $src = "$($this.SolutionRoot)\src" - $tmp = $this.BuildTemp - $out = $this.BuildOutput + $tmp = $this.BuildTemp + $out = $this.BuildOutput $DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json" $DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*" - - #restore nuget packages - $this.RestoreNuGet() # run DocFx $DocFx = $this.BuildEnv.DocFx @@ -453,24 +450,26 @@ & $this.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput }) - $ubuild.DefineMethod("PrepareAngularDocs", + $ubuild.DefineMethod("BuildAngularDocs", { - Write-Host "Prepare Angular Documentation" + Write-Host "Building Angular Documentation" $src = "$($this.SolutionRoot)\src" - $out = $this.BuildOutput + $out = $this.BuildOutput - $this.CompileBelle() + # Check if the solution has been built + if (!(Test-Path "$src\Umbraco.Web.UI.Client\node_modules")) {throw "Umbraco needs to be built before generating the Angular Docs"} - "Moving to Umbraco.Web.UI.Client folder" - cd .\src\Umbraco.Web.UI.Client + Push-Location "$src\Umbraco.Web.UI.Client" "Generating the docs and waiting before executing the next commands" & gulp docs | Out-Null + Pop-Location + # change baseUrl $BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/" - $IndexPath = "./docs/api/index.html" + $IndexPath = "$src\Umbraco.Web.UI.Client\docs\api\index.html" (Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath # zip it From d153bc3551a5aa0cb45da30ab268658f4efe7f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 18 Nov 2019 11:58:40 +0100 Subject: [PATCH 03/96] Do not copy keys from NestedContent properties, instead we trust they will be created if they dont exists. which they do. --- .../src/common/services/clipboard.service.js | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index c3a1ba6432..0184f2a1e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -53,11 +53,39 @@ function clipboardService(notificationsService, eventsService, localStorageServi return false; } + function removeKeys(values) { + for (var i = 0; i < values.length; i++) { + var obj = values[i]; + + delete obj.key; + delete obj.$$hashKey; + + // Loop through all properties: + for (var k in obj) { + // if this property is an array, we need to check if there's more keys to remove. + if (Array.isArray(obj[k])) { + removeKeys(obj[k]) + } + } + } + } + var prepareEntryForStorage = function(entryData) { var shallowCloneData = Object.assign({}, entryData);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data) delete shallowCloneData.key; delete shallowCloneData.$$hashKey; + + // remove keys from sub-entries + for (var t = 0; t < shallowCloneData.variants[0].tabs.length; t++) { + var tab = shallowCloneData.variants[0].tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + if (prop.view === "nestedcontent") { + removeKeys(prop.value); + } + } + } return shallowCloneData; } @@ -84,15 +112,19 @@ function clipboardService(notificationsService, eventsService, localStorageServi * @param {string} alias A string defining the alias of the data to store, example: 'product' * @param {object} entry A object containing the properties to be saved, this could be the object of a ElementType, ContentNode, ... * @param {string} displayLabel (optional) A string swetting the label to display when showing paste entries. + * @param {string} displayIcon (optional) A string setting the icon to display when showing paste entries. + * @param {string} uniqueKey (optional) A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data. * * @description * Saves a single JS-object with a type and alias to the clipboard. */ - service.copy = function(type, alias, data, displayLabel) { + service.copy = function(type, alias, data, displayLabel, displayIcon, uniqueKey) { var storage = retriveStorage(); - var uniqueKey = data.key || data.$$hashKey || console.error("missing unique key for this content"); + displayLabel = displayLabel || data.name; + displayIcon = displayIcon || iconHelper.convertFromLegacyIcon(data.icon); + uniqueKey = uniqueKey || data.key || data.$$hashKey || console.error("missing unique key for this content"); // remove previous copies of this entry: storage.entries = storage.entries.filter( @@ -101,7 +133,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi } ); - var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel || data.name, icon:iconHelper.convertFromLegacyIcon(data.icon)}; + var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel, icon:displayIcon}; storage.entries.push(entry); if (saveStorage(storage) === true) { From 882ba8d270763a23ae2253b76f8ce4b626a4a43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 18 Nov 2019 13:21:18 +0100 Subject: [PATCH 04/96] only nested content properties should be handled, for now... --- .../src/common/services/clipboard.service.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index 0184f2a1e7..ec2a754420 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -56,15 +56,16 @@ function clipboardService(notificationsService, eventsService, localStorageServi function removeKeys(values) { for (var i = 0; i < values.length; i++) { var obj = values[i]; - - delete obj.key; - delete obj.$$hashKey; - // Loop through all properties: - for (var k in obj) { - // if this property is an array, we need to check if there's more keys to remove. - if (Array.isArray(obj[k])) { - removeKeys(obj[k]) + // Entires with this property are entries of a nested content property. And those keys we can remove. + if (obj.ncContentTypeAlias) { + delete obj.key; + // Loop through all properties: + for (var k in obj) { + // if this property is an array, we need to check if there's more keys to remove. + if (Array.isArray(obj[k])) { + removeKeys(obj[k]) + } } } } From eb04d55d2b7b1cfd68d2a205033d3951454652cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 16 Mar 2020 20:00:39 +0100 Subject: [PATCH 05/96] Makes sure supplied arguments cannot be used more than once. --- src/Umbraco.Core/FactoryExtensions.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/FactoryExtensions.cs b/src/Umbraco.Core/FactoryExtensions.cs index 8514525417..8ae2f76af3 100644 --- a/src/Umbraco.Core/FactoryExtensions.cs +++ b/src/Umbraco.Core/FactoryExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Umbraco.Core.Composing; @@ -77,15 +78,28 @@ namespace Umbraco.Core var ctorParameters = ctor.GetParameters(); var ctorArgs = new object[ctorParameters.Length]; + var availableArgs = new List(args); var i = 0; foreach (var parameter in ctorParameters) { // no! IsInstanceOfType is not ok here // ReSharper disable once UseMethodIsInstanceOfType - var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); - ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType); + var idx = availableArgs.FindIndex(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); + if(idx >= 0) + { + // Found a suitable supplied argument + ctorArgs[i++] = availableArgs[idx]; + + // A supplied argument can be used at most once + availableArgs.RemoveAt(idx); + } + else + { + // None of the provided arguments is suitable: get an instance from the factory + ctorArgs[i++] = factory.GetInstance(parameter.ParameterType); + } } return ctor.Invoke(ctorArgs); } } -} \ No newline at end of file +} From c3f59602c21d6a07f553066af7e8dd742114fd83 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Wed, 22 Apr 2020 11:33:27 +0100 Subject: [PATCH 06/96] Add GetMainDom method to UmbracoApplication so GetRuntime can be overridden (cherry picked from commit 549b4c58a7b8dec8eae15cb0cbb3bd1e1268ead8) --- src/Umbraco.Web/UmbracoApplication.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index f8ee238da7..b5ecacfd13 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Web; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Runtime; using Umbraco.Web.Runtime; @@ -17,16 +18,24 @@ namespace Umbraco.Web { var logger = SerilogLogger.CreateWithDefaultConfiguration(); + var runtime = new WebRuntime(this, logger, GetMainDom(logger)); + + return runtime; + } + + /// + /// Returns a new MainDom + /// + public static IMainDom GetMainDom(ILogger logger) + { // Determine if we should use the sql main dom or the default var appSettingMainDomLock = ConfigurationManager.AppSettings[Constants.AppSettings.MainDomLock]; var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" ? (IMainDomLock)new SqlMainDomLock(logger) : new MainDomSemaphoreLock(logger); - - var runtime = new WebRuntime(this, logger, new MainDom(logger, mainDomLock)); - return runtime; + return new MainDom(logger, mainDomLock); } /// From bac42c6fb29352efee074e0194fd821870a57a65 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 30 Apr 2020 22:46:06 +1000 Subject: [PATCH 07/96] change to protected --- src/Umbraco.Web/UmbracoApplication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index b5ecacfd13..f5667a5a85 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web /// /// Returns a new MainDom /// - public static IMainDom GetMainDom(ILogger logger) + protected IMainDom GetMainDom(ILogger logger) { // Determine if we should use the sql main dom or the default var appSettingMainDomLock = ConfigurationManager.AppSettings[Constants.AppSettings.MainDomLock]; From 045604d33921ddca37d17007e63a420e2d42c29b Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Sun, 3 May 2020 15:28:56 +0100 Subject: [PATCH 08/96] Move ReflectionTests --- .../Umbraco.Core}/ReflectionTests.cs | 27 +++---------------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 3 insertions(+), 25 deletions(-) rename src/{Umbraco.Tests/Clr => Umbraco.Tests.UnitTests/Umbraco.Core}/ReflectionTests.cs (54%) diff --git a/src/Umbraco.Tests/Clr/ReflectionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionTests.cs similarity index 54% rename from src/Umbraco.Tests/Clr/ReflectionTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionTests.cs index f3704c8552..063a8c2621 100644 --- a/src/Umbraco.Tests/Clr/ReflectionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionTests.cs @@ -2,7 +2,7 @@ using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.Clr +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class ReflectionTests @@ -25,37 +25,16 @@ namespace Umbraco.Tests.Clr Assert.Contains(typeof(object), types); } - [Test] - public void GetInterfacesIsOk() - { - // tests that GetInterfaces gets _all_ interfaces - // so the AllInterfaces extension method is useless - - var type = typeof(Class2); - var interfaces = type.GetInterfaces(); - Assert.AreEqual(2, interfaces.Length); - Assert.Contains(typeof(IInterface1), interfaces); - Assert.Contains(typeof(IInterface2), interfaces); - } - #region Test Objects - interface IInterface1 - { } - - interface IInterface2 : IInterface1 + private class Class1 { - void Method(); } - class Class1 : IInterface2 + private class Class2 : Class1 { - public void Method() { } } - class Class2 : Class1 - { } - #endregion } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a832d84ef1..615463bff1 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -302,7 +302,6 @@ - From 72071a536bfb7e28f2542275389f46a16d80908a Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Sun, 3 May 2020 15:51:46 +0100 Subject: [PATCH 09/96] Move ReflectionUtilitiesTests --- .../Umbraco.Core}/ReflectionUtilitiesTests.cs | 14 ++++---------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) rename src/{Umbraco.Tests/Clr => Umbraco.Tests.UnitTests/Umbraco.Core}/ReflectionUtilitiesTests.cs (98%) diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionUtilitiesTests.cs similarity index 98% rename from src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionUtilitiesTests.cs index c885f364dc..0f48f2cea2 100644 --- a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionUtilitiesTests.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; +using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; -using System.Linq; -using Newtonsoft.Json; -namespace Umbraco.Tests.Clr +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class ReflectionUtilitiesTests @@ -104,8 +104,6 @@ namespace Umbraco.Tests.Clr [Test] public void EmitMethodEmitsStaticStatic() { - // static types cannot be used as type arguments - //var method = ReflectionUtilities.EmitMethod("Method"); var method = ReflectionUtilities.EmitMethod(typeof (StaticClass1), "Method"); method(); } @@ -205,10 +203,6 @@ namespace Umbraco.Tests.Clr (var getter3, var setter3) = ReflectionUtilities.EmitPropertyGetterAndSetter("Value3"); Assert.AreEqual(42, getter3(class1)); setter3(class1, 42); - - // this is not supported yet - //var getter4 = ReflectionUtilities.EmitPropertyGetter("Value1", returned: typeof(int)); - //Assert.AreEqual(42, getter1(class1)); } [Test] @@ -448,7 +442,7 @@ namespace Umbraco.Tests.Clr var propInt4 = type4.GetProperty("IntValue"); Assert.IsNotNull(propInt4); - // ... if explicitely getting a value type + // ... if explicitly getting a value type var getterInt4T = ReflectionUtilities.EmitPropertyGetter(propInt4); Assert.IsNotNull(getterInt4T); var valueInt4T = getterInt4T(object4); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 615463bff1..7284f617f4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -117,7 +117,6 @@ - From 6693ea0ff79d3f283d41740d38288585947f5aab Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Sun, 3 May 2020 15:57:45 +0100 Subject: [PATCH 10/96] Move DeepCloneableListTests --- .../Collections/DeepCloneableListTests.cs | 13 +++++-------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) rename src/{Umbraco.Tests => Umbraco.Tests.UnitTests/Umbraco.Core}/Collections/DeepCloneableListTests.cs (94%) diff --git a/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs similarity index 94% rename from src/Umbraco.Tests/Collections/DeepCloneableListTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs index 37e5b2081d..305a392311 100644 --- a/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using NUnit.Framework; using Umbraco.Core.Collections; using Umbraco.Core.Models; -namespace Umbraco.Tests.Collections +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections { [TestFixture] public class DeepCloneableListTests @@ -85,7 +82,7 @@ namespace Umbraco.Tests.Collections list.Add(new TestClone()); list.Add(new TestClone()); - var cloned = (DeepCloneableList)list.DeepClone(); + var cloned = (DeepCloneableList) list.DeepClone(); //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) Assert.IsTrue(list.SequenceEqual(cloned)); @@ -111,8 +108,8 @@ namespace Umbraco.Tests.Collections Id = Guid.NewGuid(); } - public Guid Id { get; private set; } - public bool IsClone { get; private set; } + public Guid Id { get; } + public bool IsClone { get; } public object DeepClone() { @@ -145,7 +142,7 @@ namespace Umbraco.Tests.Collections if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((TestClone)obj); + return Equals((TestClone) obj); } /// diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7284f617f4..ab6d9e052e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -248,7 +248,6 @@ - From 07d72d55621843c2c95132a0439c982def3dee8d Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Sun, 3 May 2020 16:03:45 +0100 Subject: [PATCH 11/96] Move OrderedHashSetTests --- .../Umbraco.Core}/Collections/OrderedHashSetTests.cs | 9 ++++----- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) rename src/{Umbraco.Tests => Umbraco.Tests.UnitTests/Umbraco.Core}/Collections/OrderedHashSetTests.cs (87%) diff --git a/src/Umbraco.Tests/Collections/OrderedHashSetTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/OrderedHashSetTests.cs similarity index 87% rename from src/Umbraco.Tests/Collections/OrderedHashSetTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/OrderedHashSetTests.cs index a98348751b..df7f004884 100644 --- a/src/Umbraco.Tests/Collections/OrderedHashSetTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/OrderedHashSetTests.cs @@ -1,9 +1,8 @@ using System; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Collections; -namespace Umbraco.Tests.Collections +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections { [TestFixture] public class OrderedHashSetTests @@ -12,7 +11,7 @@ namespace Umbraco.Tests.Collections public void Keeps_Last() { var list = new OrderedHashSet(keepOldest: false); - var items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") }; + var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")}; foreach (var item in items) { list.Add(item); @@ -27,7 +26,7 @@ namespace Umbraco.Tests.Collections public void Keeps_First() { var list = new OrderedHashSet(keepOldest: true); - var items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") }; + var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")}; foreach (var item in items) { list.Add(item); @@ -60,7 +59,7 @@ namespace Umbraco.Tests.Collections if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((MyClass)obj); + return Equals((MyClass) obj); } public override int GetHashCode() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ab6d9e052e..3a32ee17fc 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -117,7 +117,6 @@ - From 0717d5b2467c78e24a5c0cbee7570e6b11e7fe8b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 May 2020 18:57:55 +1000 Subject: [PATCH 12/96] Fixes: SqlMainDom setting for Azure does not work in Load Balancing #8038 --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index f3bfe4eefc..0107ecb8da 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Web; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -16,7 +17,7 @@ namespace Umbraco.Core.Runtime internal class SqlMainDomLock : IMainDomLock { private string _lockId; - private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom"; + private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom"; private const string UpdatedSuffix = "_updated"; private readonly ILogger _logger; private IUmbracoDatabase _db; @@ -126,6 +127,14 @@ namespace Umbraco.Core.Runtime } + /// + /// Returns the keyvalue table key for the current server/app + /// + private string MainDomKey { get; } = MainDomKeyPrefix + "-" + + (NetworkHelper.MachineName // eg DOMAIN\SERVER + + HttpRuntime.AppDomainAppId) // eg /LM/S3SVC/11/ROOT + .GenerateHash(); + private void ListeningLoop() { while (true) From 2dd938bf29134910f3769b5a26501b473d7185d2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 May 2020 19:12:20 +1000 Subject: [PATCH 13/96] Change to re-use the MainDomId of the normal maindom --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 0107ecb8da..5f5d0d607f 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -3,6 +3,7 @@ using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using System.Web; @@ -130,10 +131,12 @@ namespace Umbraco.Core.Runtime /// /// Returns the keyvalue table key for the current server/app /// - private string MainDomKey { get; } = MainDomKeyPrefix + "-" - + (NetworkHelper.MachineName // eg DOMAIN\SERVER - + HttpRuntime.AppDomainAppId) // eg /LM/S3SVC/11/ROOT - .GenerateHash(); + /// + /// The key is the the normal MainDomId which takes into account the AppDomainAppId and the physical file path of the app and this is + /// combined with the current machine name. The machine name is required because the default semaphore lock is machine wide so it implicitly + /// takes into account machine name whereas this needs to be explicitly per machine. + /// + private string MainDomKey { get; } = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId()).GenerateHash(); private void ListeningLoop() { From c6055cf0e3043020fbce00de809ea662f88d01f9 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Mon, 11 May 2020 14:39:12 +0100 Subject: [PATCH 14/96] Add keys for custom audit events #8050 (#8051) --- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 2 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index d7ccb13193..e764b5591e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -168,6 +168,7 @@ Content sent for publishing Content sent for publishing for languages: %0% Sort child items performed by user + %0% Copy Publish Publish @@ -180,6 +181,7 @@ Send To Publish Send To Publish Sort + Custom History (all variants) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index e4c764e112..e1b1df2c34 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -170,6 +170,7 @@ Content sent for publishing Content sent for publishing for languages: %0% Sort child items performed by user + %0% Copy Publish Publish @@ -183,6 +184,7 @@ Send To Publish Send To Publish Sort + Custom History (all variants) From 89143b5daef2b6996250c6c1a16c741417e2bb1e Mon Sep 17 00:00:00 2001 From: Alan Mac Kenna Date: Sat, 2 May 2020 14:47:59 +0100 Subject: [PATCH 15/96] upgraded tinymce to 4.9.10 --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 8ab5980107..c298f063a7 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -42,7 +42,7 @@ "npm": "6.13.6", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", - "tinymce": "4.9.9", + "tinymce": "4.9.10", "typeahead.js": "0.11.1", "underscore": "1.9.1" }, From b2a9e17c90ab5c9d2bfa7c7b6d2e738a152073e0 Mon Sep 17 00:00:00 2001 From: Alan Thom Date: Fri, 8 May 2020 13:41:28 +0100 Subject: [PATCH 16/96] Adding ng-trim="false" attribute to textarea property editor --- .../src/views/propertyeditors/textarea/textarea.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index d255c4a5d6..87f6ffeac9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -1,6 +1,6 @@
- + {{mandatoryMessage}} From 3e73888510b2d146efe71954cb84e2208eb9da5e Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 12 May 2020 12:20:32 +0200 Subject: [PATCH 17/96] Changing path that was causing an error --- build/build.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/build.ps1 b/build/build.ps1 index b69f02442b..2ca0a28565 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -460,8 +460,11 @@ $src = "$($this.SolutionRoot)\src" $out = $this.BuildOutput + # Check if the solution has been built + if (!(Test-Path "$src\Umbraco.Web.UI.Client\node_modules")) {throw "Umbraco needs to be built before generating the Angular Docs"} + "Moving to Umbraco.Web.UI.Docs folder" - cd ..\src\Umbraco.Web.UI.Docs + cd $src\Umbraco.Web.UI.Docs "Generating the docs and waiting before executing the next commands" & npm install From 64be0c99afa43482248d3409290a2da90df886a4 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 12 May 2020 13:33:30 +0200 Subject: [PATCH 18/96] Changing the naming in case they are used on the build server --- build/build.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/build.ps1 b/build/build.ps1 index 2ca0a28565..45c98a3bcb 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -433,9 +433,9 @@ $this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput) }) - $ubuild.DefineMethod("BuildCSharpDocs", + $ubuild.DefineMethod("PrepareCSharpDocs", { - Write-Host "Building C# Documentation" + Write-Host "Prepare C# Documentation" $src = "$($this.SolutionRoot)\src" $tmp = $this.BuildTemp @@ -453,9 +453,9 @@ & $this.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput }) - $ubuild.DefineMethod("BuildAngularDocs", + $ubuild.DefineMethod("PrepareAngularDocs", { - Write-Host "Building Angular Documentation" + Write-Host "Prepare Angular Documentation" $src = "$($this.SolutionRoot)\src" $out = $this.BuildOutput From 8059927ac340c346ecfac07b33ef24f06d9d972f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 14 May 2020 08:41:05 +0200 Subject: [PATCH 19/96] Remove "double tabbing" in the datatype picker (#8102) --- .../datatypepicker/datatypepicker.html | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html index b31a396c2d..768f8a8c24 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html @@ -37,9 +37,8 @@
{{key | umbCmsTitleCase}}
public string Culture { get; set; } + /// + /// When dealing with content variants, this is the segment for the variant + /// + public string Segment { get; set; } + /// /// An array of metadata that is parsed out from the file info posted to the server which is set on the client. /// diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index 96a072330b..653b4f427c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -26,6 +26,7 @@ fileManager.setFiles({ propertyAlias: vm.propertyAlias, culture: vm.culture, + segment: vm.segment, files: [] }); //clear the current files @@ -92,6 +93,11 @@ vm.culture = null; } + //normalize segment to null if it's not there + if (!vm.segment) { + vm.segment = null; + } + // TODO: need to figure out what we can do for things like Nested Content var existingClientFiles = checkPendingClientFiles(); @@ -134,11 +140,16 @@ vm.culture = null; } + //normalize segment to null if it's not there + if (!vm.segment) { + vm.segment = null; + } + //check the file manager to see if there's already local files pending for this editor var existingClientFiles = _.map( _.filter(fileManager.getFiles(), function (f) { - return f.alias === vm.propertyAlias && f.culture === vm.culture; + return f.alias === vm.propertyAlias && f.culture === vm.culture && f.segment === vm.segment; }), function (f) { return f.file; @@ -264,7 +275,8 @@ fileManager.setFiles({ propertyAlias: vm.propertyAlias, files: args.files, - culture: vm.culture + culture: vm.culture, + segment: vm.segment }); updateModelFromSelectedFiles(args.files).then(function(newVal) { @@ -287,6 +299,7 @@ templateUrl: 'views/components/upload/umb-property-file-upload.html', bindings: { culture: "@?", + segment: "@?", propertyAlias: "@", value: "<", hideSelection: "<", diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 9e0285d58d..38aee3fc4a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -39,18 +39,22 @@ function fileManager($rootScope) { args.culture = null; } + if (!args.segment) { + args.segment = null; + } + var metaData = []; if (Utilities.isArray(args.metaData)) { metaData = args.metaData; } - //this will clear the files for the current property/culture and then add the new ones for the current property + //this will clear the files for the current property/culture/segment and then add the new ones for the current property fileCollection = _.reject(fileCollection, function (item) { - return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture); + return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture) && (!args.segment || args.segment === item.segment); }); for (var i = 0; i < args.files.length; i++) { //save the file object to the files collection - fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData }); + fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, segment: args.segment, metaData: metaData }); } }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index edf698c8a7..4cbc5e567a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -252,12 +252,13 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe for (var f in args.files) { //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key // so we know which property it belongs to on the server side - var fileKey = "file_" + args.files[f].alias + "_" + (args.files[f].culture ? args.files[f].culture : ""); + var file = args.files[f]; + var fileKey = "file_" + file.alias + "_" + (file.culture ? file.culture : "") + "_" + (file.segment ? file.segment : ""); - if (Utilities.isArray(args.files[f].metaData) && args.files[f].metaData.length > 0) { - fileKey += ("_" + args.files[f].metaData.join("_")); + if (Utilities.isArray(file.metaData) && file.metaData.length > 0) { + fileKey += ("_" + file.metaData.join("_")); } - formData.append(fileKey, args.files[f].file); + formData.append(fileKey, file.file); } }).then(function (response) { //success callback diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 17959b9950..c485f4bbc6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -34,6 +34,7 @@ fileManager.setFiles({ propertyAlias: $scope.model.alias, culture: $scope.model.culture, + segment: $scope.model.segment, files: [] }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index 2bc609714a..522278e99e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -1,5 +1,6 @@ 
3) + { + segment = parts[3]; + //normalize to null if empty + if (segment.IsNullOrWhiteSpace()) + { + segment = null; + } + } + + // TODO: anything after 4 parts we can put in metadata var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); @@ -59,6 +71,7 @@ namespace Umbraco.Web.Editors.Binders TempFilePath = file.LocalFileName, PropertyAlias = propAlias, Culture = culture, + Segment = segment, FileName = fileName }); } diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 300c777b3a..893c9f5941 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -76,7 +76,7 @@ namespace Umbraco.Web.Editors // prepare files, if any matching property and culture var files = contentItem.UploadedFiles - .Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture) + .Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture && x.Segment == propertyDto.Segment) .ToArray(); foreach (var file in files) From ea380985dbb3274c50fa6f428c3503bac3d74a49 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:19:43 +0200 Subject: [PATCH 23/96] Moving files using MailKit nuget package (Smtp successor) as SmtpClient has been marked as obsolete by Microsoft --- .../Events/SendEmailEventArgs.cs | 0 .../Users}/EmailSender.cs | 50 ++++++++++++++++--- .../Users}/IEmailSender.cs | 0 3 files changed, 43 insertions(+), 7 deletions(-) rename src/{Umbraco.Core => Umbraco.Infrastructure}/Events/SendEmailEventArgs.cs (100%) rename src/{Umbraco.Core => Umbraco.Infrastructure/Users}/EmailSender.cs (58%) rename src/{Umbraco.Core => Umbraco.Infrastructure/Users}/IEmailSender.cs (100%) diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs similarity index 100% rename from src/Umbraco.Core/Events/SendEmailEventArgs.cs rename to src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs diff --git a/src/Umbraco.Core/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs similarity index 58% rename from src/Umbraco.Core/EmailSender.cs rename to src/Umbraco.Infrastructure/Users/EmailSender.cs index 5cfdd765bc..62c45b8f78 100644 --- a/src/Umbraco.Core/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -1,9 +1,12 @@ using System; using System.Net.Mail; using System.Threading.Tasks; -using Umbraco.Core.Composing; +using MailKit.Security; +using MimeKit; +using MimeKit.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; namespace Umbraco.Core { @@ -21,7 +24,7 @@ namespace Umbraco.Core { } - internal EmailSender(IGlobalSettings globalSettings, bool enableEvents) + public EmailSender(IGlobalSettings globalSettings, bool enableEvents) { _globalSettings = globalSettings; _enableEvents = enableEvents; @@ -45,7 +48,9 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - client.Send(message); + client.Connect(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + client.Send(ConstructEmailMessage(message)); + client.Disconnect(true); } } } @@ -65,14 +70,21 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - if (client.DeliveryMethod == SmtpDeliveryMethod.Network) + var appSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; + var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), appSettingsDeliveryMethod, true); + + await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + if (deliveryMethod == SmtpDeliveryMethod.Network) { - await client.SendMailAsync(message); + await client.SendAsync(ConstructEmailMessage(message)); } else { - client.Send(message); + client.Send(ConstructEmailMessage(message)); } + + await client.DisconnectAsync(true); } } } @@ -83,7 +95,7 @@ namespace Umbraco.Core /// /// We assume this is possible if either an event handler is registered or an smtp server is configured /// - internal static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured; + public static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured; /// /// returns true if an event handler has been registered @@ -103,5 +115,29 @@ namespace Umbraco.Core var handler = SendEmail; if (handler != null) handler(null, e); } + + private MimeMessage ConstructEmailMessage(MailMessage mailMessage) + { + var messageToSend = new MimeMessage + { + Subject = mailMessage.Subject + }; + + var fromEmail = mailMessage.From?.Address; + if(string.IsNullOrEmpty(fromEmail)) + fromEmail = _globalSettings.SmtpSettings.From; + + messageToSend.From.Add(new MailboxAddress(fromEmail)); + + foreach (var mailAddress in mailMessage.To) + messageToSend.To.Add(new MailboxAddress(mailAddress.Address)); + + if (mailMessage.IsBodyHtml) + messageToSend.Body = new TextPart(TextFormat.Html) { Text = mailMessage.Body }; + else + messageToSend.Body = new TextPart(TextFormat.Plain) { Text = mailMessage.Body }; + + return messageToSend; + } } } diff --git a/src/Umbraco.Core/IEmailSender.cs b/src/Umbraco.Infrastructure/Users/IEmailSender.cs similarity index 100% rename from src/Umbraco.Core/IEmailSender.cs rename to src/Umbraco.Infrastructure/Users/IEmailSender.cs From e85ccb0dabd0b488292f566afcbb1640980f5cb6 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:20:25 +0200 Subject: [PATCH 24/96] Removing TODOs with the actual implementation --- .../Editors/AuthenticationController.cs | 21 +++++++++++++------ src/Umbraco.Web/Editors/UsersController.cs | 20 +++++++++--------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index fc34a35566..50e9921154 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Collections.Generic; +using System.Net.Mail; using System.Security.Principal; using System.Threading.Tasks; using System.Web; @@ -328,12 +329,20 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings), new[] { identityUser.UserName, callbackUrl }); - // TODO: Port email service to ASP.NET Core - /*await UserManager.SendEmailAsync(identityUser.Id, - Services.TextService.Localize("login/resetPasswordEmailCopySubject", - // Ensure the culture of the found user is used for the email! - UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)), - message);*/ + var subject = Services.TextService.Localize("login/resetPasswordEmailCopySubject", + // Ensure the culture of the found user is used for the email! + UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)); + + var emailSender = new EmailSender(GlobalSettings, true); + var mailMessage = new MailMessage() + { + Subject = subject, + Body = message, + IsBodyHtml = true + }; + mailMessage.To.Add(user.Email); + + await emailSender.SendAsync(mailMessage); UserManager.RaiseForgotPasswordRequestedEvent(user.Id); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 7aa71b6e2e..029612dc90 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Mail; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading.Tasks; @@ -500,17 +501,16 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings), new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail }); - // TODO: Port email service to ASP.NET Core - /*await UserManager.EmailService.SendAsync( - //send the special UmbracoEmailMessage which configures it's own sender - //to allow for events to handle sending the message if no smtp is configured - new UmbracoEmailMessage(new EmailSender(GlobalSettings, true)) - { - Body = emailBody, - Destination = userDisplay.Email, - Subject = emailSubject - });*/ + var emailSender = new EmailSender(GlobalSettings, true); + var mailMessage = new MailMessage() + { + Subject = emailSubject, + Body = emailBody, + IsBodyHtml = true + }; + mailMessage.To.Add(to.Email); + await emailSender.SendAsync(mailMessage); } /// From ad31db9f4afa888a88e5617d25f94d43d856336b Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:21:45 +0200 Subject: [PATCH 25/96] Adding DeliveryMethod option which we make use of in EmailSender.cs --- src/Umbraco.Configuration/Legacy/SmtpSettings.cs | 1 + src/Umbraco.Configuration/Models/GlobalSettings.cs | 5 +++-- src/Umbraco.Core/Configuration/ISmtpSettings.cs | 1 + .../Builders/SmtpSettingsBuilder.cs | 10 ++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs index 7e3ff80690..beb6eb9691 100644 --- a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs +++ b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs @@ -8,5 +8,6 @@ namespace Umbraco.Configuration public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public string DeliveryMethod { get; set; } } } diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index 4b30813bd5..646bca0f13 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -72,10 +72,10 @@ namespace Umbraco.Configuration.Models _configuration.GetValue(Prefix + "NoNodesViewPath", "~/config/splashes/NoNodes.cshtml"); public bool IsSmtpServerConfigured => - _configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")?.GetChildren().Any() ?? false; + _configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp")?.GetChildren().Any() ?? false; public ISmtpSettings SmtpSettings => - new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")); + new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp")); private class SmtpSettingsImpl : ISmtpSettings { @@ -90,6 +90,7 @@ namespace Umbraco.Configuration.Models public string Host => _configurationSection.GetValue("Host"); public int Port => _configurationSection.GetValue("Port"); public string PickupDirectoryLocation => _configurationSection.GetValue("PickupDirectoryLocation"); + public string DeliveryMethod => _configurationSection.GetValue("DeliveryMethod"); } } } diff --git a/src/Umbraco.Core/Configuration/ISmtpSettings.cs b/src/Umbraco.Core/Configuration/ISmtpSettings.cs index c2fb4b2dbe..217e83455b 100644 --- a/src/Umbraco.Core/Configuration/ISmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/ISmtpSettings.cs @@ -6,5 +6,6 @@ namespace Umbraco.Core.Configuration string Host { get; } int Port{ get; } string PickupDirectoryLocation { get; } + string DeliveryMethod { get; } } } diff --git a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs index 344d7bcf87..140af5f723 100644 --- a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs @@ -16,6 +16,7 @@ namespace Umbraco.Tests.Common.Builders private string _host; private int? _port; private string _pickupDirectoryLocation; + private string _deliveryMethod; public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -45,12 +46,19 @@ namespace Umbraco.Tests.Common.Builders return this; } + public SmtpSettingsBuilder WithDeliveryMethod(string deliveryMethod) + { + _deliveryMethod = deliveryMethod; + return this; + } + public override ISmtpSettings Build() { var from = _from ?? null; var host = _host ?? null; var port = _port ?? 25; var pickupDirectoryLocation = _pickupDirectoryLocation ?? null; + var deliveryMethod = _deliveryMethod ?? null; return new TestSmtpSettings() { @@ -58,6 +66,7 @@ namespace Umbraco.Tests.Common.Builders Host = host, Port = port, PickupDirectoryLocation = pickupDirectoryLocation, + DeliveryMethod = deliveryMethod }; } @@ -67,6 +76,7 @@ namespace Umbraco.Tests.Common.Builders public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public string DeliveryMethod { get; set; } } } } From 16d805e6f5b7750b5acb52fdd43d8380aae020c6 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:24:55 +0200 Subject: [PATCH 26/96] Adding Smtp section in appsettings, registering IEmailSender as an email service and ofc the MailKit nuget in Infrastructure proj --- .../Runtime/CoreInitialComposer.cs | 1 + .../Umbraco.Infrastructure.csproj | 1 + src/Umbraco.Web.UI.NetCore/appsettings.json | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index 19d3716e1c..34d69c2cd5 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -320,6 +320,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index db3ae1bc25..8da86df6e8 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index 46dc20034b..95fdaeec67 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -116,6 +116,16 @@ "Replacement": "" } ] + }, + "Global": { + "Smtp": { + "From": "noreply@example.com", + "Host": "127.0.0.1", + "Port": 25, + "UserName": "username", + "Password": "password", + "DeliveryMethod": "network" + } } } } From 65fa1efc9c7f3dd4fddc3775810a224e35651659 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:54:28 +0200 Subject: [PATCH 27/96] Changing a var name for better reflection on the origin of its value --- src/Umbraco.Infrastructure/Users/EmailSender.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Users/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs index 62c45b8f78..87f27e5097 100644 --- a/src/Umbraco.Infrastructure/Users/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -70,8 +70,8 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - var appSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; - var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), appSettingsDeliveryMethod, true); + var smtpSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; + var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), smtpSettingsDeliveryMethod, true); await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); From c36495ac9c5f00c02943b9df18f36ce5b58d1571 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 20 May 2020 10:30:07 +0200 Subject: [PATCH 28/96] Use a language that's more common to people's machines --- .../cypress/integration/Settings/languages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts index 17b7bb6805..49bcf94943 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts @@ -6,7 +6,7 @@ context('Languages', () => { }); it('Add language', () => { - const name = "Neddersass’sch (Nedderlannen)"; // Must be an option in the select box + const name = "Kyrgyz (Kyrgyzstan)"; // Must be an option in the select box cy.umbracoEnsureLanguageNameNotExists(name); From 2b4dfbca9d44b7415bae5d6582bec35fde37a3b5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 11:42:23 +0200 Subject: [PATCH 29/96] #8142 - Small cleanup, and bugfix --- .../Legacy/SmtpSettings.cs | 7 +++- .../Models/GlobalSettings.cs | 7 +++- .../Configuration/ISmtpSettings.cs | 6 +++- .../Events/SendEmailEventArgs.cs | 0 .../Users => Umbraco.Core}/IEmailSender.cs | 0 .../Users/EmailSender.cs | 35 +++++++------------ .../Builders/SmtpSettingsBuilder.cs | 34 ++++++++++++++---- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 1 + .../AuthenticationControllerTests.cs | 3 +- .../Web/Controllers/UsersControllerTests.cs | 15 +++++--- .../Editors/AuthenticationController.cs | 12 ++++--- src/Umbraco.Web/Editors/UsersController.cs | 12 ++++--- 12 files changed, 85 insertions(+), 47 deletions(-) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Events/SendEmailEventArgs.cs (100%) rename src/{Umbraco.Infrastructure/Users => Umbraco.Core}/IEmailSender.cs (100%) diff --git a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs index beb6eb9691..dce3d85840 100644 --- a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs +++ b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs @@ -1,3 +1,4 @@ +using System.Net.Mail; using Umbraco.Core.Configuration; namespace Umbraco.Configuration @@ -8,6 +9,10 @@ namespace Umbraco.Configuration public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } - public string DeliveryMethod { get; set; } + public SmtpDeliveryMethod DeliveryMethod { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } } } diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index 646bca0f13..e4995cfb0b 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Mail; using Microsoft.Extensions.Configuration; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -90,7 +91,11 @@ namespace Umbraco.Configuration.Models public string Host => _configurationSection.GetValue("Host"); public int Port => _configurationSection.GetValue("Port"); public string PickupDirectoryLocation => _configurationSection.GetValue("PickupDirectoryLocation"); - public string DeliveryMethod => _configurationSection.GetValue("DeliveryMethod"); + public SmtpDeliveryMethod DeliveryMethod => _configurationSection.GetValue("DeliveryMethod"); + + public string Username => _configurationSection.GetValue("Username"); + + public string Password => _configurationSection.GetValue("Password"); } } } diff --git a/src/Umbraco.Core/Configuration/ISmtpSettings.cs b/src/Umbraco.Core/Configuration/ISmtpSettings.cs index 217e83455b..ea42ae5567 100644 --- a/src/Umbraco.Core/Configuration/ISmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/ISmtpSettings.cs @@ -1,3 +1,5 @@ +using System.Net.Mail; + namespace Umbraco.Core.Configuration { public interface ISmtpSettings @@ -6,6 +8,8 @@ namespace Umbraco.Core.Configuration string Host { get; } int Port{ get; } string PickupDirectoryLocation { get; } - string DeliveryMethod { get; } + SmtpDeliveryMethod DeliveryMethod { get; } + string Username { get; } + string Password { get; } } } diff --git a/src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs similarity index 100% rename from src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs rename to src/Umbraco.Core/Events/SendEmailEventArgs.cs diff --git a/src/Umbraco.Infrastructure/Users/IEmailSender.cs b/src/Umbraco.Core/IEmailSender.cs similarity index 100% rename from src/Umbraco.Infrastructure/Users/IEmailSender.cs rename to src/Umbraco.Core/IEmailSender.cs diff --git a/src/Umbraco.Infrastructure/Users/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs index 87f27e5097..315a1748c3 100644 --- a/src/Umbraco.Infrastructure/Users/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -1,7 +1,7 @@ using System; +using System.Linq; using System.Net.Mail; using System.Threading.Tasks; -using MailKit.Security; using MimeKit; using MimeKit.Text; using Umbraco.Core.Configuration; @@ -70,20 +70,18 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - var smtpSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; - var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), smtpSettingsDeliveryMethod, true); - await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); - if (deliveryMethod == SmtpDeliveryMethod.Network) + var mailMessage = ConstructEmailMessage(message); + if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network) { - await client.SendAsync(ConstructEmailMessage(message)); + await client.SendAsync(mailMessage); } else { - client.Send(ConstructEmailMessage(message)); + client.Send(mailMessage); } - + await client.DisconnectAsync(true); } } @@ -118,24 +116,17 @@ namespace Umbraco.Core private MimeMessage ConstructEmailMessage(MailMessage mailMessage) { - var messageToSend = new MimeMessage - { - Subject = mailMessage.Subject - }; - var fromEmail = mailMessage.From?.Address; if(string.IsNullOrEmpty(fromEmail)) fromEmail = _globalSettings.SmtpSettings.From; - - messageToSend.From.Add(new MailboxAddress(fromEmail)); - foreach (var mailAddress in mailMessage.To) - messageToSend.To.Add(new MailboxAddress(mailAddress.Address)); - - if (mailMessage.IsBodyHtml) - messageToSend.Body = new TextPart(TextFormat.Html) { Text = mailMessage.Body }; - else - messageToSend.Body = new TextPart(TextFormat.Plain) { Text = mailMessage.Body }; + var messageToSend = new MimeMessage + { + Subject = mailMessage.Subject, + From = { new MailboxAddress(fromEmail)}, + Body = new TextPart(mailMessage.IsBodyHtml ? TextFormat.Html : TextFormat.Plain) { Text = mailMessage.Body } + }; + messageToSend.To.AddRange(mailMessage.To.Select(x=>new MailboxAddress(x.Address))); return messageToSend; } diff --git a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs index 140af5f723..bd85807203 100644 --- a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Configuration; +using System.Net.Mail; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; namespace Umbraco.Tests.Common.Builders { @@ -16,7 +18,9 @@ namespace Umbraco.Tests.Common.Builders private string _host; private int? _port; private string _pickupDirectoryLocation; - private string _deliveryMethod; + private SmtpDeliveryMethod? _deliveryMethod; + private string _username; + private string _password; public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -34,19 +38,31 @@ namespace Umbraco.Tests.Common.Builders return this; } + public SmtpSettingsBuilder WithUsername(string username) + { + _username = username; + return this; + } + public SmtpSettingsBuilder WithPost(int port) { _port = port; return this; } + public SmtpSettingsBuilder WithPassword(string password) + { + _password = password; + return this; + } + public SmtpSettingsBuilder WithPickupDirectoryLocation(string pickupDirectoryLocation) { _pickupDirectoryLocation = pickupDirectoryLocation; return this; } - public SmtpSettingsBuilder WithDeliveryMethod(string deliveryMethod) + public SmtpSettingsBuilder WithDeliveryMethod(SmtpDeliveryMethod deliveryMethod) { _deliveryMethod = deliveryMethod; return this; @@ -58,7 +74,9 @@ namespace Umbraco.Tests.Common.Builders var host = _host ?? null; var port = _port ?? 25; var pickupDirectoryLocation = _pickupDirectoryLocation ?? null; - var deliveryMethod = _deliveryMethod ?? null; + var deliveryMethod = _deliveryMethod ?? SmtpDeliveryMethod.Network; + var username = _username ?? null; + var password = _password ?? null; return new TestSmtpSettings() { @@ -66,7 +84,9 @@ namespace Umbraco.Tests.Common.Builders Host = host, Port = port, PickupDirectoryLocation = pickupDirectoryLocation, - DeliveryMethod = deliveryMethod + DeliveryMethod = deliveryMethod, + Username = username, + Password = password, }; } @@ -76,7 +96,9 @@ namespace Umbraco.Tests.Common.Builders public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } - public string DeliveryMethod { get; set; } + public SmtpDeliveryMethod DeliveryMethod { get; set; } + public string Username { get; set; } + public string Password { get; set; } } } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 80f6ab9c9e..bbc869fc65 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -313,6 +313,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.RegisterUnique(); + Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index a162b0cd48..f8f02560c4 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -91,7 +91,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 4c373b2bc8..ce355180f6 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -103,7 +103,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; @@ -176,7 +177,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -219,7 +221,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -297,7 +300,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -487,7 +491,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance()); + Factory.GetInstance(), + Factory.GetInstance()); var mockOwinContext = new Mock(); var mockUserManagerMarker = new Mock(); diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 50e9921154..01e13ff051 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -48,6 +48,7 @@ namespace Umbraco.Web.Editors private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtimeState; private readonly ISecuritySettings _securitySettings; + private readonly IEmailSender _emailSender; public AuthenticationController( IUserPasswordConfiguration passwordConfiguration, @@ -61,13 +62,15 @@ namespace Umbraco.Web.Editors IRuntimeState runtimeState, UmbracoMapper umbracoMapper, ISecuritySettings securitySettings, - IPublishedUrlProvider publishedUrlProvider) + IPublishedUrlProvider publishedUrlProvider, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoMapper, publishedUrlProvider) { _passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); _runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); _securitySettings = securitySettings ?? throw new ArgumentNullException(nameof(securitySettings)); + _emailSender = emailSender; } protected BackOfficeUserManager UserManager => _userManager @@ -333,16 +336,15 @@ namespace Umbraco.Web.Editors // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)); - var emailSender = new EmailSender(GlobalSettings, true); var mailMessage = new MailMessage() { Subject = subject, Body = message, - IsBodyHtml = true + IsBodyHtml = true, + To = { user.Email} }; - mailMessage.To.Add(user.Email); - await emailSender.SendAsync(mailMessage); + await _emailSender.SendAsync(mailMessage); UserManager.RaiseForgotPasswordRequestedEvent(user.Id); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 029612dc90..3c71245a0a 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -53,6 +53,7 @@ namespace Umbraco.Web.Editors private readonly ISqlContext _sqlContext; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly ISecuritySettings _securitySettings; + private readonly IEmailSender _emailSender; public UsersController( IGlobalSettings globalSettings, @@ -69,7 +70,8 @@ namespace Umbraco.Web.Editors IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, IPublishedUrlProvider publishedUrlProvider, - ISecuritySettings securitySettings) + ISecuritySettings securitySettings, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) { _mediaFileSystem = mediaFileSystem; @@ -78,6 +80,7 @@ namespace Umbraco.Web.Editors _sqlContext = sqlContext; _imageUrlGenerator = imageUrlGenerator; _securitySettings = securitySettings; + _emailSender = emailSender; } /// @@ -501,16 +504,15 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings), new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail }); - var emailSender = new EmailSender(GlobalSettings, true); var mailMessage = new MailMessage() { Subject = emailSubject, Body = emailBody, - IsBodyHtml = true + IsBodyHtml = true, + To = { to.Email} }; - mailMessage.To.Add(to.Email); - await emailSender.SendAsync(mailMessage); + await _emailSender.SendAsync(mailMessage); } /// From 490ae37b28571996da37cdf0f932318d317a9c21 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 12:38:21 +0200 Subject: [PATCH 30/96] #8142 - Added smtp authentication if username or password provided --- src/Umbraco.Infrastructure/Users/EmailSender.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Umbraco.Infrastructure/Users/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs index 315a1748c3..9a2f425021 100644 --- a/src/Umbraco.Infrastructure/Users/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -48,7 +48,15 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { + client.Connect(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + if (!(_globalSettings.SmtpSettings.Username is null && + _globalSettings.SmtpSettings.Password is null)) + { + client.Authenticate(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password); + } + client.Send(ConstructEmailMessage(message)); client.Disconnect(true); } @@ -72,6 +80,12 @@ namespace Umbraco.Core { await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + if (!(_globalSettings.SmtpSettings.Username is null && + _globalSettings.SmtpSettings.Password is null)) + { + await client.AuthenticateAsync(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password); + } + var mailMessage = ConstructEmailMessage(message); if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network) { From 5b7f1227f9b4f97ede6e2799a1aa36bb9c4fd823 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 22 May 2020 10:46:13 +0200 Subject: [PATCH 31/96] Bugfix for issue with hanging saves. + Fix for double post of save templates --- .../cypress/integration/Settings/templates.ts | 28 +++++++++---------- src/Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../NuCache/PublishedSnapshotService.cs | 23 ++++++--------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts index bbabbcb4bf..c135258ec5 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts @@ -6,28 +6,28 @@ context('Templates', () => { }); it('Create template', () => { - const name = "Test template"; + const name = "Test template"; - cy.umbracoEnsureTemplateNameNotExists(name); + cy.umbracoEnsureTemplateNameNotExists(name); - cy.umbracoSection('settings'); - cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); - cy.umbracoTreeItem("settings", ["Templates"]).rightclick(); + cy.umbracoTreeItem("settings", ["Templates"]).rightclick(); - cy.umbracoContextMenuAction("action-create").click(); + cy.umbracoContextMenuAction("action-create").click(); - //Type name - cy.umbracoEditorHeaderName(name); + //Type name + cy.umbracoEditorHeaderName(name); - //Save - cy.get('.btn-success').click(); + //Save + cy.get("form[name='contentForm']").submit(); - //Assert - cy.umbracoSuccessNotification().should('be.visible'); + //Assert + cy.umbracoSuccessNotification().should('be.visible'); - //Clean up - cy.umbracoEnsureTemplateNameNotExists(name); + //Clean up + cy.umbracoEnsureTemplateNameNotExists(name); }); }); diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index daa1c424bb..997de4db77 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -6,7 +6,7 @@ "devDependencies": { "cross-env": "^7.0.2", "ncp": "^2.0.0", - "cypress": "^4.5.0", + "cypress": "^4.6.0", "umbraco-cypress-testhelpers": "1.0.0-beta-38" }, "dependencies": { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 7e78b2e96f..a39e26e2b1 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -882,22 +882,15 @@ namespace Umbraco.Web.PublishedCache.NuCache // they require. // These can be run side by side in parallel. + using (_contentStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); + } - Parallel.Invoke( - () => - { - using (_contentStore.GetScopedWriteLock(_scopeProvider)) - { - NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); - } - }, - () => - { - using (_mediaStore.GetScopedWriteLock(_scopeProvider)) - { - NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); - } - }); + using (_mediaStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); + } } ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); From 5be8282ce45c3a11ff2d23eb2667e5bfc9d4ec7d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 22 May 2020 12:00:56 +0200 Subject: [PATCH 32/96] Delete templates test --- src/Umbraco.Tests.AcceptanceTest/cypress.json | 3 ++- .../cypress/integration/Settings/templates.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress.json b/src/Umbraco.Tests.AcceptanceTest/cypress.json index 051bf4a871..33978211ed 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress.json +++ b/src/Umbraco.Tests.AcceptanceTest/cypress.json @@ -6,5 +6,6 @@ "username": "", "password": "" }, - "supportFile": "cypress/support/index.ts" + "supportFile": "cypress/support/index.ts", + "videoUploadOnPasses" : false } diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts index c135258ec5..6871db7ffe 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts @@ -1,4 +1,6 @@ /// +import {DocumentTypeBuilder, TemplateBuilder} from "umbraco-cypress-testhelpers"; + context('Templates', () => { beforeEach(() => { @@ -30,4 +32,26 @@ context('Templates', () => { cy.umbracoEnsureTemplateNameNotExists(name); }); + it('Delete template', () => { + const name = "Test template"; + cy.umbracoEnsureTemplateNameNotExists(name); + + const template = new TemplateBuilder() + .withName(name) + .build(); + + cy.saveTemplate(template); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Templates", name]).rightclick(); + cy.umbracoContextMenuAction("action-delete").click(); + + cy.umbracoButtonByLabelKey("general_ok").click(); + + cy.contains(name).should('not.exist'); + + cy.umbracoEnsureTemplateNameNotExists(name); + }); }); From 1a04b9711b565d173f336bdb4dc96745c87b3e3f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 22 May 2020 12:56:17 +0200 Subject: [PATCH 33/96] Updated testhelpers reference --- src/Umbraco.Tests.AcceptanceTest/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index 997de4db77..ad125d090a 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -7,7 +7,7 @@ "cross-env": "^7.0.2", "ncp": "^2.0.0", "cypress": "^4.6.0", - "umbraco-cypress-testhelpers": "1.0.0-beta-38" + "umbraco-cypress-testhelpers": "1.0.0-beta-39" }, "dependencies": { "typescript": "^3.9.2" From 4c16421cbb60f1f0b4f2e9be4fad602f436866bd Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Sat, 23 May 2020 16:15:42 +0100 Subject: [PATCH 34/96] Update umb-button.html (#8145) Noticed the missing closing bracket on the `
From b6262bf33413493f952d088c4e61150b641998ec Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Mon, 25 May 2020 16:51:52 +0100 Subject: [PATCH 35/96] Remove ContentFinderByRedirectUrl if RedirectUrlTracking is disabled (#7761) --- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 203bae4854..c631aac5e3 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -179,7 +179,7 @@ namespace Umbraco.Web.Runtime .Remove() .Remove() .Remove(); - + // add all known factories, devs can then modify this list on application // startup either by binding to events or in their own global.asax composition.FilteredControllerFactory() @@ -203,8 +203,12 @@ namespace Umbraco.Web.Runtime .Append() .Append() //.Append() // disabled, this is an odd finder - .Append() - .Append(); + .Append(); + //only append ContentFinderByRedirectUrl if RedirectUrlTracking is not disabled + if (composition.Configs.Settings().WebRouting.DisableRedirectUrlTracking == false) + { + composition.ContentFinders().Append(); + } composition.RegisterUnique(); From 5215a769feb593d4675d832e112618a581912cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 25 May 2020 21:30:56 +0200 Subject: [PATCH 36/96] Added unit test that fails before PR and succeeds after PR --- .../Composing/ContainerConformingTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index f5c1ff9bc7..06ab246d84 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -330,6 +330,17 @@ namespace Umbraco.Tests.Composing var s1 = factory.GetInstance(); var s2 = factory.GetInstance(); Assert.AreSame(s1, s2); + + register.Register(factory => + { + var param1 = new Thing1(); + var param2 = new Thing1(); + + return factory.CreateInstance(param1, param2); + }); + + var instance = factory.GetInstance(); + Assert.AreNotEqual(instance.Thing, instance.AnotherThing); } public interface IThing { } @@ -352,5 +363,17 @@ namespace Umbraco.Tests.Composing public IEnumerable Things { get; } } + + public class Thing4 : ThingBase + { + public readonly Thing1 Thing; + public readonly Thing1 AnotherThing; + + public Thing4(Thing1 thing, Thing1 anotherThing) + { + Thing = thing; + AnotherThing = anotherThing; + } + } } } From 558ffaeba70e922240a124a47faa1718a8f3e0be Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 May 2020 09:18:09 +0200 Subject: [PATCH 37/96] Move test and use params that are easier to distinguish --- .../Composing/ContainerConformingTests.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index 06ab246d84..22978bdb43 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -330,15 +330,20 @@ namespace Umbraco.Tests.Composing var s1 = factory.GetInstance(); var s2 = factory.GetInstance(); Assert.AreSame(s1, s2); + } - register.Register(factory => + [Test] + public void CanRegisterMultipleSameTypeParametersWithCreateInstance() + { + var register = GetRegister(); + var factory = register.CreateFactory(); + register.Register(factory => { - var param1 = new Thing1(); - var param2 = new Thing1(); + var param1 = "param1"; + var param2 = "param2"; return factory.CreateInstance(param1, param2); }); - var instance = factory.GetInstance(); Assert.AreNotEqual(instance.Thing, instance.AnotherThing); } @@ -366,10 +371,10 @@ namespace Umbraco.Tests.Composing public class Thing4 : ThingBase { - public readonly Thing1 Thing; - public readonly Thing1 AnotherThing; + public readonly string Thing; + public readonly string AnotherThing; - public Thing4(Thing1 thing, Thing1 anotherThing) + public Thing4(string thing, string anotherThing) { Thing = thing; AnotherThing = anotherThing; From 41ac96d69da6af39407cceaa022435645f543ec3 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Tue, 26 May 2020 08:45:57 +0100 Subject: [PATCH 38/96] Fix HasValue and IsValue for Dropdown Property Types with no Values (#8154) --- .../dropdownFlexible/dropdownFlexible.controller.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js index a8979c949b..afbb4feb20 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js @@ -15,7 +15,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo //ensure this is a bool, old data could store zeros/ones or string versions $scope.model.config.multiple = Object.toBoolean($scope.model.config.multiple); - + + //ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected + $scope.$on("formSubmitting", function () { + if ($scope.model.value.length === 0 || $scope.model.value[0] === null) { + $scope.model.value = null; + } + }); + function convertArrayToDictionaryArray(model){ //now we need to format the items in the dictionary because we always want to have an array var newItems = []; From f33b4b7be534e6e3504da53fce20450e6201477e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 May 2020 11:49:35 +0200 Subject: [PATCH 39/96] Some cleanup and fixing the build --- .../Composing/ContainerConformingTests.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index 22978bdb43..d7a19cb553 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -336,14 +336,16 @@ namespace Umbraco.Tests.Composing public void CanRegisterMultipleSameTypeParametersWithCreateInstance() { var register = GetRegister(); - var factory = register.CreateFactory(); - register.Register(factory => - { - var param1 = "param1"; - var param2 = "param2"; - return factory.CreateInstance(param1, param2); + register.Register(c => + { + const string param1 = "param1"; + const string param2 = "param2"; + + return c.CreateInstance(param1, param2); }); + + var factory = register.CreateFactory(); var instance = factory.GetInstance(); Assert.AreNotEqual(instance.Thing, instance.AnotherThing); } From f1986c0f75247e7bd6045db03961c7c71a97e06d Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 26 May 2020 12:29:14 +0200 Subject: [PATCH 40/96] Fix glitch in umb-checkbox background --- .../src/less/components/umb-form-check.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index a52f81b92a..2612448157 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -45,6 +45,7 @@ label.umb-form-check--checkbox{ } &:checked ~ .umb-form-check__state .umb-form-check__check { border-color: @ui-option-type; + background-color: @ui-option-type; } &:checked:hover ~ .umb-form-check__state .umb-form-check__check { &::before { From 220278a322eee03e70d5a7f82e4a3e1b02863b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 26 May 2020 13:48:26 +0200 Subject: [PATCH 41/96] abstracting property clearing, so each property editor can registrer their own clear property resolvers. --- .../src/common/services/clipboard.service.js | 69 +++++++++++-------- .../nestedcontent/nestedcontent.controller.js | 63 ++++++++++++++++- 2 files changed, 99 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index ec2a754420..083b4e86b7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -12,6 +12,9 @@ */ function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) { + + var clearPropertyResolvers = []; + var STORAGE_KEY = "umbClipboardService"; @@ -53,42 +56,32 @@ function clipboardService(notificationsService, eventsService, localStorageServi return false; } - function removeKeys(values) { - for (var i = 0; i < values.length; i++) { - var obj = values[i]; - // Entires with this property are entries of a nested content property. And those keys we can remove. - if (obj.ncContentTypeAlias) { - delete obj.key; - // Loop through all properties: - for (var k in obj) { - // if this property is an array, we need to check if there's more keys to remove. - if (Array.isArray(obj[k])) { - removeKeys(obj[k]) - } - } - } + function clearPropertyForStorage(prop) { + + for (var i=0; i prepareEntryForStorage(data)); + var copiedDatas = datas.map(data => prepareEntryForStorage(data, firstLevelClearupMethod)); // remove previous copies of this entry: storage.entries = storage.entries.filter( 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 7cad5a5f05..066cbd6c17 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 @@ -1,6 +1,58 @@ (function () { 'use strict'; + /** + * When performing a copy, we do copy the ElementType Data Model, but each inner Nested Content property is still stored as the Nested Content Model, aka. each property is just storing its value. To handle this we need to ensure we handle both scenarios. + */ + + + angular.module('umbraco').run(['clipboardService', function (clipboardService) { + + function clearNestedContentPropertiesForStorage(prop, propClearingMethod) { + + // if prop.editor is "Umbraco.NestedContent" + if ((typeof prop === 'object' && prop.editor === "Umbraco.NestedContent")) { + + var value = prop.value; + for (var i = 0; i < value.length; i++) { + var obj = value[i]; + + // remove the key + delete obj.key; + + // Loop through all inner properties: + for (var k in obj) { + propClearingMethod(obj[k]); + } + } + } + } + + clipboardService.registrerClearPropertyResolver(clearNestedContentPropertiesForStorage) + + + function clearInnerNestedContentPropertiesForStorage(prop, propClearingMethod) { + + // if we got an array, and it has a entry with ncContentTypeAlias this meants that we are dealing with a NestedContent property inside a NestedContent property. + if ((Array.isArray(prop) && prop.length > 0 && prop[0].ncContentTypeAlias !== undefined)) { + + for (var i = 0; i < prop.length; i++) { + var obj = prop[i]; + + // remove the key + delete obj.key; + + // Loop through all inner properties: + for (var k in obj) { + propClearingMethod(obj[k]); + } + } + } + } + + clipboardService.registrerClearPropertyResolver(clearInnerNestedContentPropertiesForStorage) + }]); + angular .module('umbraco') .component('nestedContentPropertyEditor', { @@ -13,7 +65,7 @@ } }); - function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) { + function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) { var vm = this; var model = $scope.$parent.$parent.model; @@ -76,7 +128,7 @@ } localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function (data) { - clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id); + clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id, clearNodeForCopy); }); } @@ -385,6 +437,11 @@ }); } + function clearNodeForCopy(clonedData) { + delete clonedData.key; + delete clonedData.$$hashKey; + } + vm.showCopy = clipboardService.isSupported(); vm.showPaste = false; @@ -392,7 +449,7 @@ syncCurrentNode(); - clipboardService.copy("elementType", node.contentTypeAlias, node); + clipboardService.copy("elementType", node.contentTypeAlias, node, null, null, null, clearNodeForCopy); $event.stopPropagation(); } From c3c98e2621d31dc095f012012ff2103db230f5b8 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 13:04:06 +0100 Subject: [PATCH 42/96] v8: Fix for login screen title display (#7412) --- src/Umbraco.Web.UI.Client/package-lock.json | 6 +++--- .../directives/components/application/umblogin.directive.js | 2 ++ src/Umbraco.Web.UI.Client/src/main.controller.js | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index b8087066c9..12e7b115e7 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -15313,9 +15313,9 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "tinymce": { - "version": "4.9.9", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.9.tgz", - "integrity": "sha512-7Wqh4PGSAWm6FyNwyI1uFAaZyzeQeiwd9Gg2R89SpFIqoMrSzNHIYBqnZnlDm4Bd2DJ0wcC6uJhwFrabIE8puw==" + "version": "4.9.10", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.10.tgz", + "integrity": "sha512-vyzGG04Q44Y7zWIKA4c+G7MxMCsed6JkrhU+k0TaDs9XKAiS+e+D3Fzz5OIJ7p5keF7lbRK5czgI8T1JtouZqw==" }, "tmp": { "version": "0.0.33", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index 541cc647fb..ec8da898ad 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -209,6 +209,7 @@ } if (vm.loginForm.$invalid) { + SetTitle(); return; } @@ -257,6 +258,7 @@ vm.loginForm.password.$setValidity('auth', true); } }); + SetTitle(); } function requestPasswordResetSubmit(email) { diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 81eadf150f..297d93f4bc 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -56,12 +56,14 @@ function MainController($scope, $location, appState, treeService, notificationsS appState.setSearchState("show", false); }; - $scope.showLoginScreen = function(isTimedOut) { + $scope.showLoginScreen = function (isTimedOut) { + $scope.login.pageTitle = $scope.$root.locationTitle; $scope.login.isTimedOut = isTimedOut; $scope.login.show = true; }; - $scope.hideLoginScreen = function() { + $scope.hideLoginScreen = function () { + $scope.$root.locationTitle = $scope.login.pageTitle; $scope.login.show = false; }; From 5541d130207b8a32dfb361bc4d7143c85143c645 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 13:59:33 +0100 Subject: [PATCH 43/96] V8: Accessibility Changes For umbEditorHeader Directive (edit user) (#7102) --- .../editor/umbeditorheader.directive.js | 140 ++++++++++-------- .../src/views/users/user.controller.js | 1 + 2 files changed, 78 insertions(+), 63 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 87053c083c..58f799e5af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -206,7 +206,7 @@ Use this directive to construct a header inside the main editor window. (function () { 'use strict'; - function EditorHeaderDirective(editorService, localizationService, editorState) { + function EditorHeaderDirective(editorService, localizationService, editorState, $rootScope) { function link(scope, $injector) { @@ -224,27 +224,9 @@ Use this directive to construct a header inside the main editor window. if (editorState.current) { //to do make work for user create/edit // to do make it work for user group create/ edit - // to do make it work for language edit/create - // to do make it work for log viewer - scope.isNew = editorState.current.id === 0 || - editorState.current.id === "0" || - editorState.current.id === -1 || - editorState.current.id === 0 || - editorState.current.id === "-1"; - - var localizeVars = [ - scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit", - "visuallyHiddenTexts_name", - scope.isNew ? "general_new" : "general_edit" - ]; - - if (scope.editorfor) { - localizeVars.push(scope.editorfor); - } - localizationService.localizeMany(localizeVars).then(function(data) { - setAccessibilityForEditor(data); - scope.loading = false; - }); + // to make it work for language edit/create + setAccessibilityForEditorState(); + scope.loading = false; } else { scope.loading = false; } @@ -283,59 +265,91 @@ Use this directive to construct a header inside the main editor window. editorService.iconPicker(iconPicker); }; - function setAccessibilityForEditor(data) { - - if (editorState.current) { - if (scope.nameLocked) { - scope.accessibility.a11yName = scope.name; - SetPageTitle(scope.name); - } else { - - scope.accessibility.a11yMessage = data[0]; - scope.accessibility.a11yName = data[1]; - var title = data[2] + ":"; - if (!scope.isNew) { - scope.accessibility.a11yMessage += " " + scope.name; - title += " " + scope.name; - } else { - var name = ""; - if (editorState.current.contentTypeName) { - name = editorState.current.contentTypeName; - } else if (scope.editorfor) { - name = data[3]; - } - if (name !== "") { - scope.accessibility.a11yMessage += " " + name; - scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName; - title += " " + name; - } - } - if (title !== data[2] + ":") { - SetPageTitle(title); - } - - } - scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage); - scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName); + function setAccessibilityForEditorState() { + var isNew = editorState.current.id === 0 || + editorState.current.id === "0" || + editorState.current.id === -1 || + editorState.current.id === 0 || + editorState.current.id === "-1"; + + var contentTypeName = ""; + if (editorState.current.contentTypeName) { + contentTypeName = editorState.current.contentTypeName; } - + + var setTitle = false; + if (scope.setpagetitle !== undefined) { + setTitle = scope.setpagetitle; + } + setAccessibilityHeaderDirective(isNew, scope.editorfor, scope.nameLocked, scope.name, contentTypeName, setTitle); } + function setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, entityName, contentTypeName, setTitle) { + + var localizeVars = [ + isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit", + "visuallyHiddenTexts_name", + isNew ? "general_new" : "general_edit" + ]; + + if (editorFor) { + localizeVars.push(editorFor); + } + localizationService.localizeMany(localizeVars).then(function(data) { + if (nameLocked) { + scope.accessibility.a11yName = entityName; + if (setTitle) { + SetPageTitle(entityName); + } + } else { + + scope.accessibility.a11yMessage = data[0]; + scope.accessibility.a11yName = data[1]; + var title = data[2] + ":"; + if (!isNew) { + scope.accessibility.a11yMessage += " " + entityName; + title += " " + entityName; + } else { + var name = ""; + if (contentTypeName) { + name = editorState.current.contentTypeName; + } else if (editorFor) { + name = data[3]; + } + if (name !== "") { + scope.accessibility.a11yMessage += " " + name; + scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName; + title += " " + name; + } + } + if (setTitle && title !== data[2] + ":") { + SetPageTitle(title); + } + + } + scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage); + scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName); + + }); + } + + + function isEmptyOrSpaces(str) { return str === null || str===undefined || str.trim ===''; } function SetPageTitle(title) { - var setTitle = false; - if (scope.setpagetitle !== undefined) { - setTitle = scope.setpagetitle; - } - if (setTitle) { scope.$emit("$changeTitle", title); - } } + + $rootScope.$on('$setAccessibleHeader', function (event, isNew, editorFor, nameLocked, name, contentTypeName, setTitle) { + setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, name, contentTypeName, setTitle); + }); } + + var directive = { transclude: true, restrict: 'E', diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index ecea3b1dba..19218d3d08 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -103,6 +103,7 @@ vm.changePasswordModel.config.allowManuallyChangingPassword = true; } + $scope.$emit("$setAccessibleHeader", false, "general_user", false, vm.user.name, "", true); vm.loading = false; }); }); From b80dfbf5fce0f03f299c105f2ea167a5c55aa7b0 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 14:01:50 +0100 Subject: [PATCH 44/96] Fix for tiny MCE when language is en-US (#8002) --- .../lib/tinymce/langs/en_US.js | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js new file mode 100644 index 0000000000..90eae85800 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js @@ -0,0 +1,261 @@ +tinymce.addI18n('en_US',{ +"Redo": "Redo", +"Undo": "Undo", +"Cut": "Cut", +"Copy": "Copy", +"Paste": "Paste", +"Select all": "Select all", +"New document": "New document", +"Ok": "Ok", +"Cancel": "Cancel", +"Visual aids": "Visual aids", +"Bold": "Bold", +"Italic": "Italic", +"Underline": "Underline", +"Strikethrough": "Strikethrough", +"Superscript": "Superscript", +"Subscript": "Subscript", +"Clear formatting": "Clear formatting", +"Align left": "Align left", +"Align center": "Align center", +"Align right": "Align right", +"Justify": "Justify", +"Bullet list": "Bullet list", +"Numbered list": "Numbered list", +"Decrease indent": "Decrease indent", +"Increase indent": "Increase indent", +"Close": "Close", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.", +"Headers": "Headers", +"Header 1": "Header 1", +"Header 2": "Header 2", +"Header 3": "Header 3", +"Header 4": "Header 4", +"Header 5": "Header 5", +"Header 6": "Header 6", +"Headings": "Headings", +"Heading 1": "Heading 1", +"Heading 2": "Heading 2", +"Heading 3": "Heading 3", +"Heading 4": "Heading 4", +"Heading 5": "Heading 5", +"Heading 6": "Heading 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraph", +"Blockquote": "Blockquote", +"Inline": "Inline", +"Blocks": "Blocks", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", +"Font Family": "Font Family", +"Font Sizes": "Font Sizes", +"Class": "Class", +"Browse for an image": "Browse for an image", +"OR": "OR", +"Drop an image here": "Drop an image here", +"Upload": "Upload", +"Block": "Blocks", +"Align": "Align", +"Default": "Default", +"Circle": "Circle", +"Disc": "Disc", +"Square": "Square", +"Lower Alpha": "Lower Alpha", +"Lower Greek": "Lower Greek", +"Lower Roman": "Lower Roman", +"Upper Alpha": "Upper Alpha", +"Upper Roman": "Upper Roman", +"Anchor": "Anchor", +"Name": "Name", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons, or underscores.", +"You have unsaved changes are you sure you want to navigate away?": "You have unsaved changes are you sure you want to navigate away?", +"Restore last draft": "Restore last draft", +"Special character": "Special character", +"Source code": "Source code", +"Insert\/Edit code sample": "Insert\/Edit code sample", +"Language": "Language", +"Code sample": "Code sample", +"Color": "color", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Left to right", +"Right to left": "Right to left", +"Emoticons": "Emoticons", +"Document properties": "Document properties", +"Title": "Title", +"Keywords": "Keywords", +"Description": "Description", +"Robots": "Robots", +"Author": "Author", +"Encoding": "Encoding", +"Fullscreen": "Fullscreen", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Handy Shortcuts", +"Horizontal line": "Horizontal line", +"Insert\/edit image": "Insert\/edit image", +"Image description": "Image description", +"Source": "Source", +"Dimensions": "Dimensions", +"Constrain proportions": "Constrain proportions", +"General": "General", +"Advanced": "Advanced", +"Style": "Style", +"Vertical space": "Vertical space", +"Horizontal space": "Horizontal space", +"Border": "Border", +"Insert image": "Insert image", +"Image": "Image", +"Image list": "Image list", +"Rotate counterclockwise": "Rotate counterclockwise", +"Rotate clockwise": "Rotate clockwise", +"Flip vertically": "Flip vertically", +"Flip horizontally": "Flip horizontally", +"Edit image": "Edit image", +"Image options": "Image options", +"Zoom in": "Zoom in", +"Zoom out": "Zoom out", +"Crop": "Crop", +"Resize": "Resize", +"Orientation": "Orientation", +"Brightness": "Brightness", +"Sharpen": "Sharpen", +"Contrast": "Contrast", +"Color levels": "color levels", +"Gamma": "Gamma", +"Invert": "Invert", +"Apply": "Apply", +"Back": "Back", +"Insert date\/time": "Insert date\/time", +"Date\/time": "Date\/time", +"Insert link": "Insert link", +"Insert\/edit link": "Insert\/edit link", +"Text to display": "Text to display", +"Url": "Url", +"Target": "Target", +"None": "None", +"New window": "New window", +"Remove link": "Remove link", +"Anchors": "Anchors", +"Link": "Link", +"Paste or type a link": "Paste or type a link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?", +"Link list": "Link list", +"Insert video": "Insert video", +"Insert\/edit video": "Insert\/edit video", +"Insert\/edit media": "Insert\/edit media", +"Alternative source": "Alternative source", +"Poster": "Poster", +"Paste your embed code below:": "Paste your embed code below:", +"Embed": "Embed", +"Media": "Media", +"Nonbreaking space": "Nonbreaking space", +"Page break": "Page break", +"Paste as text": "Paste as text", +"Preview": "Preview", +"Print": "Print", +"Save": "Save", +"Find": "Find", +"Replace with": "Replace with", +"Replace": "Replace", +"Replace all": "Replace all", +"Prev": "Prev", +"Next": "Next", +"Find and replace": "Find and replace", +"Could not find the specified string.": "Could not find the specified string.", +"Match case": "Match case", +"Whole words": "Whole words", +"Spellcheck": "Spellcheck", +"Ignore": "Ignore", +"Ignore all": "Ignore all", +"Finish": "Finish", +"Add to Dictionary": "Add to Dictionary", +"Insert table": "Insert table", +"Table properties": "Table properties", +"Delete table": "Delete table", +"Cell": "Cell", +"Row": "Row", +"Column": "Column", +"Cell properties": "Cell properties", +"Merge cells": "Merge cells", +"Split cell": "Split cell", +"Insert row before": "Insert row before", +"Insert row after": "Insert row after", +"Delete row": "Delete row", +"Row properties": "Row properties", +"Cut row": "Cut row", +"Copy row": "Copy row", +"Paste row before": "Paste row before", +"Paste row after": "Paste row after", +"Insert column before": "Insert column before", +"Insert column after": "Insert column after", +"Delete column": "Delete column", +"Cols": "Cols", +"Rows": "Rows", +"Width": "Width", +"Height": "Height", +"Cell spacing": "Cell spacing", +"Cell padding": "Cell padding", +"Caption": "Caption", +"Left": "Left", +"Center": "Center", +"Right": "Right", +"Cell type": "Cell type", +"Scope": "Scope", +"Alignment": "Alignment", +"H Align": "H Align", +"V Align": "V Align", +"Top": "Top", +"Middle": "Middle", +"Bottom": "Bottom", +"Header cell": "Header cell", +"Row group": "Row group", +"Column group": "Column group", +"Row type": "Row type", +"Header": "Header", +"Body": "Body", +"Footer": "Footer", +"Border color": "Border color", +"Insert template": "Insert template", +"Templates": "Templates", +"Template": "Template", +"Text color": "Text color", +"Background color": "Background color", +"Custom...": "Custom...", +"Custom color": "Custom color", +"No color": "No color", +"Table of Contents": "Table of Contents", +"Show blocks": "Show blocks", +"Show invisible characters": "Show invisible characters", +"Words: {0}": "Words: {0}", +"{0} words": "{0} words", +"File": "File", +"Edit": "Edit", +"Insert": "Insert", +"View": "View", +"Format": "Format", +"Table": "Table", +"Tools": "Tools", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" +}); \ No newline at end of file From e2604103a32ec01ffd722a3b51ceb23a855a2f83 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 14:10:16 +0100 Subject: [PATCH 45/96] v8: Accessibility Create User Page Title (#7104) --- .../src/views/users/views/users/users.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 102efae702..2217628872 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -814,6 +814,7 @@ vm.newUser.message = ""; // clear button state vm.page.createButtonState = "init"; + $scope.$emit("$setAccessibleHeader", true, "general_user", false, "", "", true); } init(); From 6da69f1e429e4666592a81c615e9e95aa5ad092e Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 26 May 2020 14:34:30 +0100 Subject: [PATCH 46/96] V8: Accessibility changes for login screen (#5800) --- .../application/umblogin.directive.js | 124 +++++++++--------- .../src/less/pages/login.less | 5 +- .../components/application/umb-login.html | 6 +- 3 files changed, 69 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index ec8da898ad..6dd740e08b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -63,10 +63,14 @@ vm.labels = {}; localizationService.localizeMany([ vm.usernameIsEmail ? "general_email" : "general_username", - vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint"] + vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint", + vm.usernameIsEmail ? "placeholders_emptyEmail" : "placeholders_emptyUsername", + "placeholders_emptyPassword"] ).then(function (data) { vm.labels.usernameLabel = data[0]; vm.labels.usernamePlaceholder = data[1]; + vm.labels.usernameError = data[2]; + vm.labels.passwordError = data[3]; }); vm.twoFactor = {}; @@ -193,72 +197,70 @@ } function loginSubmit() { - - // make sure that we are returning to the login view. - vm.view = "login"; - - // TODO: Do validation properly like in the invite password update + + if (formHelper.submitForm({ scope: $scope })) { + //if the login and password are not empty we need to automatically + // validate them - this is because if there are validation errors on the server + // then the user has to change both username & password to resubmit which isn't ideal, + // so if they're not empty, we'll just make sure to set them to valid. + if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); + } + + if (vm.loginForm.$invalid) { + SetTitle(); + return; + } + + // make sure that we are returning to the login view. + vm.view = "login"; - //if the login and password are not empty we need to automatically - // validate them - this is because if there are validation errors on the server - // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty, we'll just make sure to set them to valid. - if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } + vm.loginStates.submitButton = "busy"; - if (vm.loginForm.$invalid) { - SetTitle(); - return; - } + userService.authenticate(vm.login, vm.password) + .then(function(data) { + vm.loginStates.submitButton = "success"; + userService._retryRequestQueue(true); + if (vm.onLogin) { + vm.onLogin(); + } + }, + function(reason) { - vm.loginStates.submitButton = "busy"; + //is Two Factor required? + if (reason.status === 402) { + vm.errorMsg = "Additional authentication required"; + show2FALoginDialog(reason.data.twoFactorView); + } else { + vm.loginStates.submitButton = "error"; + vm.errorMsg = reason.errorMsg; - userService.authenticate(vm.login, vm.password) - .then(function (data) { - vm.loginStates.submitButton = "success"; - userService._retryRequestQueue(true); - if(vm.onLogin) { - vm.onLogin(); + //set the form inputs to invalid + vm.loginForm.username.$setValidity("auth", false); + vm.loginForm.password.$setValidity("auth", false); + } + + userService._retryRequestQueue(); + + }); + + //setup a watch for both of the model values changing, if they change + // while the form is invalid, then revalidate them so that the form can + // be submitted again. + vm.loginForm.username.$viewChangeListeners.push(function() { + if (vm.loginForm.$invalid) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); } - }, - function (reason) { - - //is Two Factor required? - if (reason.status === 402) { - vm.errorMsg = "Additional authentication required"; - show2FALoginDialog(reason.data.twoFactorView); - } - else { - vm.loginStates.submitButton = "error"; - vm.errorMsg = reason.errorMsg; - - //set the form inputs to invalid - vm.loginForm.username.$setValidity("auth", false); - vm.loginForm.password.$setValidity("auth", false); - } - - userService._retryRequestQueue(); - }); - - //setup a watch for both of the model values changing, if they change - // while the form is invalid, then revalidate them so that the form can - // be submitted again. - vm.loginForm.username.$viewChangeListeners.push(function () { - if (vm.loginForm.$invalid) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } - }); - vm.loginForm.password.$viewChangeListeners.push(function () { - if (vm.loginForm.$invalid) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } - }); - SetTitle(); + vm.loginForm.password.$viewChangeListeners.push(function() { + if (vm.loginForm.$invalid) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); + } + }); + } } function requestPasswordResetSubmit(email) { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index e36acdc273..818b1d84d1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -123,6 +123,7 @@ position: relative; text-align: right; user-select: none; + margin-left: auto; a { opacity: .5; @@ -134,8 +135,8 @@ .password-text { background-repeat: no-repeat; background-size: 18px; - background-position: left center; - padding-left: 26px; + background-position: 0px 1px; + padding-left: 24px; &.show { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E"); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index 2e81395643..098d69960d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -149,16 +149,16 @@
- +
- +
- +
Show password From e434b01386e52650d17cab4fe51aa2213716c287 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 26 May 2020 14:43:57 +0100 Subject: [PATCH 47/96] Adds in NC Property Component to deal with creating/adding missing keys --- .../Compose/NestedContentPropertyComponent.cs | 174 ++++++++++++++++++ .../Compose/NestedContentPropertyComposer.cs | 9 + src/Umbraco.Web/Umbraco.Web.csproj | 2 + 3 files changed, 185 insertions(+) create mode 100644 src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs create mode 100644 src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs new file mode 100644 index 0000000000..28c2a288f8 --- /dev/null +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -0,0 +1,174 @@ +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.PropertyEditors; + +namespace Umbraco.Web.Compose +{ + public class NestedContentPropertyComponent : IComponent + { + public void Initialize() + { + ContentService.Copying += ContentService_Copying; + ContentService.Saving += ContentService_Saving; + ContentService.Publishing += ContentService_Publishing; + } + + private void ContentService_Copying(IContentService sender, CopyEventArgs e) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + + // Each NC Property on a doctype + foreach (var nestedContentProp in nestedContentProps) + { + // A NC Prop may have one or more values due to cultures + var propVals = nestedContentProp.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested NC's + var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue.ToString()); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested NC's + var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue.ToString()); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + + private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + { + // One or more content nodes could be saved in a bulk publish + foreach(var entity in e.SavedEntities) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + + // Each NC Property on a doctype + foreach (var nestedContentProp in nestedContentProps) + { + // A NC Prop may have one or more values due to cultures + var propVals = nestedContentProp.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested NC's + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested NC's + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + } + + private void ContentService_Publishing(IContentService sender, ContentPublishingEventArgs e) + { + // One or more content nodes could be saved in a bulk publish + foreach (var entity in e.PublishedEntities) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + + // Each NC Property on a doctype + foreach (var nestedContentProp in nestedContentProps) + { + // A NC Prop may have one or more values due to cultures + var propVals = nestedContentProp.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested NC's + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested NC's + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + } + + public void Terminate() + { + ContentService.Copying -= ContentService_Copying; + ContentService.Saving -= ContentService_Saving; + ContentService.Publishing -= ContentService_Publishing; + } + + private string CreateNewNestedContentKeys(string ncJson) + { + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) + var ncItems = JArray.Parse(ncJson); + + // NC prop contains one or more items/rows of things + foreach (var nestedContentItem in ncItems.Children()) + { + foreach (var ncItemProp in nestedContentItem.Properties()) + { + if(ncItemProp.Name.ToLowerInvariant() == "key") + ncItemProp.Value = Guid.NewGuid().ToString(); + + // No need to check this property for JSON - as this is a JSON prop we know + // That onyl contains the string of the doctype alias used as the NC item + if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) + continue; + + // As we don't know what properties in the JSON may contain the nested NC + // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop + if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + { + // Recurse & update this JSON property + ncItemProp.Value = CreateNewNestedContentKeys(ncItemProp.Value.ToString()); + } + } + } + + return ncItems.ToString(); + } + + private string CreateMissingNestedContentKeys(string ncJson) + { + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) + var ncItems = JArray.Parse(ncJson); + + // NC prop contains one or more items/rows of things + foreach (var nestedContentItem in ncItems.Children()) + { + var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); + if(ncKeyProp == null) + { + nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); + } + + foreach (var ncItemProp in nestedContentItem.Properties()) + { + // No need to check this property for JSON (Its the key OR ncContentTypeAlias) which has no JSON + if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey || ncItemProp.Name.ToLowerInvariant() == "key") + continue; + + // As we don't know what properties in the JSON may contain the nested NC + // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop + if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + { + // Recurse & update this JSON property + ncItemProp.Value = CreateMissingNestedContentKeys(ncItemProp.Value.ToString()); + } + } + } + + return ncItems.ToString(); + } + } +} diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs new file mode 100644 index 0000000000..4c9d9dee1c --- /dev/null +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs @@ -0,0 +1,9 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Compose +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class NestedContentPropertyComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e39687bed8..c06ec574c5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -129,6 +129,7 @@ + @@ -237,6 +238,7 @@ + From 32a0c3b075f8230dd00142aa7ebc161a67faffaf Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 17:45:39 +0200 Subject: [PATCH 48/96] "Actions" drop down options do not give context to the user (#6301) --- .../views/components/editor/umb-editor-menu.html | 9 ++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 10 +++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 10 +++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 14 +++++++++++++- src/Umbraco.Web/Models/Trees/MenuItem.cs | 9 ++++++++- src/Umbraco.Web/Models/Trees/MenuItemList.cs | 11 ++++++++--- src/Umbraco.Web/Trees/ContentTreeController.cs | 2 +- 7 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html index fe90fef07a..ceae7c3bb9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html @@ -15,7 +15,14 @@ diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 7cead86114..a876b1a6b7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -247,7 +247,7 @@ Dette dokument er udgivet, men dets URL kan ikke dirigeres Udgiv Udgivet - Udgivet (Afventende ændringer) + Udgivet (Ventede ændringer) Udgivelsesstatus Udgiv med undersider for at udgive %0% og alle sider under og dermed gøre deres indhold offentligt tilgængelige.]]> Udgiv med undersider for at udgive de valgte sprog og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]> @@ -426,6 +426,7 @@ Internt link: Ved lokalt link, indsæt da en "#" foran linket Åben i nyt vindue? + Makroindstillinger Denne makro har ingen egenskaber du kan redigere Indsæt tekst Rediger rettigheder for @@ -663,6 +664,7 @@ Ikon Id Importer + Inkludér undermapper i søgning Søg kun i denne mappe Info Indre margen @@ -1749,6 +1751,12 @@ Mange hilsner fra Umbraco robotten Åben backoffice søgning Åben/Luk backoffice hjælp Åben/Luk dine profil indstillinger + Tilføj domæne på %0% + Opret ny node under %0% + Opsæt offentlig adgang på %0% + Opsæt rettigheder på %0% + Juster soterings rækkefølgen for %0% + Opret indholds skabelon baseret på %0% Aktivt sprog Skift sprog til Opret ny mappe diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index e764b5591e..e4f9ed7b91 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -141,6 +141,7 @@ Save and send for approval Save list view Schedule + Preview Save and preview Preview is disabled because there's no template assigned Choose style @@ -686,6 +687,7 @@ Icon Id Import + Include subfolders in search Search only this folder Info Inner margin @@ -2228,6 +2230,12 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Setup Culture and Hostnames for %0% + Create new node under %0% + Setup Public access on %0% + Setup Permissions on %0% + Change sort order for %0% + Create Content Template based on %0% Open context menu for Current language Switch language to @@ -2243,7 +2251,7 @@ To manage your website, simply open the Umbraco back office and start adding con Create Edit Name - Add new row + Add new row View more options diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index e1b1df2c34..5fc037d8b3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -140,6 +140,7 @@ Send for approval Save list view Schedule + Preview Save and preview Preview is disabled because there's no template assigned Choose style @@ -369,7 +370,8 @@ Document Types within the Settings section, by changing the Allow as root option under Permissions.]]> Media Types Types within the Settings section, by editing the Allowed child node types under Permissions.]]> The selected media in the tree doesn't allow for any other media to be created below it. - Edit permissions for this media type Document Type without a template + Edit permissions for this media type + Document Type without a template New folder New data type New JavaScript file @@ -570,6 +572,9 @@ #value or ?key=value Enter alias... Generating alias... + Create item + Edit + Name Create custom list view @@ -690,6 +695,7 @@ Icon Id Import + Include subfolders in search Search only this folder Info Inner margin @@ -2244,6 +2250,12 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Setup Culture and Hostnames for %0% + Create new node under %0% + Setup Public access on %0% + Setup Permissions on %0% + Change sort order for %0% + Create Content Template based on %0% Open context menu for Current language Switch language to diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index 9d4c76eea1..094c6b24ff 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Actions; +using System.Threading; namespace Umbraco.Web.Models.Trees { @@ -28,12 +29,15 @@ namespace Umbraco.Web.Models.Trees Name = name; } - public MenuItem(string alias, ILocalizedTextService textService) : this() { + var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); + values.TryGetValue($"visuallyHiddenTexts/{alias}_description", out var textDescription); + Alias = alias; Name = textService.Localize($"actions/{Alias}"); + TextDescription = textDescription; } /// @@ -74,6 +78,9 @@ namespace Umbraco.Web.Models.Trees [Required] public string Alias { get; set; } + [DataMember(Name = "textDescription")] + public string TextDescription { get; set; } + /// /// Ensures a menu separator will exist before this menu item /// diff --git a/src/Umbraco.Web/Models/Trees/MenuItemList.cs b/src/Umbraco.Web/Models/Trees/MenuItemList.cs index 1df486ebdf..4aaf0632ab 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemList.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemList.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Actions; @@ -75,7 +76,7 @@ namespace Umbraco.Web.Models.Trees } return null; } - + internal MenuItem CreateMenuItem(string name, bool hasSeparator = false, bool opensDialog = false) where T : IAction { @@ -96,14 +97,18 @@ namespace Umbraco.Web.Models.Trees var item = Current.Actions.GetAction(); if (item == null) return null; + var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); + values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription); + var menuItem = new MenuItem(item, textService.Localize($"actions/{item.Alias}")) { SeparatorBefore = hasSeparator, - OpensDialog = opensDialog + OpensDialog = opensDialog, + TextDescription = textDescription, }; return menuItem; } - + } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 85ff30487d..663af43643 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -323,7 +323,7 @@ namespace Umbraco.Web.Trees private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool opensDialog = false) where TAction : IAction { - var menuItem = menu.Items.Add(Services.TextService.Localize("actions", _actions.GetAction().Alias), hasSeparator, opensDialog); + var menuItem = menu.Items.Add(Services.TextService, hasSeparator, opensDialog); } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) From cc04a1c8fe9d7d3c639d744a618bfd9cd631a41a Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 18:01:17 +0200 Subject: [PATCH 49/96] Content templates dashboard: Add translations and move styles external (#6888) --- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../less/dashboards/content-templates.less | 22 +++++++++++ .../src/views/contentblueprints/intro.html | 37 ++++++++++++------- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 17 +++++++++ .../Umbraco/config/lang/en_us.xml | 17 +++++++++ 5 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 174f9f41d7..f0d7c6f1e1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -227,6 +227,7 @@ @import "dashboards/umbraco-forms.less"; @import "dashboards/examine-management.less"; @import "dashboards/healthcheck.less"; +@import "dashboards/content-templates.less"; @import "dashboards/nucache.less"; @import "typeahead.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less new file mode 100644 index 0000000000..9966fc97e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less @@ -0,0 +1,22 @@ +.content-templates-dashboard{ + p{ + line-height: 1.6em; + margin-bottom: 30px; + + &:last-child{ + margin-bottom: 0; + } + } + + ul{ + margin-bottom: 15px; + } + + li{ + margin-bottom: 5px; + + &:last-child{ + margin-bottom: 0; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html index ce423225f6..66695ace91 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html @@ -12,21 +12,32 @@ - -

What are Content Templates?

-

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

+ +

+ What are Content Templates? +

+

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

-

How do I create a Content Template?

-

There are two ways to create a Content Template:

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

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

+

+ How do I create a Content Template? +

+ +

There are two ways to create a Content Template:

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

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

+
-

How do I manage Content Templates

-

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

+

+ How do I manage Content Templates? +

+

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

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

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

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

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

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

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

+ ]]> +
+ How do I manage Content Templates? + You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete it. + From 19f16c3b1fefadfd71d2de523fd13561ffeb69aa Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 18:02:21 +0200 Subject: [PATCH 50/96] Theme backoffice docs for v8 to clearly differentiate it from the v7 docs (#8010) --- src/Umbraco.Web.UI.Docs/gulpfile.js | 4 +-- src/Umbraco.Web.UI.Docs/umb-docs.css | 39 +++++++++++++++++----------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Docs/gulpfile.js b/src/Umbraco.Web.UI.Docs/gulpfile.js index 789e2b7845..a3e596ecad 100644 --- a/src/Umbraco.Web.UI.Docs/gulpfile.js +++ b/src/Umbraco.Web.UI.Docs/gulpfile.js @@ -16,7 +16,7 @@ gulp.task('docs', [], function (cb) { var options = { html5Mode: false, startPage: '/api', - title: "Umbraco Backoffice UI API Documentation", + title: "Umbraco 8 Backoffice UI API Documentation", dest: './api', styles: ['./umb-docs.css'], image: "https://our.umbraco.com/assets/images/logo.svg" @@ -53,4 +53,4 @@ gulp.task('open:docs', function (cb) { gulp.src(__filename) .pipe(open(options)); cb(); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Docs/umb-docs.css b/src/Umbraco.Web.UI.Docs/umb-docs.css index 0f2e3e7f74..b748a0a609 100644 --- a/src/Umbraco.Web.UI.Docs/umb-docs.css +++ b/src/Umbraco.Web.UI.Docs/umb-docs.css @@ -4,7 +4,7 @@ html { } body { font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif; - + } .container, .navbar-static-top .container, .navbar-fixed-top .container, .navbar-fixed-bottom .container { @@ -33,37 +33,43 @@ body { font-family: inherit; } +.navbar .container{ + min-height: inherit; + display: flex; + align-items: center; +} + .navbar .brand { display: block; - float: left; - padding: 10px 20px 10px; - margin-left: -20px; - font-size: 20px; - font-weight: 200; - color: rgba(0,0,0,.8); + color: white; text-shadow: none; } .navbar-fixed-top .navbar-inner { min-height: 50px; - background: #a3db78; + background: #3544b1; } .form-search .well { - background-color: #f5fbf1; + background-color: #f7f7f7; } .form-search > ul.nav > li.module { - background-color: #daf0c9; + background-color: #3544b1; } -.breadcrumb { - background-color: #f5fbf1; +.form-search > ul.nav > li.module a { + color: white; } -a { - color: #f36f21; +.form-search > ul.nav > li.section { + background-color: #ccc; } + +.breadcrumb { + background-color: #f7f7f7; +} + a:hover { text-decoration: none; color: rgba(0,0,0,.8); @@ -87,9 +93,12 @@ a:hover { color: #000; } +.form-search > ul.nav > li.module > a:hover{ + color: #fff; +} + .header img { width: 50px; - margin-top: 5px; } .content .methods code { From 14ad2bd27cae73f2dcf41d8f810ec1c50975b032 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 26 May 2020 18:15:18 +0200 Subject: [PATCH 51/96] Convert span to button, add missing label, hide icons for screen readers (#8011) --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 2 +- .../src/views/propertyeditors/datepicker/datepicker.html | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 764b73c593..b5870b8dce 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -863,11 +863,11 @@ .bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;} .umb-datepicker .input-append .add-on{cursor: pointer;} .umb-datepicker .input-append .on-top { + border: 0 none; position: absolute; margin-left: -31px; margin-top: 1px; display: inline-block; - height: 22px; padding: 5px 6px 3px 6px; font-size: @baseFontSize; font-weight: normal; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index e697dc56a5..b35663c3df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -20,10 +20,11 @@ ng-required="model.validation.mandatory" val-server="value" class="datepickerinput"> - - - - + +
From 62b0886afe8b6bbc394709be51376c446b3961be Mon Sep 17 00:00:00 2001 From: BatJan Date: Sat, 16 May 2020 20:23:44 +0200 Subject: [PATCH 52/96] Fix label alignment --- .../src/less/components/umb-form-check.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index a52f81b92a..9a3760444d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -18,6 +18,8 @@ label.umb-form-check--checkbox{ } .umb-form-check__info { margin-left:20px; + position: relative; + top: 3px; } From 30900c59aef9d160136f559ac230ef060572b40b Mon Sep 17 00:00:00 2001 From: BatJan Date: Sat, 16 May 2020 21:00:41 +0200 Subject: [PATCH 53/96] Add disable-dirty-check option --- .../components/forms/umbcheckbox.directive.js | 4 +++- .../views/components/forms/umb-checkbox.html | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js index 9a9d6d4a76..389aec2044 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -32,6 +32,7 @@ @param {boolean} required Set the checkbox to be required. @param {callback} onChange Callback when the value of the checkbox change by interaction. @param {string} cssClass Set a css class modifier +@param {boolean} disableDirtyCheck Disable checking if the model is dirty **/ @@ -84,7 +85,8 @@ required: "<", onChange: "&?", cssClass: "@?", - iconClass: "@?" + iconClass: "@?", + disableDirtyCheck: "=?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html index ba5adf199a..5f83d9ae53 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html @@ -1,7 +1,7 @@
- + ng-change="vm.change()" + no-dirty-check /> + +
-
{{ name }}
+
{{ name }}
{{ description }}
From 9df6a8bd6af17ecfb2aa2e0a7107798236703db8 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 27 May 2020 14:59:15 +0000 Subject: [PATCH 56/96] Bugfix: Adds new confirmation email marketing step (#8091) --- .../confirm/confirm.controller.js | 15 +++++++++++++++ .../tours/umbEmailMarketing/confirm/confirm.html | 15 +++++++++++++++ .../umbEmailMarketing/emails/emails.controller.js | 5 +---- .../config/BackOfficeTours/getting-started.json | 4 ++++ 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js new file mode 100644 index 0000000000..ef4f5ce1ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.controller.js @@ -0,0 +1,15 @@ +(function () { + "use strict"; + + function ConfirmController($scope, userService) { + + var vm = this; + vm.userEmailAddress = ""; + + userService.getCurrentUser().then(function(user){ + vm.userEmailAddress = user.email; + }); + } + + angular.module("umbraco").controller("Umbraco.Tours.UmbEmailMarketing.ConfirmController", ConfirmController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html new file mode 100644 index 0000000000..34acb194e3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/confirm/confirm.html @@ -0,0 +1,15 @@ +
+ + + + +

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

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

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

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

", "view": "emails", "type": "promotion" + }, + { + "title": "Thank you for subscribing to our mailing list", + "view": "confirm" } ] }, From ff30a2769733db85e9f48dfcf8ee7523adf06e5a Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 28 May 2020 08:41:32 +0200 Subject: [PATCH 57/96] adding missing nullchecks. --- .../Compose/NestedContentPropertyComponent.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index 28c2a288f8..bff821358d 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -34,11 +34,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue.ToString()); + var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue?.ToString()); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue.ToString()); + var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue?.ToString()); cultureVal.EditedValue = updatedEditedVal; } } @@ -61,11 +61,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); cultureVal.EditedValue = updatedEditedVal; } } @@ -89,11 +89,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); cultureVal.EditedValue = updatedEditedVal; } } @@ -109,6 +109,9 @@ namespace Umbraco.Web.Compose private string CreateNewNestedContentKeys(string ncJson) { + if (string.IsNullOrWhiteSpace(ncJson)) + return ncJson; + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) var ncItems = JArray.Parse(ncJson); @@ -140,6 +143,9 @@ namespace Umbraco.Web.Compose private string CreateMissingNestedContentKeys(string ncJson) { + if (string.IsNullOrWhiteSpace(ncJson)) + return ncJson; + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) var ncItems = JArray.Parse(ncJson); From c9ab2ca9ef8b19d3a0f1d408b2601193504cc9cb Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 28 May 2020 11:45:43 +0100 Subject: [PATCH 58/96] Use InvariantEquals --- src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index bff821358d..e41c14d94b 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -120,7 +120,7 @@ namespace Umbraco.Web.Compose { foreach (var ncItemProp in nestedContentItem.Properties()) { - if(ncItemProp.Name.ToLowerInvariant() == "key") + if(ncItemProp.Name.InvariantEquals("key")) ncItemProp.Value = Guid.NewGuid().ToString(); // No need to check this property for JSON - as this is a JSON prop we know From c81268995ceaa352acbe55359783925d2be19f23 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 28 May 2020 12:06:04 +0100 Subject: [PATCH 59/96] Remove code duplication --- .../Compose/NestedContentPropertyComponent.cs | 77 +++++++------------ 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index e41c14d94b..28b503fc9b 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -34,11 +34,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue?.ToString()); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), false); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue?.ToString()); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), false); cultureVal.EditedValue = updatedEditedVal; } } @@ -61,11 +61,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); cultureVal.EditedValue = updatedEditedVal; } } @@ -89,11 +89,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); cultureVal.EditedValue = updatedEditedVal; } } @@ -107,21 +107,36 @@ namespace Umbraco.Web.Compose ContentService.Publishing -= ContentService_Publishing; } - private string CreateNewNestedContentKeys(string ncJson) + private string CreateNestedContentKeys(string ncJson, bool onlyMissingKeys) { if (string.IsNullOrWhiteSpace(ncJson)) return ncJson; - // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) + // Convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) var ncItems = JArray.Parse(ncJson); // NC prop contains one or more items/rows of things foreach (var nestedContentItem in ncItems.Children()) { + // If saving/publishing - we only generate keys for NC items that are missing + if (onlyMissingKeys) + { + var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); + if (ncKeyProp == null) + { + nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); + } + } + + foreach (var ncItemProp in nestedContentItem.Properties()) { - if(ncItemProp.Name.InvariantEquals("key")) - ncItemProp.Value = Guid.NewGuid().ToString(); + if(onlyMissingKeys == false) + { + // Only when copying a node - we generate new keys for all NC items + if (ncItemProp.Name.InvariantEquals("key")) + ncItemProp.Value = Guid.NewGuid().ToString(); + } // No need to check this property for JSON - as this is a JSON prop we know // That onyl contains the string of the doctype alias used as the NC item @@ -130,46 +145,12 @@ namespace Umbraco.Web.Compose // As we don't know what properties in the JSON may contain the nested NC // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop - if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + var ncItemPropVal = ncItemProp.Value?.ToString(); + + if (ncItemPropVal.DetectIsJson() && ncItemPropVal.Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) { // Recurse & update this JSON property - ncItemProp.Value = CreateNewNestedContentKeys(ncItemProp.Value.ToString()); - } - } - } - - return ncItems.ToString(); - } - - private string CreateMissingNestedContentKeys(string ncJson) - { - if (string.IsNullOrWhiteSpace(ncJson)) - return ncJson; - - // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) - var ncItems = JArray.Parse(ncJson); - - // NC prop contains one or more items/rows of things - foreach (var nestedContentItem in ncItems.Children()) - { - var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); - if(ncKeyProp == null) - { - nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); - } - - foreach (var ncItemProp in nestedContentItem.Properties()) - { - // No need to check this property for JSON (Its the key OR ncContentTypeAlias) which has no JSON - if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey || ncItemProp.Name.ToLowerInvariant() == "key") - continue; - - // As we don't know what properties in the JSON may contain the nested NC - // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop - if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) - { - // Recurse & update this JSON property - ncItemProp.Value = CreateMissingNestedContentKeys(ncItemProp.Value.ToString()); + ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); } } } From 732d574ec2170a1c53801827dc7271b1ee6029f5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 28 May 2020 21:35:39 +0100 Subject: [PATCH 60/96] Tidy up & better checking for NC JSON with JSONPath & SelectTokens() Less code duplication. No need for Publishing event as Saving event is called before publishing --- .../Compose/NestedContentPropertyComponent.cs | 129 +++++++----------- 1 file changed, 48 insertions(+), 81 deletions(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index 28b503fc9b..6e4fdb386e 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; @@ -17,7 +18,6 @@ namespace Umbraco.Web.Compose { ContentService.Copying += ContentService_Copying; ContentService.Saving += ContentService_Saving; - ContentService.Publishing += ContentService_Publishing; } private void ContentService_Copying(IContentService sender, CopyEventArgs e) @@ -25,7 +25,29 @@ namespace Umbraco.Web.Compose // When a content node contains nested content property // Check if the copied node contains a nested content var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + UpdateNestedContentProperties(nestedContentProps, false); + } + private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + { + // One or more content nodes could be saved in a bulk publish + foreach (var entity in e.SavedEntities) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + UpdateNestedContentProperties(nestedContentProps, true); + } + } + + public void Terminate() + { + ContentService.Copying -= ContentService_Copying; + ContentService.Saving -= ContentService_Saving; + } + + private void UpdateNestedContentProperties(IEnumerable nestedContentProps, bool onlyMissingKeys) + { // Each NC Property on a doctype foreach (var nestedContentProp in nestedContentProps) { @@ -34,104 +56,40 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), false); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), false); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), onlyMissingKeys); cultureVal.EditedValue = updatedEditedVal; } } } - private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) { - // One or more content nodes could be saved in a bulk publish - foreach(var entity in e.SavedEntities) - { - // When a content node contains nested content property - // Check if the copied node contains a nested content - var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + if (string.IsNullOrWhiteSpace(rawJson)) + return rawJson; - // Each NC Property on a doctype - foreach (var nestedContentProp in nestedContentProps) - { - // A NC Prop may have one or more values due to cultures - var propVals = nestedContentProp.Values; - foreach (var cultureVal in propVals) - { - // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); - cultureVal.PublishedValue = updatedPublishedVal; - - // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); - cultureVal.EditedValue = updatedEditedVal; - } - } - } - } - - private void ContentService_Publishing(IContentService sender, ContentPublishingEventArgs e) - { - // One or more content nodes could be saved in a bulk publish - foreach (var entity in e.PublishedEntities) - { - // When a content node contains nested content property - // Check if the copied node contains a nested content - var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); - - // Each NC Property on a doctype - foreach (var nestedContentProp in nestedContentProps) - { - // A NC Prop may have one or more values due to cultures - var propVals = nestedContentProp.Values; - foreach (var cultureVal in propVals) - { - // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); - cultureVal.PublishedValue = updatedPublishedVal; - - // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); - cultureVal.EditedValue = updatedEditedVal; - } - } - } - } - - public void Terminate() - { - ContentService.Copying -= ContentService_Copying; - ContentService.Saving -= ContentService_Saving; - ContentService.Publishing -= ContentService_Publishing; - } - - private string CreateNestedContentKeys(string ncJson, bool onlyMissingKeys) - { - if (string.IsNullOrWhiteSpace(ncJson)) - return ncJson; - - // Convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) - var ncItems = JArray.Parse(ncJson); + // Parse JSON + var ncJson = JToken.Parse(rawJson); // NC prop contains one or more items/rows of things - foreach (var nestedContentItem in ncItems.Children()) + foreach (var nestedContentItem in ncJson.Children()) { // If saving/publishing - we only generate keys for NC items that are missing if (onlyMissingKeys) { - var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); + var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.InvariantEquals("key")); if (ncKeyProp == null) { nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); } } - foreach (var ncItemProp in nestedContentItem.Properties()) { - if(onlyMissingKeys == false) + if (onlyMissingKeys == false) { // Only when copying a node - we generate new keys for all NC items if (ncItemProp.Name.InvariantEquals("key")) @@ -139,23 +97,32 @@ namespace Umbraco.Web.Compose } // No need to check this property for JSON - as this is a JSON prop we know - // That onyl contains the string of the doctype alias used as the NC item + // That only contains the string of the doctype alias used as the NC item if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) continue; - // As we don't know what properties in the JSON may contain the nested NC + // As we don't know what properties in the JSON may contain other complex editors or deep nested NC // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop var ncItemPropVal = ncItemProp.Value?.ToString(); - if (ncItemPropVal.DetectIsJson() && ncItemPropVal.Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + if (ncItemPropVal.DetectIsJson()) { - // Recurse & update this JSON property - ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); + // Parse the nested JSON (complex editor) + var complexEditorJson = JToken.Parse(ncItemPropVal); + + // Verify the complex editor is nested content (Will have one or more ncContentTypeAlias) + // One for each NC item/row + var hasNestedContentJsonProp = complexEditorJson.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false); + if (hasNestedContentJsonProp.Count() > 0) + { + // Recurse & update this JSON property + ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); + } } } } - return ncItems.ToString(); + return ncJson.ToString(); } } } From 2565a626cb60cf108e3b254e50c2d02e1935dea1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 29 May 2020 13:01:56 +1000 Subject: [PATCH 61/96] Fixes NC key replacement and adds tests --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Compose/NestedContentPropertyComponent.cs | 86 +++++++++---------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3c359cdde8..731dc05363 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -148,6 +148,7 @@ + diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index 6e4fdb386e..5794a2734e 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -66,63 +66,61 @@ namespace Umbraco.Web.Compose } } - private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) + + // internal for tests + internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func createGuid = null) { - if (string.IsNullOrWhiteSpace(rawJson)) + // used so we can test nicely + if (createGuid == null) + createGuid = () => Guid.NewGuid(); + + if (string.IsNullOrWhiteSpace(rawJson) || !rawJson.DetectIsJson()) return rawJson; - // Parse JSON - var ncJson = JToken.Parse(rawJson); + // Parse JSON + var complexEditorValue = JToken.Parse(rawJson); - // NC prop contains one or more items/rows of things - foreach (var nestedContentItem in ncJson.Children()) + UpdateNestedContentKeysRecursively(complexEditorValue, onlyMissingKeys, createGuid); + + return complexEditorValue.ToString(); + } + + private void UpdateNestedContentKeysRecursively(JToken json, bool onlyMissingKeys, Func createGuid) + { + // check if this is NC + var isNestedContent = json.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false).Any(); + + // select all values (flatten) + var allProperties = json.SelectTokens("$..*").OfType().Select(x => x.Parent as JProperty).WhereNotNull().ToList(); + foreach (var prop in allProperties) { - // If saving/publishing - we only generate keys for NC items that are missing - if (onlyMissingKeys) + if (prop.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) { - var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.InvariantEquals("key")); - if (ncKeyProp == null) + // get it's sibling 'key' property + var ncKeyVal = prop.Parent["key"] as JValue; + // TODO: This bool seems odd, if the key is null, shouldn't we fill it in regardless of onlyMissingKeys? + if ((onlyMissingKeys && ncKeyVal == null) || (!onlyMissingKeys && ncKeyVal != null)) { - nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); - } + // create or replace + prop.Parent["key"] = createGuid().ToString(); + } } - - foreach (var ncItemProp in nestedContentItem.Properties()) + else if (!isNestedContent || prop.Name != "key") { - if (onlyMissingKeys == false) + // this is an arbitrary property that could contain a nested complex editor + var propVal = prop.Value?.ToString(); + // check if this might contain a nested NC + if (!propVal.IsNullOrWhiteSpace() && propVal.DetectIsJson() && propVal.InvariantContains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) { - // Only when copying a node - we generate new keys for all NC items - if (ncItemProp.Name.InvariantEquals("key")) - ncItemProp.Value = Guid.NewGuid().ToString(); - } - - // No need to check this property for JSON - as this is a JSON prop we know - // That only contains the string of the doctype alias used as the NC item - if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) - continue; - - // As we don't know what properties in the JSON may contain other complex editors or deep nested NC - // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop - var ncItemPropVal = ncItemProp.Value?.ToString(); - - if (ncItemPropVal.DetectIsJson()) - { - // Parse the nested JSON (complex editor) - var complexEditorJson = JToken.Parse(ncItemPropVal); - - // Verify the complex editor is nested content (Will have one or more ncContentTypeAlias) - // One for each NC item/row - var hasNestedContentJsonProp = complexEditorJson.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false); - if (hasNestedContentJsonProp.Count() > 0) - { - // Recurse & update this JSON property - ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); - } + // recurse + var parsed = JToken.Parse(propVal); + UpdateNestedContentKeysRecursively(parsed, onlyMissingKeys, createGuid); + // set the value to the updated one + prop.Value = parsed.ToString(); } } } - - return ncJson.ToString(); } + } } From b1d0cbf1d0a99479cdf7a0ce0c75d5442869155e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 29 May 2020 09:30:34 +0100 Subject: [PATCH 62/96] Remove the CSProj reference to missing test file in repo - until we get a chance to add it back in #8199 --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 731dc05363..3c359cdde8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -148,7 +148,6 @@ - From 180b858ff58afe407028aafa14117b337c57bc85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 29 May 2020 14:09:20 +0200 Subject: [PATCH 63/96] V8/bugfix/gulp update inline views on watch (#8200) --- src/Umbraco.Web.UI.Client/gulp/config.js | 12 +++++++++--- src/Umbraco.Web.UI.Client/gulp/modes.js | 12 +++++++++++- src/Umbraco.Web.UI.Client/gulp/tasks/test.js | 17 +++++++++++++++-- .../gulp/tasks/watchTask.js | 19 ++++++++++--------- .../gulp/util/processJs.js | 4 +++- src/Umbraco.Web.UI.Client/gulpfile.js | 10 +++++----- 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 39dc9bb2a4..3c32832ba0 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -3,10 +3,16 @@ module.exports = { compile: { build: { - sourcemaps: false + sourcemaps: false, + embedtemplates: true }, dev: { - sourcemaps: true + sourcemaps: true, + embedtemplates: true + }, + test: { + sourcemaps: false, + embedtemplates: true } }, sources: { @@ -17,7 +23,7 @@ module.exports = { installer: { files: "./src/less/installer.less", watch: "./src/less/**/*.less", out: "installer.css" }, nonodes: { files: "./src/less/pages/nonodes.less", watch: "./src/less/**/*.less", out: "nonodes.style.min.css"}, preview: { files: "./src/less/canvas-designer.less", watch: "./src/less/**/*.less", out: "canvasdesigner.css" }, - umbraco: { files: "./src/less/belle.less", watch: "./src/less/**/*.less", out: "umbraco.css" }, + umbraco: { files: "./src/less/belle.less", watch: "./src/**/*.less", out: "umbraco.css" }, rteContent: { files: "./src/less/rte-content.less", watch: "./src/less/**/*.less", out: "rte-content.css" } }, diff --git a/src/Umbraco.Web.UI.Client/gulp/modes.js b/src/Umbraco.Web.UI.Client/gulp/modes.js index dc2947f2cc..21609cdcf8 100644 --- a/src/Umbraco.Web.UI.Client/gulp/modes.js +++ b/src/Umbraco.Web.UI.Client/gulp/modes.js @@ -10,4 +10,14 @@ function setDevelopmentMode(cb) { return cb(); }; -module.exports = { setDevelopmentMode: setDevelopmentMode }; +function setTestMode(cb) { + + config.compile.current = config.compile.test; + + return cb(); +}; + +module.exports = { + setDevelopmentMode: setDevelopmentMode, + setTestMode: setTestMode + }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js index 1e8d074f7e..255fe17435 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js @@ -6,11 +6,24 @@ var karmaServer = require('karma').Server; * Build tests **************************/ - // Karma test +// Karma test function testUnit() { + return new karmaServer({ + configFile: __dirname + "/../../test/config/karma.conf.js" + }) + .start(); +}; + +// Run karma test server +function runUnitTestServer() { + return new karmaServer({ configFile: __dirname + "/../../test/config/karma.conf.js", + autoWatch: true, + port: 9999, + singleRun: false, + browsers: ['ChromeDebugging'], keepalive: true }) .start(); @@ -24,4 +37,4 @@ function testE2e() { .start(); }; -module.exports = { testUnit: testUnit, testE2e: testE2e }; +module.exports = { testUnit: testUnit, testE2e: testE2e, runUnitTestServer: runUnitTestServer }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index 24a6e65540..f8e2570ff9 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -1,7 +1,7 @@ 'use strict'; const config = require('../config'); -const {watch, parallel, dest, src} = require('gulp'); +const {watch, series, parallel, dest, src} = require('gulp'); var _ = require('lodash'); var MergeStream = require('merge-stream'); @@ -9,9 +9,7 @@ var MergeStream = require('merge-stream'); var processJs = require('../util/processJs'); var processLess = require('../util/processLess'); -//const { less } = require('./less'); -//const { views } = require('./views'); - +var {js} = require('./js'); function watchTask(cb) { @@ -35,11 +33,14 @@ function watchTask(cb) { var viewWatcher; _.forEach(config.sources.views, function (group) { if(group.watch !== false) { - viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }); - viewWatcher.on('change', function(path, stats) { - console.log("copying " + group.files + " to " + config.root + config.targets.views + group.folder); - src(group.files).pipe( dest(config.root + config.targets.views + group.folder) ); - }); + viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }, + parallel( + function MoveViewsAndRegenerateJS() { + return src(group.files).pipe( dest(config.root + config.targets.views + group.folder) ); + }, + js + ) + ); } }); diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js index e3e393b661..67dd6dd420 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -25,7 +25,9 @@ module.exports = function (files, out) { .pipe(sort()); //in production, embed the templates - task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })) + if(config.compile.current.embedtemplates === true) { + task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })); + } task = task.pipe(concat(out)) .pipe(wrap('(function(){\n%= body %\n})();')) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 705c54bf04..542d45c479 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -13,11 +13,11 @@ const { src, dest, series, parallel, lastRun } = require('gulp'); const config = require('./gulp/config'); -const { setDevelopmentMode } = require('./gulp/modes'); +const { setDevelopmentMode, setTestMode } = require('./gulp/modes'); const { dependencies } = require('./gulp/tasks/dependencies'); const { js } = require('./gulp/tasks/js'); const { less } = require('./gulp/tasks/less'); -const { testE2e, testUnit } = require('./gulp/tasks/test'); +const { testE2e, testUnit, runUnitTestServer } = require('./gulp/tasks/test'); const { views } = require('./gulp/tasks/views'); const { watchTask } = require('./gulp/tasks/watchTask'); @@ -31,6 +31,6 @@ exports.build = series(parallel(dependencies, js, less, views), testUnit); exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask); exports.watch = series(watchTask); // -exports.runTests = series(js, testUnit); -exports.testUnit = series(testUnit); -exports.testE2e = series(testE2e); +exports.runTests = series(setTestMode, parallel(js, testUnit)); +exports.runUnit = series(setTestMode, parallel(js, runUnitTestServer), watchTask); +exports.testE2e = series(setTestMode, parallel(testE2e)); From 33850b13d0933f36624e8f75211c16335e544c5d Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Fri, 29 May 2020 13:39:14 +0100 Subject: [PATCH 64/96] If GetControllerTypeInternal falls back to creating an instance, it should also release it (#8196) --- src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs | 5 ++++- src/Umbraco.Web/Mvc/MasterControllerFactory.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs index 07a3f2a5e3..eb39cb8faa 100644 --- a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs @@ -25,7 +25,10 @@ namespace Umbraco.Web.Mvc //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); - return instance?.GetType(); + var controllerType = instance?.GetType(); + factory.ReleaseController(instance); + + return controllerType; } } } diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs index 6d2f1ce501..79d0a9aa63 100644 --- a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs @@ -81,7 +81,10 @@ namespace Umbraco.Web.Mvc //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); - return instance?.GetType(); + var controllerType = instance?.GetType(); + factory.ReleaseController(instance); + + return controllerType; } return GetControllerType(requestContext, controllerName); From 208df92fe0ae188f669e46e6e6b60c02ba370a70 Mon Sep 17 00:00:00 2001 From: BatJan Date: Wed, 27 May 2020 23:13:33 +0200 Subject: [PATCH 65/96] Hide icons and add missing screen reader friendly texts --- .../src/views/components/umb-confirm-action.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html index b45a3bb843..1b88c9f988 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html @@ -7,10 +7,16 @@ on-outside-click="clickCancel()">
From 74a4d769834403484f5631b0cde1b551f423e769 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Fri, 29 May 2020 14:41:20 +0200 Subject: [PATCH 66/96] umb-lightbox improvements (#8191) --- .../components/umblightbox.directive.js | 4 +-- .../src/less/components/umb-lightbox.less | 20 +++++++++++--- .../src/views/components/umb-lightbox.html | 27 ++++++++++++------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js index 19a33a8351..8d223e427e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js @@ -12,9 +12,9 @@
-
- -
+
@@ -11,12 +14,18 @@
-
- -
+ -
- -
+
From c23b813036a308fd7f731c1741dbf814afc88177 Mon Sep 17 00:00:00 2001 From: rbottema Date: Wed, 27 May 2020 20:31:24 +0200 Subject: [PATCH 67/96] Fix HTML markup of includeproperties.prevalues.html I've noticed this span was missing a closing >, so I added it. --- .../propertyeditors/listview/includeproperties.prevalues.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html index c6675ccec8..0499bea713 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html @@ -9,7 +9,7 @@ - +
From f7cbac30b18ec45bb2bd93b15db5e992ea087698 Mon Sep 17 00:00:00 2001 From: rbottema Date: Wed, 27 May 2020 20:15:35 +0200 Subject: [PATCH 68/96] Fix HTML markup of umb-list-view-settings.html I've noticed this file was missing a closing div tag, so I added it. --- .../src/views/components/umb-list-view-settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html index 8b4aeba3a0..4e2edbe2a9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html @@ -44,6 +44,6 @@ aria-label="Edit"> - + From 717bbf1e2e33ce5eb3ce1e11fe26454df864f173 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Sat, 30 May 2020 21:02:26 +0100 Subject: [PATCH 69/96] umb-notifications.html - refactored switch logic (#8207) * umb-notifications.html - refactored switch logic I couldn't get the `url` property to render, so I've swapped out the `ng-switch` statement with a couple of `ng-if` conditions. * umb-notifications.html - changed quotes from single to double For consistency. --- .../components/notifications/umb-notifications.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html index 769cd422ef..872fdbe41f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html @@ -7,18 +7,19 @@
-
- - {{notification.headline}} + +
+ + -
- {{notification.headline}} +
+
- From 16d646c94174700e624268984c0dedcf909e0c0d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jun 2020 13:57:34 +1000 Subject: [PATCH 70/96] oops, adds missing tests for NestedContentPropertyComponent --- .../NestedContentPropertyComponentTests.cs | 141 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 2 files changed, 142 insertions(+) create mode 100644 src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs diff --git a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs new file mode 100644 index 0000000000..3b3ebf5cb9 --- /dev/null +++ b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -0,0 +1,141 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Web.Compose; + +namespace Umbraco.Tests.PropertyEditors +{ + [TestFixture] + public class NestedContentPropertyComponentTests + { + [Test] + public void Invalid_Json() + { + var component = new NestedContentPropertyComponent(); + + Assert.DoesNotThrow(() => component.CreateNestedContentKeys("this is not json", true)); + } + + [Test] + public void No_Nesting() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var json = @"[ + {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, + {""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} +]"; + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + [Test] + public void One_Level_Nesting_Unescaped() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"": [{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ] + } +]"; + + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()) + .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) + .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + [Test] + public void One_Level_Nesting_Escaped() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + subJsonEscaped + @" + } +]"; + + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()) + .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) + .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + // TODO: Write tests for: + // * onlyMissingKeys = true for all combinations + // * 3 levels of nesting including when NC -> unknown complex editor -> NC + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3c359cdde8..731dc05363 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -148,6 +148,7 @@ + From 4d497053a3f51a02c18aa9392db0c9855e4ec7be Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 2 Jun 2020 09:08:43 +0100 Subject: [PATCH 71/96] Fix up typos & mark incorrect as deprecated --- .../src/common/services/clipboard.service.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index 083b4e86b7..d7d6ec862c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -97,9 +97,26 @@ function clipboardService(notificationsService, eventsService, localStorageServi var service = {}; + /** * @ngdoc method - * @name umbraco.services.clipboardService#registrerPropertyClearingResolver + * @name umbraco.services.clipboardService#registrerClearPropertyResolver + * @methodOf umbraco.services.clipboardService + * + * @param {string} function A method executed for every property and inner properties copied. + * + * @description + * Executed for all properties including inner properties when performing a copy action. + * + * @deprecated Incorrect spelling please use 'registerClearPropertyResolver' + */ + service.registrerClearPropertyResolver = function(resolver) { + this.registerClearPropertyResolver(resolver); + }; + + /** + * @ngdoc method + * @name umbraco.services.clipboardService#registerClearPropertyResolver * @methodOf umbraco.services.clipboardService * * @param {string} function A method executed for every property and inner properties copied. @@ -107,7 +124,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi * @description * Executed for all properties including inner properties when performing a copy action. */ - service.registrerClearPropertyResolver = function(resolver) { + service.registerClearPropertyResolver = function(resolver) { clearPropertyResolvers.push(resolver); }; From a4274ba76727273eb8d7cfb02b97fc5bdcd3e9cc Mon Sep 17 00:00:00 2001 From: Chad Date: Tue, 2 Jun 2020 21:35:19 +1200 Subject: [PATCH 72/96] Shorter JSON property names for nucache (#8204) --- .../ContentTypeServiceVariantsTests.cs | 70 +++++++++---------- .../NuCache/DataSource/ContentNestedData.cs | 19 ++++- .../NuCache/DataSource/CultureVariation.cs | 21 ++++-- .../NuCache/DataSource/PropertyData.cs | 29 +++++++- 4 files changed, 94 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 0fe05f385f..9391b7442f 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -88,8 +88,8 @@ namespace Umbraco.Tests.Services private void AssertJsonStartsWith(int id, string expected) { var json = GetJson(id).Replace('"', '\''); - var pos = json.IndexOf("'cultureData':", StringComparison.InvariantCultureIgnoreCase); - json = json.Substring(0, pos + "'cultureData':".Length); + var pos = json.IndexOf("'cd':", StringComparison.InvariantCultureIgnoreCase); + json = json.Substring(0, pos + "'cd':".Length); Assert.AreEqual(expected, json); } @@ -595,7 +595,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch content type to Nothing contentType.Variations = ContentVariation.Nothing; @@ -612,7 +612,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch content back to Culture contentType.Variations = ContentVariation.Culture; @@ -629,7 +629,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch property back to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -645,7 +645,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); } [Test] @@ -686,7 +686,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch content type to Culture contentType.Variations = ContentVariation.Culture; @@ -702,7 +702,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch property to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -717,7 +717,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch content back to Nothing contentType.Variations = ContentVariation.Nothing; @@ -734,7 +734,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); } [Test] @@ -772,7 +772,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch property type to Nothing contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; @@ -789,7 +789,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch property back to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -805,7 +805,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch other property to Culture contentType.PropertyTypes.First(x => x.Alias == "value2").Variations = ContentVariation.Culture; @@ -823,7 +823,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'c':'en','v':'v2'}]},'cd':"); } [TestCase(ContentVariation.Culture, ContentVariation.Nothing)] @@ -1054,7 +1054,7 @@ namespace Umbraco.Tests.Services // both value11 and value21 are variant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composed.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composed); @@ -1062,7 +1062,7 @@ namespace Umbraco.Tests.Services // both value11 and value21 are invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); composed.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed); @@ -1070,7 +1070,7 @@ namespace Umbraco.Tests.Services // value11 is variant again, but value21 is still invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); composed.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed); @@ -1078,7 +1078,7 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composing); @@ -1086,7 +1086,7 @@ namespace Umbraco.Tests.Services // value11 is invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1094,7 +1094,7 @@ namespace Umbraco.Tests.Services // value11 is still invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1102,7 +1102,7 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); } [Test] @@ -1167,11 +1167,11 @@ namespace Umbraco.Tests.Services // both value11 and value21 are variant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composed1); @@ -1179,11 +1179,11 @@ namespace Umbraco.Tests.Services // both value11 and value21 are invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed1); @@ -1191,11 +1191,11 @@ namespace Umbraco.Tests.Services // value11 is variant again, but value21 is still invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed1); @@ -1203,11 +1203,11 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composing); @@ -1215,11 +1215,11 @@ namespace Umbraco.Tests.Services // value11 is invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1227,11 +1227,11 @@ namespace Umbraco.Tests.Services // value11 is still invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1239,11 +1239,11 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); } private void CreateFrenchAndEnglishLangs() diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs index adff28a2ba..ec5424ad9a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs @@ -9,15 +9,28 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource /// internal class ContentNestedData { - [JsonProperty("properties")] + //dont serialize empty properties + [JsonProperty("pd")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary PropertyData { get; set; } - [JsonProperty("cultureData")] + [JsonProperty("cd")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary CultureData { get; set; } - [JsonProperty("urlSegment")] + [JsonProperty("us")] public string UrlSegment { get; set; } + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("properties")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] + private Dictionary LegacyPropertyData { set { PropertyData = value; } } + + [JsonProperty("cultureData")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] + private Dictionary LegacyCultureData { set { CultureData = value; } } + + [JsonProperty("urlSegment")] + private string LegacyUrlSegment { set { UrlSegment = value; } } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs index a98a96f424..57ffbba34e 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs @@ -8,16 +8,29 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource /// internal class CultureVariation { - [JsonProperty("name")] + [JsonProperty("nm")] public string Name { get; set; } - [JsonProperty("urlSegment")] + [JsonProperty("us")] public string UrlSegment { get; set; } - [JsonProperty("date")] + [JsonProperty("dt")] public DateTime Date { get; set; } - [JsonProperty("isDraft")] + [JsonProperty("isd")] public bool IsDraft { get; set; } + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("name")] + private string LegacyName { set { Name = value; } } + + [JsonProperty("urlSegment")] + private string LegacyUrlSegment { set { UrlSegment = value; } } + + [JsonProperty("date")] + private DateTime LegacyDate { set { Date = value; } } + + [JsonProperty("isDraft")] + private bool LegacyIsDraft { set { IsDraft = value; } } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs index e14426a2e2..4abcbc7e6f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Newtonsoft.Json; namespace Umbraco.Web.PublishedCache.NuCache.DataSource @@ -8,21 +9,43 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource private string _culture; private string _segment; - [JsonProperty("culture")] + [DefaultValue("")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "c")] public string Culture { get => _culture; set => _culture = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null } - [JsonProperty("seg")] + [DefaultValue("")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "s")] public string Segment { get => _segment; set => _segment = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null } - [JsonProperty("val")] + [JsonProperty("v")] public object Value { get; set; } + + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("culture")] + private string LegacyCulture + { + set => Culture = value; + } + + [JsonProperty("seg")] + private string LegacySegment + { + set => Segment = value; + } + + [JsonProperty("val")] + private object LegacyValue + { + set => Value = value; + } } } From e82f5046587e9a54a826e821b6fe89c5b24c45f5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 9 Jun 2020 06:55:06 +0000 Subject: [PATCH 73/96] Adds skipStepIfVisible property to JSON Tour Steps to allow skipping if DOM item is present (#8235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niels Lyngsø --- .../application/umbtour.directive.js | 37 ++++++++++++++----- .../BackOfficeTours/getting-started.json | 19 ++++------ src/Umbraco.Web/Models/BackOfficeTourStep.cs | 4 +- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index 6f98dbca6e..01d73568ec 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -5,14 +5,14 @@ @scope @description -Added in Umbraco 7.8. The tour component is a global component and is already added to the umbraco markup. -In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco. -You can easily add you own tours to the Help-drawer or show and start tours from +Added in Umbraco 7.8. The tour component is a global component and is already added to the umbraco markup. +In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco. +You can easily add you own tours to the Help-drawer or show and start tours from anywhere in the Umbraco backoffice. To see a real world example of a custom tour implementation, install The Starter Kit in Umbraco 7.8

Extending the help drawer with custom tours

-The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file. -Place the file in App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json and it will automatically be +The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file. +Place the file in App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json and it will automatically be picked up by Umbraco and shown in the Help-drawer.

The tour object

@@ -26,7 +26,7 @@ The tour object consist of two parts - The overall tour configuration and a list "groupOrder": 200 // Control the order of tour groups "allowDisable": // Adds a "Don't" show this tour again"-button to the intro step "culture" : // From v7.11+. Specifies the culture of the tour (eg. en-US), if set the tour will only be shown to users with this culture set on their profile. If omitted or left empty the tour will be visible to all users - "requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load. + "requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load. "steps": [] // tour steps - see next example } @@ -43,11 +43,12 @@ The tour object consist of two parts - The overall tour configuration and a list "backdropOpacity": 0.4 // the backdrop opacity "view": "" // add a custom view "customProperties" : {} // add any custom properties needed for the custom view + "skipStepIfVisible": ".dashboard div [data-element='my-tour-button']" // if we can find this DOM element on the page then we will skip this step }

Adding tours to other parts of the Umbraco backoffice

-It is also possible to add a list of custom tours to other parts of the Umbraco backoffice, +It is also possible to add a list of custom tours to other parts of the Umbraco backoffice, as an example on a Dashboard in a Custom section. You can then use the {@link umbraco.services.tourService tourService} to start and stop tours but you don't have to register them as part of the tour service.

Using the tour service

@@ -86,7 +87,8 @@ as an example on a Dashboard in a Custom section. You can then use the {@link um "element": "[data-element='my-tour-button']", "title": "Click the button", "content": "Click the button", - "event": "click" + "event": "click", + "skipStepIfVisible": "[data-element='my-other-tour-button']" } ] }; @@ -257,9 +259,26 @@ In the following example you see how to run some custom logic before a step goes // make sure we don't go too far if (scope.model.currentStepIndex !== scope.model.steps.length) { + + var upcomingStep = scope.model.steps[scope.model.currentStepIndex]; + + // If the currentStep JSON object has 'skipStepIfVisible' + // It's a DOM selector - if we find it then we ship over this step + if(upcomingStep.skipStepIfVisible) { + let tryFindDomEl = document.querySelector(upcomingStep.element); + if(tryFindDomEl) { + // check if element is visible: + if( tryFindDomEl.offsetWidth || tryFindDomEl.offsetHeight || tryFindDomEl.getClientRects().length ) { + // if it was visible then we skip the step. + nextStep(); + } + } + } + startStep(); - // tour completed - final step + } else { + // tour completed - final step scope.loadingStep = true; waitForPendingRerequests().then(function () { diff --git a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json index d0aa1a1c34..3709e703d9 100644 --- a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json +++ b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json @@ -188,27 +188,21 @@ "event": "click" }, { - "element": "[data-element~='editor-data-type-picker']", + "element": "[ng-controller*='Umbraco.Editors.DataTypePickerController'] [data-element='editor-data-type-picker']", "elementPreventClick": true, "title": "Editor picker", - "content": "

In the editor picker dialog we can pick one of the many built-in editors.

You can choose from preconfigured data types (Reuse) or create a new configuration (Available editors).

" + "content": "

In the editor picker dialog we can pick one of the many built-in editors.

" }, { - "element": "[data-element~='editor-data-type-picker'] [data-element='editor-Textarea']", + "element": "[data-element~='editor-data-type-picker'] [data-element='datatype-Textarea']", "title": "Select editor", "content": "Select the Textarea editor. This will add a textarea to the Welcome Text property.", "event": "click" }, { - "element": "[data-element~='editor-data-type-settings']", - "elementPreventClick": true, + "element": "[data-element='editor-data-type-picker'] [data-element='datatypeconfig-Textarea'] > a", "title": "Editor settings", - "content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed." - }, - { - "element": "[data-element~='editor-data-type-settings'] [data-element='button-submit']", - "title": "Save editor", - "content": "Click Submit to save the changes.", + "content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed.", "event": "click" }, { @@ -317,7 +311,8 @@ "content": "

To see all our templates click the small triangle to the left of the templates node.

", "event": "click", "eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-expand']", - "view": "templatetree" + "view": "templatetree", + "skipStepIfVisible": "#tree [data-element='tree-item-templates'] > div > button[data-element=tree-item-expand].icon-navigation-down" }, { "element": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page']", diff --git a/src/Umbraco.Web/Models/BackOfficeTourStep.cs b/src/Umbraco.Web/Models/BackOfficeTourStep.cs index a64bf15b7f..c21b09523d 100644 --- a/src/Umbraco.Web/Models/BackOfficeTourStep.cs +++ b/src/Umbraco.Web/Models/BackOfficeTourStep.cs @@ -29,5 +29,7 @@ namespace Umbraco.Web.Models public string EventElement { get; set; } [DataMember(Name = "customProperties")] public JObject CustomProperties { get; set; } + [DataMember(Name = "skipStepIfVisible")] + public string SkipStepIfVisible { get; set; } } -} \ No newline at end of file +} From a06952c65f972d01aa260164f58d152116001c5c Mon Sep 17 00:00:00 2001 From: Thomas Andresen Date: Tue, 9 Jun 2020 09:36:12 +0200 Subject: [PATCH 74/96] If content type is deleted/changed which other document types has a reference to, fire a change event for those --- ...ntentTypeServiceBaseOfTRepositoryTItemTService.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index fdd2d9ceae..3d4c109bfb 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -511,6 +511,16 @@ namespace Umbraco.Core.Services.Implement // delete content DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id)); + + // Next find all other document types that have a reference to this content type + var referenceToAllowedContentTypes = GetAll().Where(q => q.AllowedContentTypes.Any(p=>p.Id.Value==item.Id)); + foreach (var reference in referenceToAllowedContentTypes) + { + reference.AllowedContentTypes = reference.AllowedContentTypes.Where(p => p.Id.Value != item.Id); + var changedRef = new List>() { new ContentTypeChange(reference, ContentTypeChangeTypes.RefreshMain) }; + // Fire change event + OnChanged(scope, changedRef.ToEventArgs()); + } // finally delete the content type // - recursively deletes all descendants @@ -518,7 +528,7 @@ namespace Umbraco.Core.Services.Implement // (contents of any descendant type have been deleted but // contents of any composed (impacted) type remain but // need to have their property data cleared) - Repository.Delete(item); + Repository.Delete(item); //... var changes = descendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove)) From 7807ded47928da243e1cdfa030a9613ee812c9c6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Jun 2020 16:33:51 +0100 Subject: [PATCH 75/96] Adds in missing tests for a complex editor such as the grid storing a NC value along with replacing only missing keys --- .../NestedContentPropertyComponentTests.cs | 270 +++++++++++++++++- 1 file changed, 267 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs index 3b3ebf5cb9..1b83c048d2 100644 --- a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -133,9 +133,273 @@ namespace Umbraco.Tests.PropertyEditors Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); } - // TODO: Write tests for: - // * onlyMissingKeys = true for all combinations - // * 3 levels of nesting including when NC -> unknown complex editor -> NC + [Test] + public void Nested_In_Complex_Editor_Escaped() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + // Complex editor such as the grid + var complexEditorJsonEscaped = @"{ + ""name"": ""1 column layout"", + ""sections"": [ + { + ""grid"": ""12"", + ""rows"": [ + { + ""name"": ""Article"", + ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", + ""areas"": [ + { + ""grid"": ""4"", + ""controls"": [ + { + ""value"": ""I am quote"", + ""editor"": { + ""alias"": ""quote"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }, + { + ""grid"": ""8"", + ""controls"": [ + { + ""value"": ""Header"", + ""editor"": { + ""alias"": ""headline"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }, + { + ""value"": " + subJsonEscaped + @", + ""editor"": { + ""alias"": ""madeUpNestedContent"", + ""view"": ""madeUpNestedContentInGrid"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }] + }] +}"; + + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + complexEditorJsonEscaped + @" + } +]"; + + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()) + .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) + .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + + [Test] + public void No_Nesting_Generates_Keys_For_Missing_Items() + { + var guids = new[] { Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var json = @"[ + {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1 my key wont change"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, + {""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} +]"; + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, true, guidFactory); + + // Ensure the new GUID is put in a key into the JSON + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); + + // Ensure that the original key is NOT changed/modified & still exists + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08")); + } + + [Test] + public void One_Level_Nesting_Escaped_Generates_Keys_For_Missing_Items() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""name"": ""Nested Item 2 was copied and has no key"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + var json = @"[{ + ""name"": ""Item 1 was copied and has no key"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + subJsonEscaped + @" + } +]"; + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, true, guidFactory); + + // Ensure the new GUID is put in a key into the JSON for each item + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString())); + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[2].ToString())); + } + + [Test] + public void Nested_In_Complex_Editor_Escaped_Generates_Keys_For_Missing_Items() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""name"": ""Nested Item 2 was copied and has no key"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + // Complex editor such as the grid + var complexEditorJsonEscaped = @"{ + ""name"": ""1 column layout"", + ""sections"": [ + { + ""grid"": ""12"", + ""rows"": [ + { + ""name"": ""Article"", + ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", + ""areas"": [ + { + ""grid"": ""4"", + ""controls"": [ + { + ""value"": ""I am quote"", + ""editor"": { + ""alias"": ""quote"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }, + { + ""grid"": ""8"", + ""controls"": [ + { + ""value"": ""Header"", + ""editor"": { + ""alias"": ""headline"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }, + { + ""value"": " + subJsonEscaped + @", + ""editor"": { + ""alias"": ""madeUpNestedContent"", + ""view"": ""madeUpNestedContentInGrid"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }] + }] +}"; + + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""name"": ""Item 2 was copied and has no key"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + complexEditorJsonEscaped + @" + } +]"; + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, true, guidFactory); + + // Ensure the new GUID is put in a key into the JSON for each item + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString())); + } } } From ae993519ce7fffa0303cc08c86c6f0e963453dde Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 10 Jun 2020 21:14:07 +0200 Subject: [PATCH 76/96] Fix radio button checked appearance --- .../src/less/components/umb-form-check.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index 2d3ef92ef9..8a24d948ac 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -47,6 +47,8 @@ label.umb-form-check--checkbox{ } &:checked ~ .umb-form-check__state .umb-form-check__check { border-color: @ui-option-type; + } + &[type='checkbox']:checked ~ .umb-form-check__state .umb-form-check__check { background-color: @ui-option-type; } &:checked:hover ~ .umb-form-check__state .umb-form-check__check { From eabf6f459bcefbe54009d14c2ba71cd991b98842 Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:39:10 +0100 Subject: [PATCH 77/96] Move TestClone class --- src/Umbraco.Tests.Common/TestClone.cs | 77 +++++++++++++++++++ .../Collections/DeepCloneableListTests.cs | 77 +------------------ .../Cache/DeepCloneAppCacheTests.cs | 12 +-- 3 files changed, 85 insertions(+), 81 deletions(-) create mode 100644 src/Umbraco.Tests.Common/TestClone.cs diff --git a/src/Umbraco.Tests.Common/TestClone.cs b/src/Umbraco.Tests.Common/TestClone.cs new file mode 100644 index 0000000000..5fd0aa30e2 --- /dev/null +++ b/src/Umbraco.Tests.Common/TestClone.cs @@ -0,0 +1,77 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Common +{ + public class TestClone : IDeepCloneable, IEquatable + { + public TestClone(Guid id) + { + Id = id; + IsClone = true; + } + + public TestClone() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; } + public bool IsClone { get; } + + public object DeepClone() + { + return new TestClone(Id); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(TestClone other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TestClone)obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public static bool operator ==(TestClone left, TestClone right) + { + return Equals(left, right); + } + + public static bool operator !=(TestClone left, TestClone right) + { + return Equals(left, right) == false; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs index 305a392311..bcfe142d8d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs @@ -1,8 +1,7 @@ -using System; -using System.Linq; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Collections; -using Umbraco.Core.Models; +using Umbraco.Tests.Common; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections { @@ -94,77 +93,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections Assert.AreNotSame(item, clone); } } - - public class TestClone : IDeepCloneable, IEquatable - { - public TestClone(Guid id) - { - Id = id; - IsClone = true; - } - - public TestClone() - { - Id = Guid.NewGuid(); - } - - public Guid Id { get; } - public bool IsClone { get; } - - public object DeepClone() - { - return new TestClone(Id); - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(TestClone other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Id.Equals(other.Id); - } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// - /// true if the specified object is equal to the current object; otherwise, false. - /// - /// The object to compare with the current object. - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((TestClone) obj); - } - - /// - /// Serves as the default hash function. - /// - /// - /// A hash code for the current object. - /// - public override int GetHashCode() - { - return Id.GetHashCode(); - } - - public static bool operator ==(TestClone left, TestClone right) - { - return Equals(left, right); - } - - public static bool operator !=(TestClone left, TestClone right) - { - return Equals(left, right) == false; - } - } } } diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index 63e481e9a5..f7b39590f2 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -12,7 +12,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; -using Umbraco.Tests.Collections; +using Umbraco.Tests.Common; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Cache; @@ -41,12 +41,12 @@ namespace Umbraco.Tests.Cache [Test] public void Clones_List() { - var original = new DeepCloneableList(ListCloneBehavior.Always); - original.Add(new DeepCloneableListTests.TestClone()); - original.Add(new DeepCloneableListTests.TestClone()); - original.Add(new DeepCloneableListTests.TestClone()); + var original = new DeepCloneableList(ListCloneBehavior.Always); + original.Add(new TestClone()); + original.Add(new TestClone()); + original.Add(new TestClone()); - var val = _provider.GetCacheItem>("test", () => original); + var val = _provider.GetCacheItem>("test", () => original); Assert.AreEqual(original.Count, val.Count); foreach (var item in val) From 06a1671f950b9cd356d79eaa34483508283bb796 Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:39:45 +0100 Subject: [PATCH 78/96] Move ClaimsIdentityExtensionsTests --- .../Umbraco.Core}/ClaimsIdentityExtensionsTests.cs | 3 +-- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/ClaimsIdentityExtensionsTests.cs (96%) diff --git a/src/Umbraco.Tests/CoreThings/ClaimsIdentityExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ClaimsIdentityExtensionsTests.cs similarity index 96% rename from src/Umbraco.Tests/CoreThings/ClaimsIdentityExtensionsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/ClaimsIdentityExtensionsTests.cs index 4f728c3861..76f928ca46 100644 --- a/src/Umbraco.Tests/CoreThings/ClaimsIdentityExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ClaimsIdentityExtensionsTests.cs @@ -3,9 +3,8 @@ using System.Collections.Generic; using System.Security.Claims; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Web; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { public class ClaimsIdentityExtensionsTests { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 9bc7e71fad..602a35f727 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -122,7 +122,6 @@ - From 3da8fd4a9803c247045e398f9c586c992da9b9ca Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:41:48 +0100 Subject: [PATCH 79/96] Move AttemptTests --- .../Umbraco.Core}/AttemptTests.cs | 3 +-- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/AttemptTests.cs (92%) diff --git a/src/Umbraco.Tests/CoreThings/AttemptTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/AttemptTests.cs similarity index 92% rename from src/Umbraco.Tests/CoreThings/AttemptTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/AttemptTests.cs index a004095243..9a16c5b10f 100644 --- a/src/Umbraco.Tests/CoreThings/AttemptTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/AttemptTests.cs @@ -1,12 +1,11 @@ using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class AttemptTests { - [Test] public void AttemptIf() { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 602a35f727..30d2751826 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -244,7 +244,6 @@ - From 958b4f431ea3fdbe80ccfa80a52ae880e4ff1a8b Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:47:08 +0100 Subject: [PATCH 80/96] Move DelegateExtensionsTests --- .../Umbraco.Tests.UnitTests.csproj | 1 + .../CoreThings/DelegateExtensionsTests.cs | 38 ------------------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 3 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 src/Umbraco.Tests/CoreThings/DelegateExtensionsTests.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 67d822fb03..24627b2722 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Umbraco.Tests/CoreThings/DelegateExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/DelegateExtensionsTests.cs deleted file mode 100644 index 87b2f4c03b..0000000000 --- a/src/Umbraco.Tests/CoreThings/DelegateExtensionsTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Lucene.Net.Index; -using NUnit.Framework; -using Umbraco.Core; - -namespace Umbraco.Tests.CoreThings -{ - [TestFixture] - public class DelegateExtensionsTests - { - [Test] - public void Only_Executes_Specific_Count() - { - const int maxTries = 5; - var totalTries = 0; - DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => - { - totalTries = currentTry; - return Attempt.Fail(); - }, 5, TimeSpan.FromMilliseconds(10)); - - Assert.AreEqual(maxTries, totalTries); - } - - [Test] - public void Quits_On_Success_Count() - { - var totalTries = 0; - DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => - { - totalTries = currentTry; - return totalTries == 2 ? Attempt.Succeed() : Attempt.Fail(); - }, 5, TimeSpan.FromMilliseconds(10)); - - Assert.AreEqual(2, totalTries); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 30d2751826..1ea8d465e6 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -252,7 +252,6 @@ - From cb3a5d6b97c7a2cb01f281130b086c2a4c2a3f84 Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:48:57 +0100 Subject: [PATCH 81/96] Move EnumerableExtensionsTests --- .../Umbraco.Core/DelegateExtensionsTests.cs | 38 +++++++++++++++++++ .../EnumerableExtensionsTests.cs | 5 +-- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/EnumerableExtensionsTests.cs (98%) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs new file mode 100644 index 0000000000..a3e36c8ae6 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs @@ -0,0 +1,38 @@ +using System; +using Lucene.Net.Index; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests.UnitTests.Umbraco.Core +{ + [TestFixture] + public class DelegateExtensionsTests + { + [Test] + public void Only_Executes_Specific_Count() + { + const int maxTries = 5; + var totalTries = 0; + DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => + { + totalTries = currentTry; + return Attempt.Fail(); + }, 5, TimeSpan.FromMilliseconds(10)); + + Assert.AreEqual(maxTries, totalTries); + } + + [Test] + public void Quits_On_Success_Count() + { + var totalTries = 0; + DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => + { + totalTries = currentTry; + return totalTries == 2 ? Attempt.Succeed() : Attempt.Fail(); + }, 5, TimeSpan.FromMilliseconds(10)); + + Assert.AreEqual(2, totalTries); + } + } +} diff --git a/src/Umbraco.Tests/CoreThings/EnumerableExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumerableExtensionsTests.cs similarity index 98% rename from src/Umbraco.Tests/CoreThings/EnumerableExtensionsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumerableExtensionsTests.cs index e734713c76..32e039f26a 100644 --- a/src/Umbraco.Tests/CoreThings/EnumerableExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumerableExtensionsTests.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class EnumerableExtensionsTests diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1ea8d465e6..acb5b7398e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -458,7 +458,6 @@ - From 299a461afbe1580dea6516a40cd3ff47c99a1f13 Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:50:10 +0100 Subject: [PATCH 82/96] Move EnumExtensionsTests --- .../Umbraco.Core}/EnumExtensionsTests.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/EnumExtensionsTests.cs (97%) diff --git a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumExtensionsTests.cs similarity index 97% rename from src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumExtensionsTests.cs index faa15b0077..d5ea4d2677 100644 --- a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumExtensionsTests.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Web.Trees; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class EnumExtensionsTests diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index acb5b7398e..18ee81a9c7 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -122,7 +122,6 @@ - From 449bf55060969ebf21a4cf782e300448b1827079 Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:54:19 +0100 Subject: [PATCH 83/96] Move GuidUtilsTests --- .../Umbraco.Core}/GuidUtilsTests.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/GuidUtilsTests.cs (95%) diff --git a/src/Umbraco.Tests/CoreThings/GuidUtilsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/GuidUtilsTests.cs similarity index 95% rename from src/Umbraco.Tests/CoreThings/GuidUtilsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/GuidUtilsTests.cs index 639d85b4ff..62e0955d78 100644 --- a/src/Umbraco.Tests/CoreThings/GuidUtilsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/GuidUtilsTests.cs @@ -2,7 +2,7 @@ using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { public class GuidUtilsTests { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 18ee81a9c7..6ab2a06491 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -122,7 +122,6 @@ - From 663adc1db7931510de24676b721005b146a2a2c3 Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:56:24 +0100 Subject: [PATCH 84/96] Move HexEncoderTests --- .../Umbraco.Core}/HexEncoderTests.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/HexEncoderTests.cs (97%) diff --git a/src/Umbraco.Tests/CoreThings/HexEncoderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HexEncoderTests.cs similarity index 97% rename from src/Umbraco.Tests/CoreThings/HexEncoderTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/HexEncoderTests.cs index 588fff83e8..f22c3f2ac1 100644 --- a/src/Umbraco.Tests/CoreThings/HexEncoderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HexEncoderTests.cs @@ -3,7 +3,7 @@ using System.Text; using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { public class HexEncoderTests { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 6ab2a06491..432caf7b04 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -122,7 +122,6 @@ - From 7aadae68d8273ffb0a218522eb7f55e76e7a813f Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 14:59:14 +0100 Subject: [PATCH 85/96] Move VersionExtensionTests --- .../Umbraco.Core}/VersionExtensionTests.cs | 4 ++-- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/VersionExtensionTests.cs (86%) diff --git a/src/Umbraco.Tests/CoreThings/VersionExtensionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/VersionExtensionTests.cs similarity index 86% rename from src/Umbraco.Tests/CoreThings/VersionExtensionTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/VersionExtensionTests.cs index 5799e28b87..a4ab15afe7 100644 --- a/src/Umbraco.Tests/CoreThings/VersionExtensionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/VersionExtensionTests.cs @@ -2,7 +2,7 @@ using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class VersionExtensionTests @@ -18,7 +18,7 @@ namespace Umbraco.Tests.CoreThings [TestCase(0, 0, 1, 1, "0.0.1.0")] [TestCase(0, 0, 0, 1, "0.0.0.0")] [TestCase(7, 3, 0, 0, "7.2.2147483647.2147483647")] - public void Subract_Revision(int major, int minor, int build, int rev, string outcome) + public void Subtract_Revision(int major, int minor, int build, int rev, string outcome) { var version = new Version(major, minor, build, rev); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 432caf7b04..0e08e282a8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -463,7 +463,6 @@ - From 5d5c7ba95e59d96bb8a2888b6e9ed9c9e8f05bbd Mon Sep 17 00:00:00 2001 From: Ollie Philpott Date: Thu, 11 Jun 2020 15:13:35 +0100 Subject: [PATCH 86/96] Move XmlExtensionsTests --- .../Umbraco.Core}/XmlExtensionsTests.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core}/XmlExtensionsTests.cs (96%) diff --git a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/XmlExtensionsTests.cs similarity index 96% rename from src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/XmlExtensionsTests.cs index 62e385beff..ae3762ef57 100644 --- a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/XmlExtensionsTests.cs @@ -3,7 +3,7 @@ using System.Xml.Linq; using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class XmlExtensionsTests diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 0e08e282a8..6455d4f7cc 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -465,7 +465,6 @@ - From 96ab448e74f920f23f4b6980d560f4d167ce6494 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Jun 2020 09:47:53 +0000 Subject: [PATCH 87/96] Azure DevOps YML pipeline for creating C# & JS Docs (#8282) --- build/V8-Docs-Pipeline.yml | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 build/V8-Docs-Pipeline.yml diff --git a/build/V8-Docs-Pipeline.yml b/build/V8-Docs-Pipeline.yml new file mode 100644 index 0000000000..1df4092397 --- /dev/null +++ b/build/V8-Docs-Pipeline.yml @@ -0,0 +1,51 @@ +############################################################## +## V8 CMS - .NET & AngularJS Doc sites ## +## Built on demand only, NO automatic PR/branch triggers ## +## ## +## This build pipeline has a webhook for sucessful ## +## builds, that sends the ZIP artifacts to our.umb to host ## +############################################################## + +# Name != name of pipeline but the build number format +# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/run-number?view=azure-devops&tabs=yaml + +# Build Pipeline triggers +# https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#ci-triggers +trigger: none +pr: none + +# Variables & their default values +variables: + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + +# VM to run the build on & it's installed software +# https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops +# https://github.com/actions/virtual-environments/blob/master/images/win/Windows2019-Readme.md +pool: + vmImage: 'windows-2019' + +jobs: + - job: buildDocs + displayName: 'Build static docs site as ZIPs' + steps: + + - task: PowerShell@2 + displayName: 'Prep build tool, build C# & JS Docs' + inputs: + targetType: 'inline' + script: | + $uenv=./build.ps1 -get -doc + $uenv.SandboxNode() + $uenv.PrepareAngularDocs() + $nugetsourceUmbraco = "https://api.nuget.org/v3/index.json" + $uenv.PrepareCSharpDocs() + $uenv.RestoreNode() + errorActionPreference: 'continue' + workingDirectory: 'build' + + - task: PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.Repository.LocalPath)\build.out\' + artifact: 'docs' + publishLocation: 'pipeline' \ No newline at end of file From 0836118d542d99670e009cc05a55277e309a95d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 17 Jun 2020 12:07:15 +0200 Subject: [PATCH 88/96] force-resolutions and be able to run server including watching files for changes. --- src/Umbraco.Web.UI.Docs/gulpfile.js | 3 +++ src/Umbraco.Web.UI.Docs/package.json | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Docs/gulpfile.js b/src/Umbraco.Web.UI.Docs/gulpfile.js index a3e596ecad..a2a913d5fc 100644 --- a/src/Umbraco.Web.UI.Docs/gulpfile.js +++ b/src/Umbraco.Web.UI.Docs/gulpfile.js @@ -54,3 +54,6 @@ gulp.task('open:docs', function (cb) { .pipe(open(options)); cb(); }); + +gulp.task('watch', ['docs', 'connect:docs', 'open:docs']); + diff --git a/src/Umbraco.Web.UI.Docs/package.json b/src/Umbraco.Web.UI.Docs/package.json index 843740482e..6e58f958cf 100644 --- a/src/Umbraco.Web.UI.Docs/package.json +++ b/src/Umbraco.Web.UI.Docs/package.json @@ -1,9 +1,16 @@ { "private": true, "scripts": { + "preinstall": "npx npm-force-resolutions", "docs": "gulp docs", "start": "gulp docs", - "default": "gulp docs" + "default": "gulp docs", + "dev": "gulp watch", + "serve": "gulp watch", + "watch": "gulp watch" + }, + "resolutions": { + "graceful-fs": "4.2.3" }, "devDependencies": { "gulp": "^3.9.1", From d7cd52161fce0061fe2ba7ccc235a904ddaa77a9 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Jun 2020 12:46:11 +0100 Subject: [PATCH 89/96] Fix SkipStep in tour directive was looking at wrong DOM selector it needs to be skipStepIfVisible --- .../directives/components/application/umbtour.directive.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index 01d73568ec..eb521312fc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -264,13 +264,14 @@ In the following example you see how to run some custom logic before a step goes // If the currentStep JSON object has 'skipStepIfVisible' // It's a DOM selector - if we find it then we ship over this step - if(upcomingStep.skipStepIfVisible) { - let tryFindDomEl = document.querySelector(upcomingStep.element); - if(tryFindDomEl) { + if (upcomingStep.skipStepIfVisible) { + let tryFindDomEl = document.querySelector(upcomingStep.skipStepIfVisible); + if (tryFindDomEl) { // check if element is visible: if( tryFindDomEl.offsetWidth || tryFindDomEl.offsetHeight || tryFindDomEl.getClientRects().length ) { // if it was visible then we skip the step. nextStep(); + return; } } } From d4c464c1233816c43e0524df4dde2b20701c7844 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Jun 2020 12:50:31 +0100 Subject: [PATCH 90/96] Remove duplicate comment # Conflicts: # src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js --- .../directives/components/application/umbtour.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index eb521312fc..1a9c3c771a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -277,7 +277,7 @@ In the following example you see how to run some custom logic before a step goes } startStep(); - + // tour completed - final step } else { // tour completed - final step scope.loadingStep = true; From 25488d20167ff7ee38f631b0efc6b1e99cdf82d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 17 Jun 2020 15:32:54 +0200 Subject: [PATCH 91/96] add watch functionallity --- src/Umbraco.Web.UI.Docs/gulpfile.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Docs/gulpfile.js b/src/Umbraco.Web.UI.Docs/gulpfile.js index a2a913d5fc..5dd4c57032 100644 --- a/src/Umbraco.Web.UI.Docs/gulpfile.js +++ b/src/Umbraco.Web.UI.Docs/gulpfile.js @@ -8,6 +8,8 @@ var connect = require('gulp-connect'); var open = require('gulp-open'); var gulpDocs = require('gulp-ngdocs'); +var documentationFiles = ['../Umbraco.Web.UI.Client/src/common/**/*.js', './src/api/**/*.ngdoc']; + /************************** * Build Backoffice UI API documentation **************************/ @@ -24,14 +26,14 @@ gulp.task('docs', [], function (cb) { return gulpDocs.sections({ api: { - glob: ['../Umbraco.Web.UI.Client/src/common/**/*.js', './src/api/**/*.ngdoc'], + glob: documentationFiles, api: true, title: 'UI API Documentation' } }) .pipe(gulpDocs.process(options)) .pipe(gulp.dest('./api')); - cb(); + }); gulp.task('connect:docs', function (cb) { @@ -44,6 +46,10 @@ gulp.task('connect:docs', function (cb) { cb(); }); +gulp.task('watch:docs', function (cb) { + return gulp.watch(documentationFiles, ['docs']); +}); + gulp.task('open:docs', function (cb) { var options = { @@ -55,5 +61,5 @@ gulp.task('open:docs', function (cb) { cb(); }); -gulp.task('watch', ['docs', 'connect:docs', 'open:docs']); +gulp.task('watch', ['docs', 'connect:docs', 'open:docs', 'watch:docs']); From 4ddd1a91edadbeb2c4b0829eaf1555af4bbb1a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 17 Jun 2020 15:41:08 +0200 Subject: [PATCH 92/96] make reload work --- src/Umbraco.Web.UI.Docs/gulpfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Docs/gulpfile.js b/src/Umbraco.Web.UI.Docs/gulpfile.js index 5dd4c57032..a7269ec8b7 100644 --- a/src/Umbraco.Web.UI.Docs/gulpfile.js +++ b/src/Umbraco.Web.UI.Docs/gulpfile.js @@ -32,7 +32,8 @@ gulp.task('docs', [], function (cb) { } }) .pipe(gulpDocs.process(options)) - .pipe(gulp.dest('./api')); + .pipe(gulp.dest('./api')) + .pipe(connect.reload()); }); From f0f92db3dcb00b83b5a20e17a4632318cda7e5b3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 17 Jun 2020 18:02:23 +0200 Subject: [PATCH 93/96] First compile all the gulp things before trying to make the docs --- build/V8-Docs-Pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/build/V8-Docs-Pipeline.yml b/build/V8-Docs-Pipeline.yml index 1df4092397..56e8fde60c 100644 --- a/build/V8-Docs-Pipeline.yml +++ b/build/V8-Docs-Pipeline.yml @@ -37,6 +37,7 @@ jobs: script: | $uenv=./build.ps1 -get -doc $uenv.SandboxNode() + $uenv.CompileBelle() $uenv.PrepareAngularDocs() $nugetsourceUmbraco = "https://api.nuget.org/v3/index.json" $uenv.PrepareCSharpDocs() From d49265f3bae2b3dcb4c19c7b95633440cbc33570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 18 Jun 2020 09:23:57 +0200 Subject: [PATCH 94/96] removed preinstall fix. --- src/Umbraco.Web.UI.Docs/package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Docs/package.json b/src/Umbraco.Web.UI.Docs/package.json index 6e58f958cf..f216b4b129 100644 --- a/src/Umbraco.Web.UI.Docs/package.json +++ b/src/Umbraco.Web.UI.Docs/package.json @@ -1,7 +1,6 @@ { "private": true, "scripts": { - "preinstall": "npx npm-force-resolutions", "docs": "gulp docs", "start": "gulp docs", "default": "gulp docs", @@ -9,9 +8,6 @@ "serve": "gulp watch", "watch": "gulp watch" }, - "resolutions": { - "graceful-fs": "4.2.3" - }, "devDependencies": { "gulp": "^3.9.1", "gulp-connect": "5.6.1", From 95cb7e72d1a8423b4e36b7e40127e0c473c33d8a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 18 Jun 2020 11:21:03 +0200 Subject: [PATCH 95/96] Clarify some property meanings on `IContent` --- src/Umbraco.Core/Models/IContent.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 6990a7f7da..7a4fc83253 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core.Models /// /// Gets a value indicating whether the content is published. /// + /// The property tells you which version of the content is currently published. bool Published { get; set; } PublishedState PublishedState { get; set; } @@ -32,10 +33,11 @@ namespace Umbraco.Core.Models /// /// Gets a value indicating whether the content has been edited. /// + /// Will return `true` once unpublished edits have been made after the version with has been published. bool Edited { get; set; } /// - /// Gets the published version identifier. + /// Gets the version identifier for the currently published version of the content. /// int PublishedVersionId { get; set; } @@ -129,6 +131,6 @@ namespace Umbraco.Core.Models /// /// IContent DeepCloneWithResetIdentities(); - + } } From 3b2d87950cf2a381c23e3089722c210c51c0464a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 18 Jun 2020 11:28:31 +0200 Subject: [PATCH 96/96] Fix replacement of baseurl in UI docs --- build/build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.ps1 b/build/build.ps1 index 185d1b8ff6..c2c5bdd232 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -475,7 +475,7 @@ # change baseUrl $BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/" $IndexPath = "./api/index.html" - (Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath + (Get-Content $IndexPath).replace('origin + location.href.substr(origin.length).replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath # zip it & $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Docs\api\*.*"