From 2630dcc81cd9f0f0c51114394114b29598bfb2f5 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 30 Apr 2021 15:51:49 +0100 Subject: [PATCH 01/81] Switched .csproj to new SDK-style With many thanks to... https://www.andybutland.dev/2021/03/umbraco-package-migration-to-net-core-false-start.html https://natemcmaster.com/blog/2017/03/09/vs2015-to-vs2017-upgrade/ --- .../Umbraco.Community.Contentment.csproj | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index c95d19d4..c3181d55 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -1,13 +1,12 @@ - - + - net472 + net5.0 Umbraco.Community.Contentment Our.Umbraco.Community.Contentment Contentment for Umbraco - Contentment, a collection of components for Umbraco 8. + Contentment, a collection of components for Umbraco. umbraco - 2.1.0-develop + 3.0.0-develop Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher @@ -17,9 +16,12 @@ git - + + - - - + From 062b8a71acb55f2cbe96b7577e444705669e828c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 30 Apr 2021 15:53:11 +0100 Subject: [PATCH 02/81] Incremented version number, v3.0.0-develop I'm unsure what major version this should be. My gut instinct was to v9.0 it, but then I'm tying myself to Umbraco versions, unable to make my own breaking-changes. I've gone with v3.0 for now. --- VERSION | 2 +- src/Umbraco.Community.Contentment/Properties/VersionInfo.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Community.Contentment/Properties/VersionInfo.cs diff --git a/VERSION b/VERSION index c66a1308..223d33e2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.0-develop \ No newline at end of file +3.0.0-develop \ No newline at end of file diff --git a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs new file mode 100644 index 00000000..4f261a13 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs @@ -0,0 +1,5 @@ +using System.Reflection; + +[assembly: AssemblyVersion("3.0")] +[assembly: AssemblyFileVersion("3.0.0")] +[assembly: AssemblyInformationalVersion("3.0.0-develop")] From d887c1ce9b9b299d1313006bb505b39a049308ad Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 11:52:00 +0100 Subject: [PATCH 03/81] Re-introduced post-build powershell script --- build/build-assets.ps1 | 2 +- src/.editorconfig | 8 ++++++-- .../Umbraco.Community.Contentment.csproj | 10 ++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/build/build-assets.ps1 b/build/build-assets.ps1 index 914bb28c..cf2fcfcf 100644 --- a/build/build-assets.ps1 +++ b/build/build-assets.ps1 @@ -31,7 +31,7 @@ if (Test-Path -Path $targetFolder) { } # Copy DLL / PDB -$binFolder = "${targetFolder}\bin"; +$binFolder = "${targetFolder}\bin\Debug\net5.0"; if (!(Test-Path -Path $binFolder)) {New-Item -Path $binFolder -Type Directory;} Copy-Item -Path "${TargetDir}${ProjectName}.*" -Destination $binFolder; diff --git a/src/.editorconfig b/src/.editorconfig index 8380e946..b241e164 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -1,5 +1,5 @@ # This .editorconfig has been taken from Umbraco CMS, licensed under MIT. -# https://github.com/umbraco/Umbraco-CMS/blob/release-8.1.0/.editorconfig +# https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/.editorconfig # top-most EditorConfig file root = true @@ -26,6 +26,7 @@ dotnet_naming_rule.private_members_with_underscore.severity = suggestion dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private +# dotnet_naming_symbols.private_fields.required_modifiers = abstract,async,readonly,static # all except const dotnet_naming_style.prefix_underscore.capitalization = camel_case dotnet_naming_style.prefix_underscore.required_prefix = _ @@ -37,5 +38,8 @@ csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion csharp_prefer_braces = false : none -[*.{js,less}] +[*.js] +trim_trailing_whitespace = true + +[*.less] trim_trailing_whitespace = false diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index c3181d55..183492c8 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-develop + 3.0.0 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher @@ -19,9 +19,7 @@ - + + + From f4402a75204ae590208a8f5b994ffd9569931220 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 11:58:01 +0100 Subject: [PATCH 04/81] Replaced the `ServerVariablesParser.Parsing` with `INotificationHandler<>` --- .../Composing/ContentmentComponent.cs | 19 +----------- .../ContentmentServerVariablesParsing.cs | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs index 9601f668..74f6b4df 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs @@ -39,26 +39,9 @@ public void Initialize() { var upgrader = new Upgrader(new ContentmentPlan()); upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger); - - ServerVariablesParser.Parsing += ServerVariablesParser_Parsing; } public void Terminate() - { - ServerVariablesParser.Parsing -= ServerVariablesParser_Parsing; - } - - private void ServerVariablesParser_Parsing(object sender, Dictionary e) - { - if (e.TryGetValueAs("umbracoPlugins", out Dictionary umbracoPlugins) == true && umbracoPlugins.ContainsKey(Constants.Internals.ProjectAlias) == false) - { - umbracoPlugins.Add(Constants.Internals.ProjectAlias, new - { - name = Constants.Internals.ProjectName, - version = Configuration.ContentmentVersion.SemanticVersion.ToSemanticString(), - telemetry = Telemetry.ContentmentTelemetryComponent.Disabled == false, - }); - } - } + { } } } diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs new file mode 100644 index 00000000..8663127f --- /dev/null +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs @@ -0,0 +1,30 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.WebAssets; +using Umbraco.Core; +using Umbraco.Extensions; + +namespace Umbraco.Community.Contentment.Composing +{ + internal sealed class ContentmentServerVariablesParsing : INotificationHandler + { + public void Handle(ServerVariablesParsing notification) + { + if (notification.ServerVariables.TryGetValueAs("umbracoPlugins", out Dictionary umbracoPlugins) == true && + umbracoPlugins.ContainsKey(Constants.Internals.ProjectAlias) == false) + { + umbracoPlugins.Add(Constants.Internals.ProjectAlias, new + { + name = Constants.Internals.ProjectName, + version = Configuration.ContentmentVersion.SemanticVersion.ToSemanticString(), + telemetry = Telemetry.ContentmentTelemetryComponent.Disabled == false, + }); + } + } + } +} From 085c3bbdd6d361c613bfb23f41d2419030fe0029 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:00:21 +0100 Subject: [PATCH 05/81] Updated the Composers from using `Composition` to `IUmbracoBuilder` --- .../Composing/CompositionExtensions.cs | 11 +++--- .../Composing/ContentmentComponent.cs | 24 ++++++------ .../Composing/ContentmentComposer.cs | 37 +++++++++++-------- .../ContentmentListItemCollectionBuilder.cs | 4 +- .../Configuration/ContentmentVersion.cs | 3 +- .../Telemetry/CompositionExtensions.cs | 32 ++++++++++------ .../Trees/CompositionExtensions.cs | 16 +++++--- 7 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs index c143d5fd..c73fc4dc 100644 --- a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Community.Contentment.Composing; using Umbraco.Community.Contentment.DataEditors; @@ -12,14 +13,14 @@ namespace Umbraco.Core.Composing { public static partial class CompositionExtensions { - public static ContentmentListItemCollectionBuilder ContentmentListItems(this Composition composition) + public static ContentmentListItemCollectionBuilder ContentmentListItems(this IUmbracoBuilder builder) { - return composition.WithCollectionBuilder(); + return builder.WithCollectionBuilder(); } - public static Composition UnlockContentment(this Composition composition) + public static IUmbracoBuilder UnlockContentment(this IUmbracoBuilder builder) { - composition + builder .WithCollectionBuilder() // Data List - Data Sources .Add() @@ -37,7 +38,7 @@ public static Composition UnlockContentment(this Composition composition) .Add() ; - return composition; + return builder; } } } diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs index 74f6b4df..f258798e 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs @@ -3,16 +3,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Community.Contentment.Migrations; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; -using Umbraco.Core.Migrations; -using Umbraco.Core.Migrations.Upgrade; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Web.JavaScript; namespace Umbraco.Community.Contentment.Composing { @@ -21,24 +18,27 @@ internal sealed class ContentmentComponent : IComponent private readonly IScopeProvider _scopeProvider; private readonly IMigrationBuilder _migrationBuilder; private readonly IKeyValueService _keyValueService; - private readonly IProfilingLogger _logger; + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; public ContentmentComponent( IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, - IProfilingLogger logger) + ILogger logger, + ILoggerFactory loggerFactory) { _scopeProvider = scopeProvider; _migrationBuilder = migrationBuilder; _keyValueService = keyValueService; _logger = logger; + _loggerFactory = loggerFactory; } public void Initialize() { var upgrader = new Upgrader(new ContentmentPlan()); - upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger); + upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger, _loggerFactory); } public void Terminate() diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs index 3518c021..e6e0c0bd 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs @@ -3,41 +3,46 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.WebAssets; using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Community.Contentment.Telemetry; -using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Web.Runtime; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Composing { - [ComposeAfter(typeof(WebInitialComposer))] - [RuntimeLevel(MinLevel = RuntimeLevel.Boot)] + // TODO: [LK:2021-04-03] v9 Review this. + //[ComposeAfter(typeof(WebInitialComposer))] internal sealed class ContentmentComposer : IUserComposer { - public void Compose(Composition composition) + public void Compose(IUmbracoBuilder builder) { - composition + builder .ContentmentListItems() - .Add(() => composition.TypeLoader.GetTypes()) + .Add(() => builder.TypeLoader.GetTypes()) ; - composition.RegisterUnique(); + builder.Services.AddUnique(); - if (composition.RuntimeState.Level > RuntimeLevel.Install) + //if (_runtimeState.Level > RuntimeLevel.Install) { - composition + builder .Components() .Append() ; + + builder.AddNotificationHandler(); } - if (composition.RuntimeState.Level == RuntimeLevel.Run) + //if (_runtimeState.Level == RuntimeLevel.Run) { - if (ContentmentTelemetryComponent.Disabled == false) - { - composition.EnableContentmentTelemetry(); - } + // if (ContentmentTelemetryComponent.Disabled == false) + // { + // builder.EnableContentmentTelemetry(); + // } } } } diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs index 1687ce14..82eb6cd2 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs @@ -6,9 +6,9 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using Umbraco.Cms.Core.Composing; using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Core; -using Umbraco.Core.Composing; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Composing { diff --git a/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs b/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs index 25688e59..23fcfd66 100644 --- a/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs +++ b/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs @@ -10,8 +10,7 @@ using System; using System.Reflection; -using Semver; -using Umbraco.Core; +using Umbraco.Cms.Core.Semver; namespace Umbraco.Community.Contentment.Configuration { diff --git a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs index 84b57a17..d591f94c 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs @@ -3,6 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; using Umbraco.Community.Contentment.Telemetry; // NOTE: This extension method class is deliberately using the Umbraco namespace, @@ -11,28 +14,33 @@ namespace Umbraco.Core.Composing { public static partial class CompositionExtensions { - public static Composition EnableContentmentTelemetry(this Composition composition) + public static IUmbracoBuilder EnableContentmentTelemetry(this IUmbracoBuilder builder) { ContentmentTelemetryComponent.Disabled = false; - composition - .Components() - .Append() - ; + // TODO: [LK:2021-04-30] v9 Review this. + //builder + // .Components() + // .Append() + //; - return composition; + // TODO: [LK:2021-04-30] v9 Maybe renamed this to `ContentmentTelemetryHandler` + builder.AddNotificationHandler, ContentmentTelemetryComponent>(); + + return builder; } - public static Composition DisableContentmentTelemetry(this Composition composition) + public static IUmbracoBuilder DisableContentmentTelemetry(this IUmbracoBuilder builder) { ContentmentTelemetryComponent.Disabled = true; - composition - .Components() - .Remove() - ; + // TODO: [LK:2021-04-30] v9 Review this. + //builder + // .Components() + // .Remove() + //; - return composition; + return builder; } } } diff --git a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs index b6d04d71..2a742aef 100644 --- a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs @@ -3,7 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Community.Contentment.Trees; +using Umbraco.Extensions; using Umbraco.Web; // NOTE: This extension method class is deliberately using the Umbraco namespace, @@ -12,14 +14,16 @@ namespace Umbraco.Core.Composing { public static partial class CompositionExtensions { - public static Composition DisableContentmentTree(this Composition composition) + public static IUmbracoBuilder DisableContentmentTree(this IUmbracoBuilder builder) { - composition - .Trees() - .RemoveTreeController() - ; + // TODO: [LK:2021-05-03] Commented out, as I had to comment out `ContentmentTreeController` for now. - return composition; + //builder + // .Trees() + // .RemoveTreeController() + //; + + return builder; } } } From 043d771c6b1bf9cafc0f292c355aa15024b31985 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:01:10 +0100 Subject: [PATCH 06/81] [WIP] Telemetry - replaced `DataTypeService.Saved` event hook with an `INotificationHandler<>` --- .../ContentmentTelemetryComponent.cs | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs index e202d5de..5dbd1ac1 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs @@ -9,50 +9,43 @@ using System.Net.Mime; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; using Umbraco.Community.Contentment.Configuration; using Umbraco.Community.Contentment.DataEditors; using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Telemetry { - internal sealed class ContentmentTelemetryComponent : IComponent + // TODO: [LK:2021-04-30] v9 Maybe renamed this to `ContentmentTelemetryHandler` + // Currently keeping as `ContentmentTelemetryComponent` for version-control tracking. + internal sealed class ContentmentTelemetryComponent : INotificationHandler> { - internal static bool Disabled { get; set; } - - private readonly IUmbracoSettingsSection _umbracoSettings; - - public ContentmentTelemetryComponent(IUmbracoSettingsSection umbracoSettings) - { - _umbracoSettings = umbracoSettings; - } + private readonly IUmbracoVersion _umbracoVersion; + private readonly IOptions _globalSettings; - public void Initialize() - { - DataTypeService.Saved += DataTypeService_Saved; - } + internal static bool Disabled { get; set; } - public void Terminate() + public ContentmentTelemetryComponent(IOptions globalSettings, IUmbracoVersion umbracoVersion) { - DataTypeService.Saved -= DataTypeService_Saved; + _globalSettings = globalSettings; + _umbracoVersion = umbracoVersion; } - private void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs e) + public void Handle(SavedNotification notification) { if (Disabled == true) { return; } - foreach (var entity in e.SavedEntities) + foreach (var entity in notification.SavedEntities) { if (entity.EditorAlias.InvariantStartsWith(Constants.Internals.DataEditorAliasPrefix) == true) { @@ -103,7 +96,7 @@ array[0] is JObject item && } } - var umbracoId = Guid.TryParse(_umbracoSettings.BackOffice.Id, out var telemetrySiteIdentifier) == true + var umbracoId = Guid.TryParse(_globalSettings.Value.Id, out var telemetrySiteIdentifier) == true ? telemetrySiteIdentifier : Guid.Empty; @@ -113,7 +106,7 @@ array[0] is JObject item && dataType = entity.Key, editorAlias = entity.EditorAlias.Substring(Constants.Internals.DataEditorAliasPrefix.Length), umbracoId = umbracoId, - umbracoVersion = UmbracoVersion.SemanticVersion.ToString(), + umbracoVersion = _umbracoVersion.SemanticVersion.ToString(), contentmentVersion = ContentmentVersion.SemanticVersion.ToString(), dataTypeConfig = dataTypeConfig, }; From 56e5e336b4f439decfe25ebda7d2acab2c32df97 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:09:05 +0100 Subject: [PATCH 07/81] Updated using namespaces - mostly from "Umbraco." to "Umbraco.Cms." --- .../Core/DictionaryExtensions.cs | 1 + .../Trees/TreeCollectionBuilderExtensions.cs | 3 ++- .../Core/Xml/UmbracoXPathPathSyntaxParser.cs | 1 + .../Buttons/ButtonsDataListEditor.cs | 7 ++++--- .../Bytes/BytesConfigurationEditor.cs | 5 +++-- .../DataEditors/Bytes/BytesDataEditor.cs | 8 ++++++-- .../DataEditors/Bytes/BytesValueConverter.cs | 6 +++--- .../CheckboxList/CheckboxListDataListEditor.cs | 2 +- .../CodeEditorConfigurationEditor.cs | 6 ++++-- .../CodeEditor/CodeEditorDataEditor.cs | 11 ++++++++--- .../CodeEditor/CodeEditorValueConverter.cs | 6 +++--- .../ConfigurationEditorDataEditor.cs | 2 +- .../ConfigurationEditorModel.cs | 2 +- .../ConfigurationEditorUtility.cs | 4 +++- .../ContentBlocks/ContentBlockPreviewModel.cs | 4 ++-- .../ContentBlocks/ContentBlockPreviewView.cs | 5 +++-- .../ContentBlocksConfigurationEditor.cs | 10 ++++++---- .../ContentBlocks/ContentBlocksDataEditor.cs | 10 +++++++--- .../ContentBlocksDataValueEditor.cs | 12 +++++++----- .../ContentBlocksTypesConfigurationField.cs | 7 ++++--- .../ContentBlocksValueConverter.cs | 10 +++++----- .../ContentBlocks/ContentTypeCacheHelper.cs | 4 ++-- .../DisplayModes/ListDisplayMode.cs | 3 ++- .../DisplayModes/StackDisplayMode.cs | 2 +- .../DataList/DataListApiController.cs | 11 ++++------- .../DataList/DataListConfigurationEditor.cs | 6 ++++-- .../DataEditors/DataList/DataListDataEditor.cs | 7 ++++++- .../DataList/DataListValueConverter.cs | 7 ++++--- .../DataSources/CountriesDataListSource.cs | 6 +++--- .../DataSources/CurrenciesDataListSource.cs | 6 +++--- .../DataList/DataSources/EnumDataListSource.cs | 9 +++++---- .../DataSources/ExamineDataListSource.cs | 10 ++++++---- .../DataList/DataSources/JsonDataListSource.cs | 7 ++++--- .../PhysicalFileSystemDataSource.cs | 8 ++++++-- .../DataList/DataSources/SqlDataListSource.cs | 10 ++++++---- .../DataSources/TextDelimitedDataListSource.cs | 8 +++++--- .../DataSources/TimeZoneDataListSource.cs | 4 ++-- .../UmbracoContentDataListSource.cs | 13 ++++++++----- .../UmbracoContentPropertiesDataListSource.cs | 12 +++++++----- .../UmbracoContentXPathDataListSource.cs | 15 +++++++++------ .../UmbracoDictionaryDataListSource.cs | 8 ++++---- .../DataSources/UmbracoEntityDataListSource.cs | 16 ++++++++++------ .../UmbracoImageCropDataListSource.cs | 12 ++++++------ .../UmbracoMemberGroupDataListSource.cs | 10 +++++----- .../UmbracoMembersDataListSource.cs | 18 ++++++++++-------- .../DataSources/UserDefinedDataListSource.cs | 6 ++++-- .../DataList/DataSources/XmlDataListSource.cs | 9 ++++++--- .../DataSources/uCssClassNameDataListSource.cs | 8 +++++--- .../DropdownList/DropdownListDataListEditor.cs | 3 ++- .../IconPickerConfigurationEditor.cs | 5 +++-- .../IconPicker/IconPickerDataEditor.cs | 10 +++++++--- .../IconPicker/IconPickerValueConverter.cs | 7 ++++--- .../ItemPicker/ItemPickerDataListEditor.cs | 5 +++-- .../Notes/NotesConfigurationEditor.cs | 5 +++-- .../Notes/NotesConfigurationField.cs | 4 +++- .../DataEditors/Notes/NotesDataEditor.cs | 9 +++++++-- .../DataEditors/Notes/NotesValueConverter.cs | 7 ++++--- .../NumberInputConfigurationEditor.cs | 5 +++-- .../NumberInput/NumberInputDataEditor.cs | 10 +++++++--- .../NumberInput/NumberInputValueConverter.cs | 7 ++++--- .../RadioButtonListDataListEditor.cs | 2 +- .../ReadOnly/ReadOnlyDataValueEditor.cs | 5 ++++- .../RenderMacroConfigurationEditor.cs | 5 +++-- .../RenderMacro/RenderMacroDataEditor.cs | 9 +++++++-- .../RenderMacro/RenderMacroValueConverter.cs | 7 ++++--- .../DataEditors/Tags/TagsDataListEditor.cs | 2 +- .../TemplatedListDataListEditor.cs | 5 +++-- .../TextInput/TextInputConfigurationEditor.cs | 6 ++++-- .../TextInput/TextInputDataEditor.cs | 11 ++++++++--- .../TextInput/TextInputValueConverter.cs | 7 ++++--- .../AllowClearConfigurationField.cs | 2 +- .../DisableSortingConfigurationField.cs | 2 +- .../EnableDevModeConfigurationField.cs | 2 +- .../EnableFilterConfigurationField.cs | 2 +- .../HideLabelConfigurationField.cs | 2 +- .../HtmlAttributesConfigurationField.cs | 5 +++-- .../MaxItemsConfigurationField.cs | 5 +++-- .../ShowDescriptionsConfigurationField.cs | 2 +- .../ShowIconsConfigurationField.cs | 2 +- .../DataEditors/_/IContentmentEditorItem.cs | 2 +- .../DataEditors/_/IContentmentListItem.cs | 2 +- .../Migrations/ContentmentPlan.cs | 2 +- .../Install/RegisterUmbracoPackageEntry.cs | 6 +++--- .../Trees/ContentmentTreeController.cs | 17 ++++++++++------- .../EnumDataListSourceApiController.cs | 11 +++++++---- .../PublishedCache/DetachedPublishedElement.cs | 2 +- .../DetachedPublishedProperty.cs | 4 ++-- .../PublishedContentContractResolver.cs | 2 +- 88 files changed, 341 insertions(+), 224 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs index e9ffae3f..2dc8b812 100644 --- a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs +++ b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs @@ -4,6 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +using Umbraco.Extensions; namespace Umbraco.Core { diff --git a/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs index 2d08ccca..231115b4 100644 --- a/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs +++ b/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs @@ -12,7 +12,8 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Umbraco.Web.Trees; +using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Web.BackOffice.Trees; namespace Umbraco.Web { diff --git a/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs index da52d87c..99d8812a 100644 --- a/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Umbraco.Cms.Core.Xml; namespace Umbraco.Core.Xml { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs index 4ac78b4a..82aecdbc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs @@ -4,9 +4,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs index 303284da..79fb970f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs @@ -4,8 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs index d9a0e921..25dc4d12 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs @@ -3,8 +3,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs index 9808efc7..3251a399 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs @@ -4,9 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs index c44a2933..fb86458b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs index e3df5f7f..efba7a3e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.IO; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Hosting; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs index bc0331de..862fe824 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs @@ -3,9 +3,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.PropertyEditors; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs index 2c331f35..4f5efeb6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs @@ -4,9 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs index e5f9dbb1..b2176601 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs @@ -11,6 +11,6 @@ internal sealed class ConfigurationEditorDataEditor internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Configuration Editor"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "configuration-editor.html"; internal const string DataEditorOverlayViewPath = Constants.Internals.EditorsPathRoot + "configuration-editor.overlay.html"; - internal const string DataEditorIcon = Core.Constants.Icons.Macro; + internal const string DataEditorIcon = Cms.Core.Constants.Icons.Macro; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs index 78505d1a..ea00adb9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs @@ -7,7 +7,7 @@ using System.ComponentModel; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs index 5e248f66..4df10fa1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs @@ -9,7 +9,9 @@ using System.Linq; using Umbraco.Community.Contentment.Composing; using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs index 4bf97bf9..b5899bce 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs @@ -3,8 +3,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index f8851469..148ad1b1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -4,10 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common.Views; using Umbraco.Community.Contentment.DataEditors; using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs index 55489715..9f2c62c8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs @@ -7,11 +7,13 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index 24e9fa7a..6a358f94 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -6,9 +6,13 @@ using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs index 9b1f3e44..f8032437 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs @@ -15,11 +15,13 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index 0fdf4c3a..1efdb77f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -6,9 +6,10 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs index bb093bb3..691dcb67 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs @@ -6,12 +6,12 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; using Umbraco.Community.Contentment.Web.PublishedCache; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.PublishedCache; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs index 9189a352..6acf5fba 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs @@ -10,8 +10,8 @@ using System; using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Services; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs index 4601ff37..47da4620 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs @@ -4,7 +4,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs index e1b6f495..5d3f55f5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs index 36d4b406..1a9c6d64 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs @@ -4,15 +4,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Web.Http; +using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Core; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Editors; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs index 5f697905..ed428d23 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs @@ -7,8 +7,10 @@ using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs index 710155a4..8e19f456 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs @@ -6,7 +6,12 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.IO; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs index fd85bf25..40a1e8ac 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs @@ -8,9 +8,10 @@ using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs index a83d98de..e14490f2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class CountriesDataListSource : IDataListSource { public string Name => ".NET Countries (ISO 3166)"; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs index e5454c5f..d4626939 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class CurrenciesDataListSource : IDataListSource { public string Name => ".NET Currencies (ISO 4217)"; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs index f90f8da4..4e26e568 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs @@ -8,13 +8,14 @@ using System.ComponentModel; using System.Linq; using System.Reflection; -using System.Runtime.Serialization; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; using Umbraco.Community.Contentment.Web.Controllers; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs index b3b60ab3..15c296c0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs @@ -8,15 +8,17 @@ using Examine; using Examine.LuceneEngine.Providers; using Examine.Search; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; using Umbraco.Examine; -using UmbConstants = Umbraco.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class ExamineDataListSource : IDataListSource { private readonly IExamineManager _examineManager; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs index 681f40e9..e6007131 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs @@ -9,11 +9,12 @@ using System.Linq; using System.Net; using System.Text; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs index f265ceab..0f1c0fcc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs @@ -6,8 +6,12 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Core.Hosting; +using Microsoft.Extensions.Logging; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index 6c0694e5..60bedcba 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -7,13 +7,15 @@ using System.Configuration; using System.Data.Common; using System.Data.SqlClient; -using System.Data.SqlServerCe; +//using System.Data.SqlServerCe; using System.IO; using System.Linq; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs index 890b947f..cb9fde8a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs @@ -7,10 +7,12 @@ using System.Collections.Generic; using System.IO; using System.Net; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs index b098653d..1de4af26 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs @@ -6,11 +6,11 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class TimeZoneDataListSource : IDataListSource, IDataListSourceValueConverter { public string Name => ".NET Time Zones (UTC)"; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index bc331555..afbb39fc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -6,13 +6,16 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; using Umbraco.Core.Xml; -using Umbraco.Web; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs index 0659e791..00a6b4b2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs @@ -8,14 +8,16 @@ using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; +using Umbraco.Cms.Core; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoContentPropertiesDataListSource : IDataListSource { private readonly IContentTypeService _contentTypeService; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs index c74f9524..f94d513c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs @@ -6,17 +6,20 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; using Umbraco.Core.Xml; -using Umbraco.Web; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoContentXPathDataListSource : IDataListSource, IDataListSourceValueConverter { private readonly IContentTypeService _contentTypeService; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs index ce802956..fda58f39 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs @@ -7,14 +7,14 @@ using System.Globalization; using System.Linq; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoDictionaryDataListSource : IDataListSource { private readonly ILocalizationService _localizationService; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index b975b5c3..3a74df33 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -7,15 +7,19 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; +using Umbraco.Extensions; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Core.IO; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoEntityDataListSource : IDataListSource, IDataListSourceValueConverter { internal static Dictionary SupportedEntityTypes = new Dictionary diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs index 4db1c9db..afbfe697 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs @@ -5,15 +5,15 @@ using System.Collections.Generic; using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoImageCropDataListSource : IDataListSource { private readonly IDataTypeService _dataTypeService; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs index 3dece000..2867e596 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs @@ -6,14 +6,14 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoMemberGroupDataListSource : IDataListSource, IDataListSourceValueConverter { private readonly IMemberGroupService _memberGroupService; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index 7b8df8ca..4081a0ac 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -7,18 +7,20 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.PublishedCache; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoMembersDataListSource : IDataListSource, IDataListSourceValueConverter { private readonly IMemberTypeService _memberTypeService; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs index 2dedbd1d..9d831585 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs @@ -7,17 +7,19 @@ using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UserDefinedDataListSource : IDataListSource { public string Name => "User-defined List"; public string Description => "Manually configure the items for the data source."; - public string Icon => Core.Constants.Icons.DataType; + public string Icon => Cms.Core.Constants.Icons.DataType; public string Group => default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index cf127f98..aeaa7fbe 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -10,9 +10,12 @@ using System.Xml; using System.Xml.XPath; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Hosting; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs index 5b7c1150..99275f89 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs @@ -7,13 +7,15 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public class uCssClassNameDataListSource : IDataListSource { public string Name => "uCssClassName"; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs index 8bc0740e..e5af87cf 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs @@ -4,7 +4,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs index 10a234d1..cc612c6d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs @@ -4,8 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs index 383252a7..ffac4aac 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs @@ -3,8 +3,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +18,7 @@ namespace Umbraco.Community.Contentment.DataEditors DataEditorName, DataEditorViewPath, ValueType = ValueTypes.String, - Group = Core.Constants.PropertyEditors.Groups.Pickers, + Group = Cms.Core.Constants.PropertyEditors.Groups.Pickers, Icon = DataEditorIcon)] public sealed class IconPickerDataEditor : DataEditor { diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs index e118455b..ad6eaf6e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs @@ -4,9 +4,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs index bdbc9819..361fd771 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs @@ -5,8 +5,9 @@ using System.Collections.Generic; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs index 641d067e..734afa6b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs @@ -4,8 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs index 9517446e..35a52326 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs @@ -4,7 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs index 463d4a0e..dd718251 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs @@ -4,8 +4,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs index 2363f91e..70903601 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs @@ -4,9 +4,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs index 262a8396..5d4c667a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs @@ -4,8 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs index e3db273f..1b110a97 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs @@ -3,8 +3,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +18,7 @@ namespace Umbraco.Community.Contentment.DataEditors DataEditorName, DataEditorViewPath, ValueType = ValueTypes.Integer, - Group = Core.Constants.PropertyEditors.Groups.Common, + Group = Cms.Core.Constants.PropertyEditors.Groups.Common, Icon = DataEditorIcon)] internal sealed class NumberInputDataEditor : DataEditor { diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs index 66425714..f51d561e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs @@ -4,9 +4,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs index 2ac3e3e5..a6359897 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs index e86a4d60..9727a9f9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs @@ -3,7 +3,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs index b656abea..d2b63db1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs @@ -4,8 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs index 5858f4a1..8ce0ff78 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs @@ -4,8 +4,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs index 49af4063..6e0b4849 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs @@ -4,9 +4,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs index 48d2faff..2833cd93 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs index c7fa2582..3b2a77da 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs @@ -5,8 +5,9 @@ using System.Collections.Generic; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs index 6b167d39..fef307f8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs @@ -7,8 +7,10 @@ using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs index 2a2447a0..e708d702 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs @@ -3,8 +3,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +19,7 @@ namespace Umbraco.Community.Contentment.DataEditors DataEditorName, DataEditorViewPath, ValueType = ValueTypes.String, - Group = Core.Constants.PropertyEditors.Groups.Common, + Group = Cms.Core.Constants.PropertyEditors.Groups.Common, Icon = DataEditorIcon)] public sealed class TextInputDataEditor : DataEditor { diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs index 2f040060..e0ec38e5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs @@ -4,9 +4,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs index 5ae52781..3c19005d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs index 865b6786..6f0a1698 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs index 95a30b63..0a5a3f5f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs index aca4fc7e..122ff1ae 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs index 332f7342..8b9e413d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs index a927a02f..f9936e8e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs @@ -4,8 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs index cfa9abf3..fd97f131 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs @@ -3,8 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs index 048da91a..4f5cc090 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs index 4890779d..d7fa6493 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs index 8cb9ebbc..08e90075 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.ComponentModel; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs index 100626ff..18d1a4c3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.ComponentModel; -using Umbraco.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs b/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs index 15341e28..24835c4c 100644 --- a/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs +++ b/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Core.Migrations; +using Umbraco.Cms.Infrastructure.Migrations; namespace Umbraco.Community.Contentment.Migrations { diff --git a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs index 3930120e..c937cad9 100644 --- a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs +++ b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs @@ -4,9 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Linq; -using Umbraco.Core.Migrations; -using Umbraco.Core.Models.Packaging; -using Umbraco.Core.Services; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations; namespace Umbraco.Community.Contentment.Migrations { diff --git a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs index 19fa13c3..7f97d572 100644 --- a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs +++ b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs @@ -3,13 +3,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Net.Http.Formatting; -using System.Web.Http.ModelBinding; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.Trees; -using Umbraco.Web.WebApi.Filters; -using UmbConstants = Umbraco.Core.Constants; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Web.BackOffice.Trees; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.ModelBinders; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.Trees { diff --git a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs index fe8c3872..35104650 100644 --- a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs +++ b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs @@ -7,11 +7,14 @@ using System.Collections.Generic; using System.ComponentModel; using System.Reflection; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Core; -using Umbraco.Web.Editors; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Web.Controllers { diff --git a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs index cd74a644..4e0754f5 100644 --- a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs +++ b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs @@ -11,7 +11,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Community.Contentment.Web.PublishedCache { diff --git a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs index 76584a0f..682cbdba 100644 --- a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs +++ b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs @@ -9,8 +9,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Community.Contentment.Web.PublishedCache { diff --git a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs index f17d84f8..963dabf4 100644 --- a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs +++ b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs @@ -17,7 +17,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; -using Umbraco.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Community.Contentment.Web.Serialization { From 7f7bb11bf87aff5e909036dc6334e7b82ed2a56d Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:23:51 +0100 Subject: [PATCH 08/81] `IIOHelper` **everywhere** MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plus a bunch of other dependencies to be injected! Oh and the odd inclusion of `new ConfigurationField`, as my old syntax sugar extension method got removed. C'est la vie `¯\_(ツ)_/¯` --- .../Buttons/ButtonsDataListEditor.cs | 15 ++++-- .../Bytes/BytesConfigurationEditor.cs | 6 +-- .../DataEditors/Bytes/BytesDataEditor.cs | 27 +++++++++-- .../CodeEditorConfigurationEditor.cs | 18 ++++---- .../CodeEditor/CodeEditorDataEditor.cs | 43 +++++++++++++++-- .../ConfigurationEditorUtility.cs | 14 +++--- .../ContentBlocksConfigurationEditor.cs | 19 ++++---- .../ContentBlocks/ContentBlocksDataEditor.cs | 46 ++++++++++++++++--- .../ContentBlocksDataValueEditor.cs | 18 +++++--- .../ContentBlocksTypesConfigurationField.cs | 13 ++++-- .../DisplayModes/ListDisplayMode.cs | 9 +++- .../DataList/DataListConfigurationEditor.cs | 15 +++--- .../DataList/DataListDataEditor.cs | 44 +++++++++++++++--- .../DataSources/EnumDataListSource.cs | 25 ++++++---- .../DataSources/ExamineDataListSource.cs | 14 ++++-- .../DataSources/JsonDataListSource.cs | 33 +++++++------ .../PhysicalFileSystemDataSource.cs | 23 ++++++++-- .../DataList/DataSources/SqlDataListSource.cs | 17 ++++--- .../TextDelimitedDataListSource.cs | 29 +++++++----- .../UmbracoContentDataListSource.cs | 12 ++++- .../UmbracoContentPropertiesDataListSource.cs | 9 +++- .../UmbracoContentXPathDataListSource.cs | 12 ++++- .../UmbracoEntityDataListSource.cs | 13 ++++-- .../UmbracoMembersDataListSource.cs | 12 +++-- .../DataList/DataSources/XmlDataListSource.cs | 27 +++++++---- .../uCssClassNameDataListSource.cs | 15 +++++- .../DropdownListDataListEditor.cs | 9 +++- .../IconPickerConfigurationEditor.cs | 4 +- .../IconPicker/IconPickerDataEditor.cs | 19 ++++++-- .../ItemPicker/ItemPickerDataListEditor.cs | 18 +++++--- .../Notes/NotesConfigurationEditor.cs | 4 +- .../Notes/NotesConfigurationField.cs | 4 +- .../DataEditors/Notes/NotesDataEditor.cs | 39 ++++++++++++++-- .../NumberInputConfigurationEditor.cs | 4 +- .../NumberInput/NumberInputDataEditor.cs | 26 +++++++++-- .../ReadOnly/ReadOnlyDataValueEditor.cs | 14 ++++++ .../RenderMacroConfigurationEditor.cs | 4 +- .../RenderMacro/RenderMacroDataEditor.cs | 41 +++++++++++++++-- .../TemplatedListDataListEditor.cs | 13 ++++-- .../TextInput/TextInputConfigurationEditor.cs | 10 ++-- .../TextInput/TextInputDataEditor.cs | 19 ++++++-- .../HtmlAttributesConfigurationField.cs | 4 +- .../MaxItemsConfigurationField.cs | 4 +- .../EnumDataListSourceApiController.cs | 9 +++- 44 files changed, 584 insertions(+), 189 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs index 82aecdbc..f8adbed7 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs @@ -15,6 +15,13 @@ public sealed class ButtonsDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "buttons.html"; + private readonly IIOHelper _ioHelper; + + public ButtonsDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Buttons"; public string Description => "Select multiple values from a group of buttons."; @@ -30,14 +37,14 @@ public sealed class ButtonsDataListEditor : IDataListEditor Key = "defaultIcon", Name = "Default icon", Description = "Select an icon to be displayed as the default icon,
(for when no icon is available).", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), + View = _ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), }, new ConfigurationField { Key = "size", Name = "Size", Description = "Select the button size. By default this is set to 'medium'.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -55,7 +62,7 @@ public sealed class ButtonsDataListEditor : IDataListEditor Key = "labelStyle", Name = "Label style", Description = "Select the style of the button's label.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -81,7 +88,7 @@ public sealed class ButtonsDataListEditor : IDataListEditor public Dictionary DefaultValues => new Dictionary { - { "defaultIcon", Core.Constants.Icons.DefaultIcon }, + { "defaultIcon", UmbConstants.Icons.DefaultIcon }, { "labelStyle", "both" }, }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs index 79fb970f..1da1a2f5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs @@ -17,14 +17,14 @@ internal sealed class BytesConfigurationEditor : ConfigurationEditor internal const string Format = "format"; internal const string Kilo = "kilo"; - public BytesConfigurationEditor() + public BytesConfigurationEditor(IIOHelper ioHelper) { Fields.Add(new ConfigurationField { Key = Kilo, Name = "Kilobytes?", Description = "How many bytes do you prefer in your kilobyte?", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -43,7 +43,7 @@ public BytesConfigurationEditor() Key = Decimals, Name = "Decimal places", Description = "How many decimal places would you like?", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/slider/slider.html"), + View = ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/slider/slider.html"), Config = new Dictionary { { "initVal1", 2 }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs index 25dc4d12..2cf53322 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs @@ -27,10 +27,29 @@ public sealed class BytesDataEditor : DataEditor internal const string DataEditorViewPath = "readonlyvalue"; internal const string DataEditorIcon = "icon-binarycode"; - public BytesDataEditor(ILogger logger) - : base(logger) - { } + private readonly IIOHelper _ioHelper; - protected override IConfigurationEditor CreateConfigurationEditor() => new BytesConfigurationEditor(); + public BytesDataEditor( + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + EditorType type = EditorType.PropertyValue) + : base( + loggerFactory, + dataTypeService, + localizationService, + localizedTextService, + shortStringHelper, + jsonSerializer, + type) + { + _ioHelper = ioHelper; + } + + protected override IConfigurationEditor CreateConfigurationEditor() => new BytesConfigurationEditor(_ioHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs index efba7a3e..1385e4ee 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs @@ -22,11 +22,13 @@ internal sealed class CodeEditorConfigurationEditor : ConfigurationEditor internal const string MinLines = "minLines"; internal const string MaxLines = "maxLines"; - public CodeEditorConfigurationEditor() + public CodeEditorConfigurationEditor( + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) : base() { var targetPath = "~/umbraco/lib/ace-builds/src-min-noconflict/"; - var aceEditorPath = IOHelper.MapPath(targetPath); + var aceEditorPath = hostingEnvironment.MapPathWebRoot(targetPath); if (Directory.Exists(aceEditorPath) == true) { @@ -60,7 +62,7 @@ public CodeEditorConfigurationEditor() Key = Mode, Name = "Language mode", Description = "Select the programming language mode. The default mode is 'Razor'.", - View = IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -77,7 +79,7 @@ public CodeEditorConfigurationEditor() Key = Theme, Name = nameof(Theme), Description = "Set the theme for the code editor. The default theme is 'Chrome'.", - View = IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -88,7 +90,7 @@ public CodeEditorConfigurationEditor() if (modes.Count > 0 || themes.Count > 0) { - Fields.Add(new NotesConfigurationField($@"
+ Fields.Add(new NotesConfigurationField(ioHelper, $@"
Would you like to add more language modes and themes?

This property editor makes use of AWS Cloud 9's Ace editor library that is distributed with Umbraco. By default, Umbraco ships a streamlined set of programming language modes and themes.

If you would like to add more modes and themes, you can do this by downloading the latest pre-packaged version of the Ace editor and copy any of the mode-* or theme-* files from the src-min-noconflict folder over to the {targetPath} folder in this Umbraco installation.

@@ -104,7 +106,7 @@ public CodeEditorConfigurationEditor() Key = FontSize, Name = "Font size", Description = @"Set the font size. The value must be a valid CSS font-size value. The default size is 'small'.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] { @@ -151,7 +153,7 @@ public CodeEditorConfigurationEditor() Key = MinLines, Name = "Minimum lines", Description = "Set the minimum number of lines that the editor will be. The default is 12 lines.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath) + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath) }); DefaultConfiguration.Add(MaxLines, 30); @@ -160,7 +162,7 @@ public CodeEditorConfigurationEditor() Key = MaxLines, Name = "Maximum lines", Description = "Set the maximum number of lines that the editor can be. If left empty, the editor will not auto-scale.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath) + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath) }); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs index 862fe824..52eace6a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs @@ -29,12 +29,45 @@ internal sealed class CodeEditorDataEditor : DataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "code-editor.html"; internal const string DataEditorIcon = "icon-fa fa-code"; - public CodeEditorDataEditor(ILogger logger) - : base(logger) - { } + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; - protected override IConfigurationEditor CreateConfigurationEditor() => new CodeEditorConfigurationEditor(); + public CodeEditorDataEditor( + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + EditorType type = EditorType.PropertyValue) + : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer, type) + { + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; + _dataTypeService = dataTypeService; + _localizationService = localizationService; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + } - protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor(Attribute); + protected override IConfigurationEditor CreateConfigurationEditor() => new CodeEditorConfigurationEditor( + _hostingEnvironment, + _ioHelper); + + protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor( + _dataTypeService, + _localizationService, + Attribute, + _localizedTextService, + _shortStringHelper, + _jsonSerializer); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs index 4df10fa1..1f9422a7 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs @@ -42,13 +42,13 @@ public T GetConfigurationEditor(string key) return default; } - public ConfigurationEditorModel GetConfigurationEditorModel(bool ignoreFields = false) + public ConfigurationEditorModel GetConfigurationEditorModel(IShortStringHelper shortStringHelper, bool ignoreFields = false) where T : IContentmentEditorItem { - return GetConfigurationEditorModel(GetConfigurationEditor(typeof(T).GetFullNameWithAssembly()), ignoreFields); + return GetConfigurationEditorModel(GetConfigurationEditor(typeof(T).GetFullNameWithAssembly()), shortStringHelper, ignoreFields); } - public ConfigurationEditorModel GetConfigurationEditorModel(T item, bool ignoreFields = false) + public ConfigurationEditorModel GetConfigurationEditorModel(T item, IShortStringHelper shortStringHelper, bool ignoreFields = false) where T : IContentmentEditorItem { var type = item.GetType(); @@ -60,9 +60,9 @@ public ConfigurationEditorModel GetConfigurationEditorModel(T item, bool igno return new ConfigurationEditorModel { Key = type.GetFullNameWithAssembly(), - Name = item.Name ?? type.Name.SplitPascalCasing(), + Name = item.Name ?? type.Name.SplitPascalCasing(shortStringHelper), Description = item.Description, - Icon = item.Icon ?? Core.Constants.Icons.DefaultIcon, + Icon = item.Icon ?? Cms.Core.Constants.Icons.DefaultIcon, Group = item.Group, Fields = fields, DefaultValues = item.DefaultValues, @@ -70,7 +70,7 @@ public ConfigurationEditorModel GetConfigurationEditorModel(T item, bool igno }; } - public IEnumerable GetConfigurationEditorModels(bool ignoreFields = false) + public IEnumerable GetConfigurationEditorModels(IShortStringHelper shortStringHelper, bool ignoreFields = false) where T : IContentmentEditorItem { var models = new List(); @@ -79,7 +79,7 @@ public IEnumerable GetConfigurationEditorModels(boo { if (item is T editorItem) { - models.Add(GetConfigurationEditorModel(editorItem, ignoreFields)); + models.Add(GetConfigurationEditorModel(editorItem, shortStringHelper, ignoreFields)); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs index 9f2c62c8..e34ecf44 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs @@ -22,22 +22,25 @@ internal sealed class ContentBlocksConfigurationEditor : ConfigurationEditor private readonly Dictionary _elementTypes; private readonly Lazy> _elementBlueprints; private readonly ConfigurationEditorUtility _utility; - + private readonly IIOHelper _ioHelper; internal const string DisplayMode = "displayMode"; public ContentBlocksConfigurationEditor( IContentService contentService, IContentTypeService contentTypeService, - ConfigurationEditorUtility utility) + ConfigurationEditorUtility utility, + IIOHelper ioHelper, + IShortStringHelper shortStringHelper) : base() { _utility = utility; + _ioHelper = ioHelper; // NOTE: Gets all the elementTypes and blueprints upfront, rather than several hits inside the loop. _elementTypes = contentTypeService.GetAllElementTypes().ToDictionary(x => x.Key); _elementBlueprints = new Lazy>(() => contentService.GetBlueprintsForContentTypes(_elementTypes.Values.Select(x => x.Id).ToArray()).ToLookup(x => x.ContentTypeId)); - var displayModes = utility.GetConfigurationEditorModels(); + var displayModes = utility.GetConfigurationEditorModels(shortStringHelper); // NOTE: Sets the default display mode to be the Blocks. var defaultDisplayMode = displayModes.FirstOrDefault(x => x.Key.InvariantEquals(typeof(BlocksDisplayMode).GetFullNameWithAssembly())); @@ -51,21 +54,21 @@ public ContentBlocksConfigurationEditor( Key = DisplayMode, Name = "Display mode", Description = "Select and configure how to display the blocks in the editor.", - View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath), Config = new Dictionary() { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureDisplayMode" }, { Constants.Conventions.ConfigurationFieldAliases.Items, displayModes }, { MaxItemsConfigurationField.MaxItems, 1 }, { DisableSortingConfigurationField.DisableSorting, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, } }); - Fields.Add(new ContentBlocksTypesConfigurationField(_elementTypes.Values)); + Fields.Add(new ContentBlocksTypesConfigurationField(_elementTypes.Values, ioHelper)); Fields.Add(new EnableFilterConfigurationField()); - Fields.Add(new MaxItemsConfigurationField()); + Fields.Add(new MaxItemsConfigurationField(ioHelper)); Fields.Add(new DisableSortingConfigurationField()); Fields.Add(new EnableDevModeConfigurationField()); } @@ -167,7 +170,7 @@ public override IDictionary ToValueEditor(object configuration) if (config.ContainsKey(Constants.Conventions.ConfigurationFieldAliases.OverlayView) == false) { - config.Add(Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ContentBlocksDataEditor.DataEditorOverlayViewPath)); + config.Add(Constants.Conventions.ConfigurationFieldAliases.OverlayView, _ioHelper.ResolveRelativeOrVirtualUrl(ContentBlocksDataEditor.DataEditorOverlayViewPath)); } return config; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index 6a358f94..4c51bb40 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -27,21 +27,36 @@ public sealed class ContentBlocksDataEditor : IDataEditor private readonly IContentService _contentService; private readonly IContentTypeService _contentTypeService; private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; private readonly Lazy _propertyEditors; private readonly ConfigurationEditorUtility _utility; + private readonly IIOHelper _ioHelper; public ContentBlocksDataEditor( IContentService contentService, IContentTypeService contentTypeService, - IDataTypeService dataTypeService, Lazy propertyEditors, - ConfigurationEditorUtility utility) + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + ConfigurationEditorUtility utility, + IIOHelper ioHelper) { _contentService = contentService; _contentTypeService = contentTypeService; _dataTypeService = dataTypeService; + _localizationService = localizationService; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; _propertyEditors = propertyEditors; _utility = utility; + _ioHelper = ioHelper; } public string Alias => DataEditorAlias; @@ -52,7 +67,7 @@ public ContentBlocksDataEditor( public string Icon => DataEditorIcon; - public string Group => Core.Constants.PropertyEditors.Groups.RichContent; + public string Group => Cms.Core.Constants.PropertyEditors.Groups.RichContent; public bool IsDeprecated => false; @@ -60,11 +75,23 @@ public ContentBlocksDataEditor( public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new ContentBlocksConfigurationEditor(_contentService, _contentTypeService, _utility); + public IConfigurationEditor GetConfigurationEditor() => new ContentBlocksConfigurationEditor( + _contentService, + _contentTypeService, + _utility, + _ioHelper, + _shortStringHelper); public IDataValueEditor GetValueEditor() { - return new ContentBlocksDataValueEditor(_contentTypeService, _dataTypeService, _propertyEditors.Value) + return new ContentBlocksDataValueEditor( + _contentTypeService, + _propertyEditors.Value, + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { ValueType = ValueTypes.Json, View = DataEditorViewPath, @@ -97,7 +124,14 @@ public IDataValueEditor GetValueEditor(object configuration) } } - return new ContentBlocksDataValueEditor(_contentTypeService, _dataTypeService, _propertyEditors.Value) + return new ContentBlocksDataValueEditor( + _contentTypeService, + _propertyEditors.Value, + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { Configuration = configuration, ValueType = ValueTypes.Json, diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs index f8032437..804e64f4 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs @@ -33,27 +33,31 @@ internal sealed class ContentBlocksDataValueEditor : DataValueEditor public ContentBlocksDataValueEditor( IContentTypeService contentTypeService, + PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, - PropertyEditorCollection propertyEditors) - : base() + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) + : base(dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer) { _dataTypeService = dataTypeService; _elementTypes = new Lazy>(() => contentTypeService.GetAllElementTypes().ToDictionary(x => x.Key)); _propertyEditors = propertyEditors; } - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, string culture = null, string segment = null) { var value = property.GetValue(culture, segment)?.ToString(); if (string.IsNullOrWhiteSpace(value) == true) { - return base.ToEditor(property, dataTypeService, culture, segment); + return base.ToEditor(property, culture, segment); } var blocks = JsonConvert.DeserializeObject>(value); if (blocks == null) { - return base.ToEditor(property, dataTypeService, culture, segment); + return base.ToEditor(property, culture, segment); } foreach (var block in blocks) @@ -88,7 +92,7 @@ public override object ToEditor(Property property, IDataTypeService dataTypeServ continue; } - var convertedValue = propertyEditor.GetValueEditor()?.ToEditor(fakeProperty, dataTypeService); + var convertedValue = propertyEditor.GetValueEditor()?.ToEditor(fakeProperty); block.Value[key] = convertedValue != null ? JToken.FromObject(convertedValue) @@ -144,7 +148,7 @@ public override object FromEditor(ContentPropertyData editorValue, object curren { ContentKey = block.Key, PropertyTypeKey = propertyType.Key, - Files = new ContentPropertyFile[0] + Files = Array.Empty() }; var convertedValue = propertyEditor.GetValueEditor(configuration)?.FromEditor(contentPropertyData, block.Value[key]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index 1efdb77f..9e0d64a7 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -16,9 +16,12 @@ namespace Umbraco.Community.Contentment.DataEditors internal sealed class ContentBlocksTypesConfigurationField : ConfigurationField { internal const string ContentBlockTypes = "contentBlockTypes"; + private readonly IIOHelper _ioHelper; - public ContentBlocksTypesConfigurationField(IEnumerable elementTypes) + public ContentBlocksTypesConfigurationField(IEnumerable elementTypes, IIOHelper ioHelper) { + _ioHelper = ioHelper; + var items = elementTypes .OrderBy(x => x.Name) .Select(x => new ConfigurationEditorModel @@ -38,13 +41,13 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp Key = ContentBlockTypes; Name = "Block types"; Description = "Configure the block types to use."; - View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath); + View = ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath); Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureElementType" }, { "allowDuplicates", Constants.Values.False }, { EnableFilterConfigurationField.EnableFilter, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { Constants.Conventions.ConfigurationFieldAliases.Items, items }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, }; @@ -58,7 +61,7 @@ private IEnumerable GetConfigurationFields(IContentType cont { Key = "elementType", Name = "Element type", - View = IOHelper.ResolveUrl(Constants.Internals.EditorsPathRoot + "readonly-node-preview.html"), + View = _ioHelper.ResolveRelativeOrVirtualUrl(Constants.Internals.EditorsPathRoot + "readonly-node-preview.html"), Config = new Dictionary { { "name", contentType.Name }, @@ -79,7 +82,7 @@ private IEnumerable GetConfigurationFields(IContentType cont Key = "overlaySize", Name = "Editor overlay size", Description = "Select the size of the overlay editing panel. By default this is set to 'small'. However if the editor fields require a wider panel, please select 'medium' or 'large'.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs index 47da4620..b239847d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs @@ -11,6 +11,13 @@ namespace Umbraco.Community.Contentment.DataEditors { internal class ListDisplayMode : IContentBlocksDisplayMode { + private readonly IIOHelper _ioHelper; + + public ListDisplayMode(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "List"; public string Description => "Blocks will be displayed in a list similar to a content picker."; @@ -30,7 +37,7 @@ internal class ListDisplayMode : IContentBlocksDisplayMode public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
A note about block type previews.

Unfortunately, the preview feature for block types is unsupported in the {Name} display mode and will be disabled.

", true), diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs index ed428d23..71038ccb 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs @@ -23,23 +23,24 @@ internal sealed class DataListConfigurationEditor : ConfigurationEditor internal const string EditorView = "editorView"; private readonly ConfigurationEditorUtility _utility; + private readonly IIOHelper _ioHelper; - public DataListConfigurationEditor(ConfigurationEditorUtility utility) + public DataListConfigurationEditor(ConfigurationEditorUtility utility, IIOHelper ioHelper, IShortStringHelper shortStringHelper) : base() { _utility = utility; - - var configEditorViewPath = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath); + _ioHelper = ioHelper; + var configEditorViewPath = _ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath); var defaultConfigEditorConfig = new Dictionary { { MaxItemsConfigurationField.MaxItems, 1 }, { DisableSortingConfigurationField.DisableSorting, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, _ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, }; - var dataSources = utility.GetConfigurationEditorModels().ToList(); - var listEditors = utility.GetConfigurationEditorModels().ToList(); + var dataSources = utility.GetConfigurationEditorModels(shortStringHelper).ToList(); + var listEditors = utility.GetConfigurationEditorModels(shortStringHelper).ToList(); Fields.Add(new ConfigurationField { @@ -73,7 +74,7 @@ public DataListConfigurationEditor(ConfigurationEditorUtility utility) { Key = "preview", Name = "Preview", - View = IOHelper.ResolveUrl(DataListDataEditor.DataEditorPreviewViewPath) + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataListDataEditor.DataEditorPreviewViewPath) }); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs index 8e19f456..1c26835b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs @@ -24,9 +24,31 @@ public sealed class DataListDataEditor : IDataEditor internal const string DataEditorListEditorViewPath = Constants.Internals.EditorsPathRoot + "data-list.editor.html"; internal const string DataEditorIcon = "icon-fa fa-list-ul"; + private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; private readonly ConfigurationEditorUtility _utility; - - public DataListDataEditor(ConfigurationEditorUtility utility) => _utility = utility; + private readonly IIOHelper _ioHelper; + + public DataListDataEditor( + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + ConfigurationEditorUtility utility, + IIOHelper ioHelper) + { + _dataTypeService = dataTypeService; + _localizationService = localizationService; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _utility = utility; + _ioHelper = ioHelper; + } public string Alias => DataEditorAlias; @@ -36,7 +58,7 @@ public sealed class DataListDataEditor : IDataEditor public string Icon => DataEditorIcon; - public string Group => Core.Constants.PropertyEditors.Groups.Lists; + public string Group => Cms.Core.Constants.PropertyEditors.Groups.Lists; public bool IsDeprecated => false; @@ -44,11 +66,16 @@ public sealed class DataListDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new DataListConfigurationEditor(_utility); + public IConfigurationEditor GetConfigurationEditor() => new DataListConfigurationEditor(_utility, _ioHelper, _shortStringHelper); public IDataValueEditor GetValueEditor() { - return new DataValueEditor + return new DataValueEditor( + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { ValueType = ValueTypes.Json, View = DataEditorViewPath, @@ -78,7 +105,12 @@ public IDataValueEditor GetValueEditor(object configuration) } } - return new DataValueEditor + return new DataValueEditor( + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { Configuration = configuration, ValueType = ValueTypes.Json, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs index 4e26e568..883459f1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs @@ -21,11 +21,18 @@ namespace Umbraco.Community.Contentment.DataEditors { public sealed class EnumDataListSource : IDataListSource, IDataListSourceValueConverter { - private readonly ILogger _logger; - - public EnumDataListSource(ILogger logger) + private readonly ILogger _logger; + private readonly IShortStringHelper _shortStringHelper; + private readonly IIOHelper _ioHelper; + + public EnumDataListSource( + ILogger logger, + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) { _logger = logger; + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; } public string Name => ".NET Enumeration"; @@ -47,7 +54,7 @@ public EnumDataListSource(ILogger logger) Key = "enumType", Name = "Enumeration type", Description = "Select the enumeration from an assembly type.", - View = CascadingDropdownListDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(CascadingDropdownListDataEditor.DataEditorViewPath), Config = new Dictionary { { CascadingDropdownListDataEditor.APIs, new[] @@ -95,7 +102,7 @@ public IEnumerable GetItems(Dictionary config) Description = attr?.Description ?? attr2?.Description, Disabled = attr?.Disabled ?? false, Icon = attr?.Icon, - Name = attr?.Name ?? field.Name.SplitPascalCasing(), + Name = attr?.Name ?? field.Name.SplitPascalCasing(_shortStringHelper), Value = attr3?.Value ?? attr?.Value ?? field.Name }); } @@ -116,11 +123,11 @@ public Type GetValueType(Dictionary config) if (enumType?.Length > 1) { var assembly = default(Assembly); - try { assembly = Assembly.Load(enumType[0]); } catch (Exception ex) { _logger.Error(ex); } + try { assembly = Assembly.Load(enumType[0]); } catch (Exception ex) { _logger.LogError(ex, "Unable to load target type."); } if (assembly != null) { var type = default(Type); - try { type = assembly.GetType(enumType[1]); } catch (Exception ex) { _logger.Error(ex); } + try { type = assembly.GetType(enumType[1]); } catch (Exception ex) { _logger.LogError(ex, "Unable to retrieve target type."); } if (type != null && type.IsEnum == true) { return type; @@ -137,7 +144,7 @@ public object ConvertValue(Type type, string value) if (string.IsNullOrWhiteSpace(value) == false && type?.IsEnum == true) { // NOTE: Can't use `Enum.TryParse` here, as it's only available with generic types in .NET 4.8. - try { return Enum.Parse(type, value, true); } catch (Exception ex) { _logger.Error(ex); } + try { return Enum.Parse(type, value, true); } catch (Exception ex) { _logger.LogError(ex, "Unable to parse Enum."); } // If the value doesn't match the Enum field, then it is most likely set with `DataListItemAttribute.Value`. var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); @@ -151,7 +158,7 @@ public object ConvertValue(Type type, string value) } } - _logger.Debug($"Unable to find value '{value}' in enum '{type.FullName}'."); + _logger.LogDebug($"Unable to find value '{value}' in enum '{type.FullName}'."); } return type.GetDefaultValue(); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs index 15c296c0..e25cc1e1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs @@ -22,7 +22,8 @@ namespace Umbraco.Community.Contentment.DataEditors public sealed class ExamineDataListSource : IDataListSource { private readonly IExamineManager _examineManager; - + private readonly IShortStringHelper _shortStringHelper; + private readonly IIOHelper _ioHelper; private const string _defaultNameField = "nodeName"; private const string _defaultValueField = UmbracoExamineIndex.NodeKeyFieldName; private const string _defaultIconField = UmbracoExamineIndex.IconFieldName; @@ -60,9 +61,14 @@ public sealed class ExamineDataListSource : IDataListSource }, }; - public ExamineDataListSource(IExamineManager examineManager) + public ExamineDataListSource( + IExamineManager examineManager, + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) { _examineManager = examineManager; + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; } public string Name => "Examine Query"; @@ -86,10 +92,10 @@ public ExamineDataListSource(IExamineManager examineManager) Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, - { Constants.Conventions.ConfigurationFieldAliases.Items, _examineManager.Indexes.OrderBy(x => x.Name).Select(x => new DataListItem { Name = x.Name.SplitPascalCasing(), Value = x.Name }) }, + { Constants.Conventions.ConfigurationFieldAliases.Items, _examineManager.Indexes.OrderBy(x => x.Name).Select(x => new DataListItem { Name = x.Name.SplitPascalCasing(_shortStringHelper), Value = x.Name }) }, } }, - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with Lucene query?

If you need assistance with Lucene query syntax, please refer to this resource on our.umbraco.com.

", true), diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs index e6007131..486aff30 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs @@ -20,11 +20,18 @@ namespace Umbraco.Community.Contentment.DataEditors { public sealed class JsonDataListSource : IDataListSource { - private readonly ILogger _logger; - - public JsonDataListSource(ILogger logger) + private readonly ILogger _logger; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + + public JsonDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) { _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; } public string Name => "JSON Data"; @@ -39,7 +46,7 @@ public JsonDataListSource(ILogger logger) public IEnumerable Fields => new[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with JSONPath expressions?

This data-source uses Newtonsoft's Json.NET library, with this we are limited to extracting only the 'value' from any key/value-pairs.

If you need assistance with JSONPath syntax, please refer to this resource: goessner.net/articles/JsonPath.

@@ -128,7 +135,7 @@ public IEnumerable GetItems(Dictionary config) if (tokens.Any() == false) { - _logger.Warn($"The JSONPath '{itemsJsonPath}' did not match any items in the JSON."); + _logger.LogWarning($"The JSONPath '{itemsJsonPath}' did not match any items in the JSON."); return Enumerable.Empty(); } @@ -159,13 +166,13 @@ public IEnumerable GetItems(Dictionary config) // How should we log if either name or value is empty? Note that empty or missing values are totally legal according to json if (name == null) { - _logger.Warn($"The JSONPath '{nameJsonPath}' did not match a 'name' in the item JSON."); + _logger.LogWarning($"The JSONPath '{nameJsonPath}' did not match a 'name' in the item JSON."); } // If value is missing we'll skip this specific item and log as a warning if (value == null) { - _logger.Warn($"The JSONPath '{valueJsonPath}' did not match a 'value' in the item XML. The item was skipped."); + _logger.LogWarning($"The JSONPath '{valueJsonPath}' did not match a 'value' in the item XML. The item was skipped."); continue; } @@ -182,7 +189,7 @@ public IEnumerable GetItems(Dictionary config) } catch (Exception ex) { - _logger.Error(ex, "Error finding items in the JSON. Please check the syntax of your JSONPath expressions."); + _logger.LogError(ex, "Error finding items in the JSON. Please check the syntax of your JSONPath expressions."); } return Enumerable.Empty(); @@ -203,27 +210,27 @@ private JToken GetJson(string url) } catch (WebException ex) { - _logger.Error(ex, $"Unable to fetch remote data from URL: {url}"); + _logger.LogError(ex, $"Unable to fetch remote data from URL: {url}"); } } else { // assume local file - var path = IOHelper.MapPath(url); + var path = _hostingEnvironment.MapPathWebRoot(url); if (File.Exists(path) == true) { content = File.ReadAllText(path); } else { - _logger.Error(new FileNotFoundException(), $"Unable to find the local file path: {url}"); + _logger.LogError(new FileNotFoundException(), $"Unable to find the local file path: {url}"); return null; } } if (string.IsNullOrWhiteSpace(content) == true) { - _logger.Warn($"The contents of '{url}' was empty. Unable to process JSON data."); + _logger.LogWarning($"The contents of '{url}' was empty. Unable to process JSON data."); return default; } @@ -237,7 +244,7 @@ private JToken GetJson(string url) catch (Exception ex) { var trimmed = content.Substring(0, Math.Min(400, content.Length)); - _logger.Error(ex, $"Error parsing string to JSON: {trimmed}"); + _logger.LogError(ex, $"Error parsing string to JSON: {trimmed}"); } return default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs index 0f1c0fcc..c6ede9b0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs @@ -17,6 +17,23 @@ namespace Umbraco.Community.Contentment.DataEditors { public sealed class PhysicalFileSystemDataSource : IDataListSource { + private readonly IShortStringHelper _shortStringHelper; + private readonly IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILogger _logger; + + public PhysicalFileSystemDataSource( + IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, + ILogger logger, + IShortStringHelper shortStringHelper) + { + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; + _hostingEnvironment = hostingEnvironment; + _logger = logger; + } + public string Name => "File System"; public string Description => "Select file paths from the file system as the data source."; @@ -72,15 +89,15 @@ public IEnumerable GetItems(Dictionary config) ? filter : "*.*"; - var fs = new PhysicalFileSystem(virtualRoot); + var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, _hostingEnvironment.MapPathContentRoot(virtualRoot), _hostingEnvironment.ToAbsolute(virtualRoot)); var files = fs.GetFiles(".", fileFilter); return files.Select(x => new DataListItem { - Name = friendlyName == true ? x.SplitPascalCasing().ToFriendlyName() : x, + Name = friendlyName == true ? x.SplitPascalCasing(_shortStringHelper).ToFriendlyName() : x, Value = virtualRoot + x, Description = virtualRoot + x, - Icon = Core.Constants.Icons.DefaultIcon, + Icon = Cms.Core.Constants.Icons.DefaultIcon, }); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index 60bedcba..65d10a48 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -23,11 +23,14 @@ public sealed class SqlDataListSource : IDataListSource { private readonly string _codeEditorMode; private readonly IEnumerable _connectionStrings; + private readonly IIOHelper _ioHelper; - public SqlDataListSource() + public SqlDataListSource( + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) { // NOTE: Umbraco doesn't ship with SqlServer mode, so we check if its been added manually, otherwise defautls to Razor. - _codeEditorMode = File.Exists(IOHelper.MapPath("~/umbraco/lib/ace-builds/src-min-noconflict/mode-sqlserver.js")) + _codeEditorMode = File.Exists(hostingEnvironment.MapPathWebRoot("~/umbraco/lib/ace-builds/src-min-noconflict/mode-sqlserver.js")) ? "sqlserver" : "razor"; @@ -38,6 +41,8 @@ public SqlDataListSource() Name = x.Name, Value = x.Name }); + + _ioHelper = ioHelper; } public string Name => "SQL Data"; @@ -52,7 +57,7 @@ public SqlDataListSource() public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Important: A note about your SQL query.

Your SQL query should be designed to return a minimum of 2 columns, (and a maximum of 5 columns). These columns will be used to populate the List Editor items.

The columns will be mapped in the following order:

@@ -70,7 +75,7 @@ public SqlDataListSource() Key = "query", Name = "SQL query", Description = "Enter your SQL query.", - View = CodeEditorDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, _codeEditorMode }, @@ -83,7 +88,7 @@ public SqlDataListSource() Key = "connectionString", Name = "Connection string", Description = "Select the connection string.", - View = DropdownListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -94,7 +99,7 @@ public SqlDataListSource() public Dictionary DefaultValues => new Dictionary { - { "query", $"-- This is an example query that will select all the content nodes that are at level 1.\r\nSELECT\r\n\t[text],\r\n\t[uniqueId]\r\nFROM\r\n\t[umbracoNode]\r\nWHERE\r\n\t[nodeObjectType] = '{Core.Constants.ObjectTypes.Strings.Document}'\r\n\tAND\r\n\t[level] = 1\r\nORDER BY\r\n\t[sortOrder] ASC\r\n;" }, + { "query", $"-- This is an example query that will select all the content nodes that are at level 1.\r\nSELECT\r\n\t[text],\r\n\t[uniqueId]\r\nFROM\r\n\t[umbracoNode]\r\nWHERE\r\n\t[nodeObjectType] = '{UmbConstants.ObjectTypes.Strings.Document}'\r\n\tAND\r\n\t[level] = 1\r\nORDER BY\r\n\t[sortOrder] ASC\r\n;" }, { "connectionString", UmbConstants.System.UmbracoConnectionName } }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs index cb9fde8a..7c07ee62 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs @@ -18,11 +18,18 @@ namespace Umbraco.Community.Contentment.DataEditors { public sealed class TextDelimitedDataListSource : IDataListSource { - private readonly ILogger _logger; - - public TextDelimitedDataListSource(ILogger logger) + private readonly ILogger _logger; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + + public TextDelimitedDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) { _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; } public string Name => "Text Delimited Data"; @@ -37,7 +44,7 @@ public TextDelimitedDataListSource(ILogger logger) public IEnumerable Fields => new[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
A note about using this data source.

The text contents will be retrieved and split into lines. Each line will be split into fields by the delimiting character.

The fields are then assigned by index position.

@@ -68,28 +75,28 @@ public TextDelimitedDataListSource(ILogger logger) Key = "nameIndex", Name = "Name Index", Description = "Enter the index position of the name field from the delimited line.
The default index position is 0.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }, new ConfigurationField { Key = "valueIndex", Name = "Value Index", Description = "Enter the index position of the value (key) field from the delimited line.
The default index position is 1.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }, new ConfigurationField { Key = "iconIndex", Name = "Icon Index", Description = "(optional) Enter the index position of the icon field from the delimited line. To ignore this option, set the value to -1.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }, new ConfigurationField { Key = "descriptionIndex", Name = "Description Index", Description = "(optional) Enter the index position of the description field from the delimited line. To ignore this option, set the value to -1.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), } }; @@ -187,20 +194,20 @@ private string[] GetTextLines(string url) } catch (WebException ex) { - _logger.Error(ex, "Unable to fetch remote data."); + _logger.LogError(ex, "Unable to fetch remote data."); } } else { // assume local file - var path = IOHelper.MapPath(url); + var path = _hostingEnvironment.MapPathWebRoot(url); if (File.Exists(path) == true) { return File.ReadAllLines(path); } else { - _logger.Warn("Unable to find the local file path."); + _logger.LogWarning("Unable to find the local file path."); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index afbb39fc..2c58e8d9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -22,12 +22,20 @@ namespace Umbraco.Community.Contentment.DataEditors public sealed class UmbracoContentDataListSource : IDataListSource, IDataListSourceValueConverter { private readonly IContentTypeService _contentTypeService; + private readonly IRequestAccessor _requestAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoContentDataListSource(IContentTypeService contentTypeService, IUmbracoContextAccessor umbracoContextAccessor) + public UmbracoContentDataListSource( + IContentTypeService contentTypeService, + IRequestAccessor requestAccessor, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; + _requestAccessor = requestAccessor; _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; } public string Name => "Umbraco Content"; @@ -47,7 +55,7 @@ public UmbracoContentDataListSource(IContentTypeService contentTypeService, IUmb Key = "parentNode", Name = "Parent node", Description = "Set a parent node to use its child nodes as the data source items.", - View = ContentPickerDataEditor.DataEditorSourceViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(ContentPickerDataEditor.DataEditorSourceViewPath), } }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs index 00a6b4b2..8a8d9d44 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs @@ -22,12 +22,17 @@ public sealed class UmbracoContentPropertiesDataListSource : IDataListSource { private readonly IContentTypeService _contentTypeService; private readonly Lazy _dataEditors; + private readonly IIOHelper _ioHelper; private Dictionary _icons; - public UmbracoContentPropertiesDataListSource(IContentTypeService contentTypeService, Lazy dataEditors) + public UmbracoContentPropertiesDataListSource( + IContentTypeService contentTypeService, + Lazy dataEditors, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; _dataEditors = dataEditors; + _ioHelper = ioHelper; } public string Name => "Umbraco Content Properties"; @@ -66,7 +71,7 @@ public IEnumerable Fields { "enableFilter", items.Count > 5 ? Constants.Values.True : Constants.Values.False }, { "items", items }, { "listType", "list" }, - { "overlayView", IOHelper.ResolveUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, + { "overlayView", _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, { "maxItems", 1 }, } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs index f94d513c..5df37c58 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs @@ -23,12 +23,20 @@ namespace Umbraco.Community.Contentment.DataEditors public sealed class UmbracoContentXPathDataListSource : IDataListSource, IDataListSourceValueConverter { private readonly IContentTypeService _contentTypeService; + private readonly IRequestAccessor _requestAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoContentXPathDataListSource(IContentTypeService contentTypeService, IUmbracoContextAccessor umbracoContextAccessor) + public UmbracoContentXPathDataListSource( + IContentTypeService contentTypeService, + IRequestAccessor requestAccessor, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; + _requestAccessor = requestAccessor; _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; } public string Name => "Umbraco Content by XPath"; @@ -50,7 +58,7 @@ public UmbracoContentXPathDataListSource(IContentTypeService contentTypeService, Description = "Enter the XPath expression to select the content.", View = "textstring", }, - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with XPath expressions?

If you need assistance with XPath syntax in general, please refer to this resource: w3schools.com/xml.


diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index 3a74df33..c68d00e3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -47,10 +47,17 @@ public sealed class UmbracoEntityDataListSource : IDataListSource, IDataListSour }; private readonly IEntityService _entityService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IIOHelper _ioHelper; - public UmbracoEntityDataListSource(IEntityService entityService) + public UmbracoEntityDataListSource( + IEntityService entityService, + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) { _entityService = entityService; + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; } public string Name => "Umbraco Entities"; @@ -63,7 +70,7 @@ public UmbracoEntityDataListSource(IEntityService entityService) public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
A note about supported Umbraco entity types.

Umbraco's EntityService API (currently) has limited support for querying entity types by GUID or UDI.

Supported entity types are available in the list below.

@@ -77,7 +84,7 @@ public UmbracoEntityDataListSource(IEntityService entityService) Config = new Dictionary() { { "allowEmpty", Constants.Values.False }, - { "items", SupportedEntityTypes.Keys.Select(x => new DataListItem { Name = x.SplitPascalCasing(), Value = x }) }, + { "items", SupportedEntityTypes.Keys.Select(x => new DataListItem { Name = x.SplitPascalCasing(_shortStringHelper), Value = x }) }, } } }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index 4081a0ac..bebb2997 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -26,12 +26,18 @@ public sealed class UmbracoMembersDataListSource : IDataListSource, IDataListSou private readonly IMemberTypeService _memberTypeService; private readonly IMemberService _memberService; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoMembersDataListSource(IMemberTypeService memberTypeService, IMemberService memberService, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public UmbracoMembersDataListSource( + IMemberTypeService memberTypeService, + IMemberService memberService, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IIOHelper ioHelper) { _memberTypeService = memberTypeService; _memberService = memberService; _publishedSnapshotAccessor = publishedSnapshotAccessor; + _ioHelper = ioHelper; } public string Name => "Umbraco Members"; @@ -59,7 +65,7 @@ public IEnumerable Fields return new[] { - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
Important note about Umbraco Members.

This data source is ideal for smaller number of members, e.g. under 50. Upwards of that, you will notice an unpleasant editor experience and rapidly diminished performance.

Remember...

@@ -80,7 +86,7 @@ public IEnumerable Fields { "enableFilter", items.Count > 5 ? Constants.Values.True : Constants.Values.False }, { "items", items }, { "listType", "list" }, - { "overlayView", IOHelper.ResolveUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, + { "overlayView", _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, { "maxItems", 1 }, } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index aeaa7fbe..0c4c04ef 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -21,11 +21,18 @@ namespace Umbraco.Community.Contentment.DataEditors { public sealed class XmlDataListSource : IDataListSource { - private readonly ILogger _logger; - - public XmlDataListSource(ILogger logger) + private readonly ILogger _logger; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + + public XmlDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) { _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; } public string Name => "XML Data"; @@ -47,7 +54,7 @@ public XmlDataListSource(ILogger logger) Description = "Enter the URL of the XML data source.
This can be either a remote URL, or local relative file path.", View = "textstring" }, - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with XPath expressions?

If you need assistance with XPath syntax, please refer to this resource: w3schools.com/xml.

@@ -109,7 +116,7 @@ public IEnumerable GetItems(Dictionary config) } var path = url.InvariantStartsWith("http") == false - ? IOHelper.MapPath(url) + ? _hostingEnvironment.MapPathWebRoot(url) : url; var doc = default(XPathDocument); @@ -120,11 +127,11 @@ public IEnumerable GetItems(Dictionary config) } catch (WebException ex) { - _logger.Error(ex, $"Unable to retrieve data from '{path}'."); + _logger.LogError(ex, $"Unable to retrieve data from '{path}'."); } catch (XmlException ex) { - _logger.Error(ex, "Unable to load XML data."); + _logger.LogError(ex, "Unable to load XML data."); } if (doc == null) @@ -158,7 +165,7 @@ public IEnumerable GetItems(Dictionary config) if (nodes.Count == 0) { - _logger.Warn($"The XPath '{itemsXPath}' did not match any items in the XML: {nav.OuterXml.Substring(0, Math.Min(300, nav.OuterXml.Length))}"); + _logger.LogWarning($"The XPath '{itemsXPath}' did not match any items in the XML: {nav.OuterXml.Substring(0, Math.Min(300, nav.OuterXml.Length))}"); return Enumerable.Empty(); } @@ -198,12 +205,12 @@ public IEnumerable GetItems(Dictionary config) if (name == null) { - _logger.Warn($"The XPath '{nameXPath}' did not match a 'name' in the item XML: {outerXml}"); + _logger.LogWarning($"The XPath '{nameXPath}' did not match a 'name' in the item XML: {outerXml}"); } if (value == null) { - _logger.Warn($"The XPath '{valueXPath}' did not match a 'value' in the item XML: {outerXml}"); + _logger.LogWarning($"The XPath '{valueXPath}' did not match a 'value' in the item XML: {outerXml}"); } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs index 99275f89..99169bb7 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs @@ -18,6 +18,17 @@ namespace Umbraco.Community.Contentment.DataEditors [Cms.Core.Composing.HideFromTypeFinder] public class uCssClassNameDataListSource : IDataListSource { + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + + public uCssClassNameDataListSource( + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) + { + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; + } + public string Name => "uCssClassName"; public string Description => "A homage to @marcemarc's bingo-famous uCssClassNameDropdown package!"; @@ -28,7 +39,7 @@ public class uCssClassNameDataListSource : IDataListSource public IEnumerable Fields => new[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
uCssClassName? What sort of a name is that?

Welcome to a piece of Umbraco package history.

First released back in 2013 by Marc Goodson, uCssClassNameDropdown became one of the most popular packages for Umbraco v4.11.3.

@@ -89,7 +100,7 @@ public IEnumerable GetItems(Dictionary config) var items = new HashSet(); - var path = IOHelper.MapPath(cssPath); + var path = _hostingEnvironment.MapPathWebRoot(cssPath); if (File.Exists(path) == true) { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs index e5af87cf..5aa48fa1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs @@ -14,6 +14,13 @@ public sealed class DropdownListDataListEditor : IDataListEditor internal const string AllowEmpty = "allowEmpty"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "dropdown-list.html"; + private readonly IIOHelper _ioHelper; + + public DropdownListDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Dropdown List"; public string Description => "Select a single value from a dropdown select list."; @@ -35,7 +42,7 @@ public sealed class DropdownListDataListEditor : IDataListEditor { "default", Constants.Values.True } } }, - new HtmlAttributesConfigurationField(), + new HtmlAttributesConfigurationField(_ioHelper), }; public Dictionary DefaultValues => new Dictionary diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs index cc612c6d..b4be1118 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs @@ -12,7 +12,7 @@ namespace Umbraco.Community.Contentment.DataEditors { internal sealed class IconPickerConfigurationEditor : ConfigurationEditor { - public IconPickerConfigurationEditor() + public IconPickerConfigurationEditor(IIOHelper ioHelper) : base() { Fields.Add(new ConfigurationField @@ -20,7 +20,7 @@ public IconPickerConfigurationEditor() Key = "defaultIcon", Name = "Default icon", Description = "Select an icon to be displayed as the default icon, (for when no icon has been selected).", - View = IOHelper.ResolveUrl(IconPickerDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(IconPickerDataEditor.DataEditorViewPath), Config = new Dictionary { { "defaultIcon", string.Empty }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs index ffac4aac..ae56a99f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs @@ -26,11 +26,22 @@ public sealed class IconPickerDataEditor : DataEditor internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Icon Picker"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "icon-picker.html"; internal const string DataEditorIcon = "icon-fa fa-circle-o"; + private readonly IIOHelper _ioHelper; - public IconPickerDataEditor(ILogger logger) - : base(logger) - { } + public IconPickerDataEditor( + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + EditorType type = EditorType.PropertyValue) + : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer, type) + { + _ioHelper = ioHelper; + } - protected override IConfigurationEditor CreateConfigurationEditor() => new IconPickerConfigurationEditor(); + protected override IConfigurationEditor CreateConfigurationEditor() => new IconPickerConfigurationEditor(_ioHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs index 361fd771..1ab96b19 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs @@ -15,6 +15,12 @@ public sealed class ItemPickerDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "item-picker.html"; internal const string DataEditorOverlayViewPath = Constants.Internals.EditorsPathRoot + "item-picker.overlay.html"; + private readonly IIOHelper _ioHelper; + + public ItemPickerDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } public string Name => "Item Picker"; @@ -31,7 +37,7 @@ public sealed class ItemPickerDataListEditor : IDataListEditor Key = "overlaySize", Name = "Editor overlay size", Description = "Select the size of the overlay editing panel. By default this is set to 'small'. However if the editor fields require a wider panel, please select 'medium' or 'large'.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -49,14 +55,14 @@ public sealed class ItemPickerDataListEditor : IDataListEditor Key = "defaultIcon", Name = "Default icon", Description = "Select an icon to be displayed as the default icon,
(for when no icon is available).", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), + View = _ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), }, new ConfigurationField { Key = "listType", Name = "List type", Description = "Select the style of list to be displayed in the overlay.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -76,7 +82,7 @@ public sealed class ItemPickerDataListEditor : IDataListEditor { "default", Constants.Values.True } }, }, - new MaxItemsConfigurationField(), + new MaxItemsConfigurationField(_ioHelper), new AllowClearConfigurationField(), new ConfigurationField { @@ -105,14 +111,14 @@ public sealed class ItemPickerDataListEditor : IDataListEditor public Dictionary DefaultValues => new Dictionary { { "listType", "list" }, - { "defaultIcon", Core.Constants.Icons.DefaultIcon }, + { "defaultIcon", Cms.Core.Constants.Icons.DefaultIcon }, { EnableFilterConfigurationField.EnableFilter, Constants.Values.True }, { MaxItemsConfigurationField.MaxItems, "0" }, }; public Dictionary DefaultConfig => new Dictionary { - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorOverlayViewPath) }, { "overlayOrderBy", string.Empty }, }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs index 734afa6b..227a5b18 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs @@ -14,7 +14,7 @@ internal sealed class NotesConfigurationEditor : ConfigurationEditor { internal const string Notes = NotesConfigurationField.Notes; - public NotesConfigurationEditor() + public NotesConfigurationEditor(IIOHelper ioHelper) : base() { Fields.Add(new ConfigurationField @@ -22,7 +22,7 @@ public NotesConfigurationEditor() Key = Notes, Name = nameof(Notes), Description = "Enter the notes to be displayed for the content editor.", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/rte/rte.html"), + View = ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/rte/rte.html"), Config = new Dictionary { { "editor", Constants.Conventions.DefaultConfiguration.RichTextEditor } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs index 35a52326..9afa2ca3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs @@ -14,12 +14,12 @@ internal class NotesConfigurationField : ConfigurationField { internal const string Notes = "notes"; - public NotesConfigurationField(string notes, bool hideLabel = true) + public NotesConfigurationField(IIOHelper ioHelper, string notes, bool hideLabel = true) : base() { Key = Notes; Name = nameof(Notes); - View = NotesDataEditor.DataEditorViewPath; + View = ioHelper.ResolveRelativeOrVirtualUrl(NotesDataEditor.DataEditorViewPath); Config = new Dictionary { { Notes, notes } }; HideLabel = hideLabel; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs index dd718251..d37e7dd6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs @@ -21,6 +21,29 @@ public sealed class NotesDataEditor : IDataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "notes.html"; internal const string DataEditorIcon = "icon-fa fa-sticky-note-o"; + private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + private readonly IIOHelper _ioHelper; + + public NotesDataEditor( + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + { + _dataTypeService = dataTypeService; + _localizationService = localizationService; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _ioHelper = ioHelper; + } + public string Alias => DataEditorAlias; public EditorType Type => EditorType.PropertyValue; @@ -37,11 +60,16 @@ public sealed class NotesDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new NotesConfigurationEditor(); + public IConfigurationEditor GetConfigurationEditor() => new NotesConfigurationEditor(_ioHelper); public IDataValueEditor GetValueEditor() { - return new ReadOnlyDataValueEditor + return new ReadOnlyDataValueEditor( + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { ValueType = ValueTypes.Integer, View = DataEditorViewPath, @@ -57,7 +85,12 @@ public IDataValueEditor GetValueEditor(object configuration) hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } - return new ReadOnlyDataValueEditor + return new ReadOnlyDataValueEditor( + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { Configuration = configuration, HideLabel = hideLabel, diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs index 5d4c667a..17af6652 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs @@ -12,7 +12,7 @@ namespace Umbraco.Community.Contentment.DataEditors { internal sealed class NumberInputConfigurationEditor : ConfigurationEditor { - public NumberInputConfigurationEditor() + public NumberInputConfigurationEditor(IIOHelper ioHelper) : base() { DefaultConfiguration.Add("size", "s"); @@ -22,7 +22,7 @@ public NumberInputConfigurationEditor() Key = "size", Name = "Numeric size", Description = "How big will the number get?", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs index 1b110a97..4fa9833c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs @@ -26,11 +26,29 @@ internal sealed class NumberInputDataEditor : DataEditor internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Number Input"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "number-input.html"; internal const string DataEditorIcon = "icon-coin"; + private readonly IIOHelper _ioHelper; - public NumberInputDataEditor(ILogger logger) - : base(logger) - { } + public NumberInputDataEditor( + ILoggerFactory loggerFactory, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + EditorType type = EditorType.PropertyValue) + : base( + loggerFactory, + dataTypeService, + localizationService, + localizedTextService, + shortStringHelper, + jsonSerializer, + type) + { + _ioHelper = ioHelper; + } - protected override IConfigurationEditor CreateConfigurationEditor() => new NumberInputConfigurationEditor(); + protected override IConfigurationEditor CreateConfigurationEditor() => new NumberInputConfigurationEditor(_ioHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs index 9727a9f9..2190d846 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs @@ -12,6 +12,20 @@ namespace Umbraco.Community.Contentment.DataEditors { internal sealed class ReadOnlyDataValueEditor : DataValueEditor { + public ReadOnlyDataValueEditor( + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) + : base( + dataTypeService, + localizationService, + localizedTextService, + shortStringHelper, + jsonSerializer) + { } + public override bool IsReadOnly => true; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs index d2b63db1..bc852e5a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs @@ -14,7 +14,7 @@ internal sealed class RenderMacroConfigurationEditor : ConfigurationEditor { internal const string Macro = "macro"; - public RenderMacroConfigurationEditor() + public RenderMacroConfigurationEditor(IIOHelper ioHelper) : base() { Fields.Add(new ConfigurationField @@ -22,7 +22,7 @@ public RenderMacroConfigurationEditor() Key = Macro, Name = nameof(Macro), Description = "Select and configure the macro to be displayed.", - View = IOHelper.ResolveUrl(MacroPickerDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(MacroPickerDataEditor.DataEditorViewPath), Config = new Dictionary { { MaxItemsConfigurationField.MaxItems, 1 } diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs index 8ce0ff78..54381af6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs @@ -19,7 +19,30 @@ public sealed class RenderMacroDataEditor : IDataEditor internal const string DataEditorAlias = Constants.Internals.DataEditorAliasPrefix + "RenderMacro"; internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Render Macro"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "render-macro.html"; - internal const string DataEditorIcon = Core.Constants.Icons.Macro; + internal const string DataEditorIcon = Cms.Core.Constants.Icons.Macro; + + private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + private readonly IIOHelper _ioHelper; + + public RenderMacroDataEditor( + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + { + _dataTypeService = dataTypeService; + _localizationService = localizationService; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _ioHelper = ioHelper; + } public string Alias => DataEditorAlias; @@ -37,11 +60,16 @@ public sealed class RenderMacroDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new RenderMacroConfigurationEditor(); + public IConfigurationEditor GetConfigurationEditor() => new RenderMacroConfigurationEditor(_ioHelper); public IDataValueEditor GetValueEditor() { - return new ReadOnlyDataValueEditor + return new ReadOnlyDataValueEditor( + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { ValueType = ValueTypes.Integer, View = DataEditorViewPath, @@ -57,7 +85,12 @@ public IDataValueEditor GetValueEditor(object configuration) hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } - return new ReadOnlyDataValueEditor + return new ReadOnlyDataValueEditor( + _dataTypeService, + _localizationService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { Configuration = configuration, HideLabel = hideLabel, diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs index 3b2a77da..ce76e4db 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs @@ -15,6 +15,13 @@ public sealed class TemplatedListDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "templated-list.html"; + private readonly IIOHelper _ioHelper; + + public TemplatedListDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Templated List"; public string Description => "Select items from a list rendered with custom markup."; @@ -25,7 +32,7 @@ public sealed class TemplatedListDataListEditor : IDataListEditor public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with your custom template?

Your custom template will be used to display an individual item from your configured data source.

The data for the item will be in the following format:

@@ -48,7 +55,7 @@ public sealed class TemplatedListDataListEditor : IDataListEditor { Key = "template", Name = "Template", - View = IOHelper.ResolveUrl(CodeEditorDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, "razor" }, @@ -64,7 +71,7 @@ public sealed class TemplatedListDataListEditor : IDataListEditor Description = "Select to enable picking multiple items.", View = "boolean", }, - new HtmlAttributesConfigurationField(), + new HtmlAttributesConfigurationField(_ioHelper), }; public Dictionary DefaultConfig => default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs index fef307f8..979cd85e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs @@ -18,12 +18,12 @@ internal sealed class TextInputConfigurationEditor : ConfigurationEditor { private readonly ConfigurationEditorUtility _utility; - public TextInputConfigurationEditor(ConfigurationEditorUtility utility) + public TextInputConfigurationEditor(ConfigurationEditorUtility utility, IIOHelper ioHelper, IShortStringHelper shortStringHelper) : base() { _utility = utility; - var dataSources = _utility.GetConfigurationEditorModels().ToList(); + var dataSources = _utility.GetConfigurationEditorModels(shortStringHelper).ToList(); Fields.Add(new ConfigurationField { @@ -38,13 +38,13 @@ public TextInputConfigurationEditor(ConfigurationEditorUtility utility) Key = Constants.Conventions.ConfigurationFieldAliases.Items, Name = "Data list", Description = "(optional) Select and configure a data source to provide a HTML5 <datalist> for this text input.", - View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath), Config = new Dictionary() { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureDataSource" }, { MaxItemsConfigurationField.MaxItems, 1 }, { DisableSortingConfigurationField.DisableSorting, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, { EnableFilterConfigurationField.EnableFilter, dataSources.Count > 10 ? Constants.Values.True : Constants.Values.False }, { Constants.Conventions.ConfigurationFieldAliases.Items, dataSources }, @@ -56,7 +56,7 @@ public TextInputConfigurationEditor(ConfigurationEditorUtility utility) Name = "Maximum allowed characters", Key = "maxChars", Description = "Enter the maximum number of characters allowed for the text input.
The default limit is 500 characters.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }); Fields.Add(new ConfigurationField diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs index e708d702..7cdca13a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs @@ -29,13 +29,26 @@ public sealed class TextInputDataEditor : DataEditor internal const string DataEditorIcon = "icon-autofill"; private readonly ConfigurationEditorUtility _utility; + private readonly IIOHelper _ioHelper; + private readonly IShortStringHelper _shortStringHelper; - public TextInputDataEditor(ILogger logger, ConfigurationEditorUtility utility) - : base(logger) + public TextInputDataEditor( + ConfigurationEditorUtility utility, + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + EditorType type = EditorType.PropertyValue) + : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer, type) { _utility = utility; + _ioHelper = ioHelper; + _shortStringHelper = shortStringHelper; } - protected override IConfigurationEditor CreateConfigurationEditor() => new TextInputConfigurationEditor(_utility); + protected override IConfigurationEditor CreateConfigurationEditor() => new TextInputConfigurationEditor(_utility, _ioHelper, _shortStringHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs index f9936e8e..051feb8d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs @@ -14,7 +14,7 @@ internal sealed class HtmlAttributesConfigurationField : ConfigurationField { internal const string HtmlAttributes = "htmlAttributes"; - public HtmlAttributesConfigurationField() + public HtmlAttributesConfigurationField(IIOHelper ioHelper) : base() { var listFields = new[] @@ -36,7 +36,7 @@ public HtmlAttributesConfigurationField() Key = HtmlAttributes; Name = "HTML attributes"; Description = "(optional) Use this field to add any HTML attributes to the list editor."; - View = IOHelper.ResolveUrl(DataTableDataEditor.DataEditorViewPath); + View = ioHelper.ResolveRelativeOrVirtualUrl(DataTableDataEditor.DataEditorViewPath); Config = new Dictionary() { { DataTableConfigurationEditor.FieldItems, listFields }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs index fd97f131..907ebbc6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs @@ -13,13 +13,13 @@ internal sealed class MaxItemsConfigurationField : ConfigurationField { internal const string MaxItems = "maxItems"; - public MaxItemsConfigurationField() + public MaxItemsConfigurationField(IIOHelper ioHelper) : base() { Key = MaxItems; Name = "Maximum items"; Description = "Enter the number for the maximum items allowed.
Use '0' for an unlimited amount."; - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath); + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath); } } } diff --git a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs index 35104650..8a3adb19 100644 --- a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs +++ b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs @@ -25,6 +25,13 @@ public sealed class EnumDataSourceApiController : UmbracoAuthorizedJsonControlle internal const string GetAssembliesUrl = "backoffice/Contentment/EnumDataSourceApi/GetAssemblies"; internal const string GetEnumsUrl = "backoffice/Contentment/EnumDataSourceApi/GetEnums?assembly={0}"; + private readonly IShortStringHelper _shortStringHelper; + + public EnumDataSourceApiController(IShortStringHelper shortStringHelper) + { + _shortStringHelper = shortStringHelper; + } + public IEnumerable GetAssemblies() { const string App_Code = "App_Code"; @@ -80,7 +87,7 @@ public IEnumerable GetEnums(string assembly) if (type.IsEnum == false) continue; - options.Add(type.FullName, new DataListItem { Name = type.Name.SplitPascalCasing(), Value = type.FullName }); + options.Add(type.FullName, new DataListItem { Name = type.Name.SplitPascalCasing(_shortStringHelper), Value = type.FullName }); } return options.Values; From d17642623a1e4618809e6ad9f7005daffd9b0207 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:24:52 +0100 Subject: [PATCH 09/81] Commented out the Assembly attributes I think I'll need to remove them completely, but keeping them around in case I've missed setting one of the values elsewhere. --- .../Properties/AssemblyInfo.cs | 8 ++++++++ .../Properties/SolutionInfo.cs | 13 +++++++++++++ .../Properties/VersionInfo.cs | 8 ++++---- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs create mode 100644 src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs diff --git a/src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs b/src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..0828605d --- /dev/null +++ b/src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +//using System.Reflection; +//using System.Runtime.InteropServices; +//using Umbraco.Community.Contentment; + +//[assembly: AssemblyTitle(Constants.Internals.ProjectNamespace)] +//[assembly: AssemblyDescription("Umbraco Contentment - a state of happiness and satisfaction")] + +//[assembly: Guid("7D440D19-44D0-474F-8BAB-A7DDF4062994")] diff --git a/src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs b/src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs new file mode 100644 index 00000000..0204ff01 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs @@ -0,0 +1,13 @@ +//using System.Reflection; +//using System.Runtime.InteropServices; +//using Umbraco.Community.Contentment; + +//[assembly: AssemblyConfiguration("")] +//[assembly: AssemblyCompany("Umbrella Inc Ltd")] +//[assembly: AssemblyProduct(Constants.Internals.ProjectNamespace)] +//[assembly: AssemblyCopyright("Copyright \xa9 2019 Lee Kelleher.")] +//[assembly: AssemblyTrademark("")] +//[assembly: AssemblyCulture("")] + +//[assembly: ComVisible(false)] + diff --git a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs index 4f261a13..66d89971 100644 --- a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs +++ b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs @@ -1,5 +1,5 @@ -using System.Reflection; +//using System.Reflection; -[assembly: AssemblyVersion("3.0")] -[assembly: AssemblyFileVersion("3.0.0")] -[assembly: AssemblyInformationalVersion("3.0.0-develop")] +//[assembly: AssemblyVersion("3.0")] +//[assembly: AssemblyFileVersion("3.0.0")] +//[assembly: AssemblyInformationalVersion("3.0.0-develop")] From dc6e3129a3144cecfb3de9c5f8efad690258e43d Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:26:30 +0100 Subject: [PATCH 10/81] Commented out the various MVC ViewEngine features. I don't understand Razor Pages enough (yet) to make the relevant amends. --- .../ContentBlocksApiController.cs | 218 +++++++++--------- .../ContentBlocks/ContentBlocksViewHelper.cs | 96 ++++---- .../Web/Extensions/HtmlHelperExtensions.cs | 146 ++++++------ 3 files changed, 239 insertions(+), 221 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs index 42503c2b..eb4056f9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -3,108 +3,116 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Web.Http; -using Newtonsoft.Json.Linq; -using Umbraco.Community.Contentment.Web.PublishedCache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Editors; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; - -namespace Umbraco.Community.Contentment.DataEditors -{ - [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] - public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController - { - private readonly ILogger _logger; - private readonly IPublishedModelFactory _publishedModelFactory; - - public ContentBlocksApiController(ILogger logger, IPublishedModelFactory publishedModelFactory) - { - _logger = logger; - _publishedModelFactory = publishedModelFactory; - } - - [HttpPost] - public HttpResponseMessage GetPreviewMarkup([FromBody] JObject item, int elementIndex, Guid elementKey, int contentId) - { - var preview = true; - - var content = UmbracoContext.Content.GetById(true, contentId); - if (content == null) - { - _logger.Debug($"Unable to retrieve content for ID '{contentId}', it is most likely a new unsaved page."); - } - - var element = default(IPublishedElement); - var block = item.ToObject(); - if (block != null && block.ElementType.Equals(Guid.Empty) == false) - { - if (ContentTypeCacheHelper.TryGetAlias(block.ElementType, out var alias, Services.ContentTypeService) == true) - { - var contentType = UmbracoContext.PublishedSnapshot.Content.GetContentType(alias); - if (contentType != null && contentType.IsElement == true) - { - var properties = new List(); - - foreach (var thing in block.Value) - { - var propType = contentType.GetPropertyType(thing.Key); - if (propType != null) - { - properties.Add(new DetachedPublishedProperty(propType, null, thing.Value, preview)); - } - } - - element = _publishedModelFactory.CreateModel(new DetachedPublishedElement(block.Key, contentType, properties)); - } - } - } - - var viewData = new System.Web.Mvc.ViewDataDictionary(element) - { - { nameof(content), content }, - { nameof(element), element }, - { nameof(elementIndex), elementIndex }, - }; - - if (ContentTypeCacheHelper.TryGetIcon(content.ContentType.Alias, out var contentIcon, Services.ContentTypeService) == true) - { - viewData.Add(nameof(contentIcon), contentIcon); - } - - if (ContentTypeCacheHelper.TryGetIcon(element.ContentType.Alias, out var elementIcon, Services.ContentTypeService) == true) - { - viewData.Add(nameof(elementIcon), elementIcon); - } - - var markup = default(string); - - try - { - markup = ContentBlocksViewHelper.RenderPartial(element.ContentType.Alias, viewData); - } - catch (InvalidCastException icex) - { - // NOTE: This type of exception happens on a new (unsaved) page, when the context becomes the parent page, - // and the preview view is strongly typed to the current page's model type. - markup = "

Unable to render the preview until the page has been saved.

"; - - _logger.Error(icex, "Error rendering preview view."); - } - catch (Exception ex) - { - markup = $"
{ex}
"; - - _logger.Error(ex, "Error rendering preview view."); - } - - return Request.CreateResponse(HttpStatusCode.OK, new { elementKey, markup }); - } - } -} +// TODO: [LK:2021-05-03] v9 Commenting out, as I'm (currently) unsure how to do the `RazorViewEngine` bits. +// Feel that I need more understanding of .NET Core RazorPages. + +//using System; +//using System.Collections.Generic; +//using System.Net; +//using System.Net.Http; +//using Microsoft.AspNetCore.Mvc; +//using Microsoft.Extensions.Logging; +//using Newtonsoft.Json.Linq; +//using Umbraco.Cms.Core.Models.PublishedContent; +//using Umbraco.Cms.Core.Services; +//using Umbraco.Cms.Web.BackOffice.Controllers; +//using Umbraco.Cms.Web.Common.Attributes; +//using Umbraco.Community.Contentment.Web.PublishedCache; + +//namespace Umbraco.Community.Contentment.DataEditors +//{ +// [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] +// public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController +// { +// private readonly ILogger _logger; +// private readonly IPublishedModelFactory _publishedModelFactory; +// private readonly IContentTypeService _contentTypeService; + +// public ContentBlocksApiController( +// ILogger logger, +// IPublishedModelFactory publishedModelFactory, +// IContentTypeService contentTypeService) +// { +// _logger = logger; +// _publishedModelFactory = publishedModelFactory; +// _contentTypeService = contentTypeService; +// } + +// [HttpPost] +// public HttpResponseMessage GetPreviewMarkup([FromBody] JObject item, int elementIndex, Guid elementKey, int contentId) +// { +// var preview = true; + +// var content = UmbracoContext.Content.GetById(true, contentId); +// if (content == null) +// { +// _logger.LogDebug($"Unable to retrieve content for ID '{contentId}', it is most likely a new unsaved page."); +// } + +// var element = default(IPublishedElement); +// var block = item.ToObject(); +// if (block != null && block.ElementType.Equals(Guid.Empty) == false) +// { +// if (ContentTypeCacheHelper.TryGetAlias(block.ElementType, out var alias, _contentTypeService) == true) +// { +// var contentType = UmbracoContext.PublishedSnapshot.Content.GetContentType(alias); +// if (contentType != null && contentType.IsElement == true) +// { +// var properties = new List(); + +// foreach (var thing in block.Value) +// { +// var propType = contentType.GetPropertyType(thing.Key); +// if (propType != null) +// { +// properties.Add(new DetachedPublishedProperty(propType, null, thing.Value, preview)); +// } +// } + +// element = _publishedModelFactory.CreateModel(new DetachedPublishedElement(block.Key, contentType, properties)); +// } +// } +// } + +// var viewData = new System.Web.Mvc.ViewDataDictionary(element) +// { +// { nameof(content), content }, +// { nameof(element), element }, +// { nameof(elementIndex), elementIndex }, +// }; + +// if (ContentTypeCacheHelper.TryGetIcon(content.ContentType.Alias, out var contentIcon, _contentTypeService) == true) +// { +// viewData.Add(nameof(contentIcon), contentIcon); +// } + +// if (ContentTypeCacheHelper.TryGetIcon(element.ContentType.Alias, out var elementIcon, _contentTypeService) == true) +// { +// viewData.Add(nameof(elementIcon), elementIcon); +// } + +// var markup = default(string); + +// try +// { +// markup = ContentBlocksViewHelper.RenderPartial(element.ContentType.Alias, viewData); +// } +// catch (InvalidCastException icex) +// { +// // NOTE: This type of exception happens on a new (unsaved) page, when the context becomes the parent page, +// // and the preview view is strongly typed to the current page's model type. +// markup = "

Unable to render the preview until the page has been saved.

"; + +// _logger.LogError(icex, "Error rendering preview view."); +// } +// catch (Exception ex) +// { +// markup = $"
{ex}
"; + +// _logger.LogError(ex, "Error rendering preview view."); +// } + +// return Request.CreateResponse(HttpStatusCode.OK, new { elementKey, markup }); +// } +// } +//} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs index d330e2ac..b0420091 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs @@ -3,49 +3,53 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.IO; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; - -namespace Umbraco.Community.Contentment.DataEditors -{ - internal static class ContentBlocksViewHelper - { - [Core.Composing.HideFromTypeFinder] - private class ContentBlocksFakeController : ControllerBase { protected override void ExecuteCore() { } } - - private static readonly RazorViewEngine _viewEngine = new RazorViewEngine - { - PartialViewLocationFormats = new[] - { - "~/Views/Partials/Blocks/{0}.cshtml", - "~/Views/Partials/Blocks/Default.cshtml", - Constants.Internals.PackagePathRoot + "render/ContentBlockPreview.cshtml" - } - }; - - internal static string RenderPartial(string partialName, ViewDataDictionary viewData) - { - using (var sw = new StringWriter()) - { - var httpContext = new HttpContextWrapper(HttpContext.Current); - - var routeData = new RouteData { Values = { { "controller", nameof(ContentBlocksFakeController) } } }; - - var controllerContext = new ControllerContext(new RequestContext(httpContext, routeData), new ContentBlocksFakeController()); - - var viewResult = _viewEngine.FindPartialView(controllerContext, partialName, false); - - if (viewResult.View == null) - { - return null; - } - - viewResult.View.Render(new ViewContext(controllerContext, viewResult.View, viewData, new TempDataDictionary(), sw), sw); - - return sw.ToString(); - } - } - } -} +// TODO: [LK:2021-05-03] v9 Commenting out, as I'm (currently) unsure how to do the `RazorViewEngine` bits. +// Feel that I need more understanding of .NET Core RazorPages. + +//using System.IO; +//using System.Web; +//using System.Web.Mvc; +//using System.Web.Routing; +//using Microsoft.AspNetCore.Mvc; +//using Microsoft.AspNetCore.Mvc.Razor; + +//namespace Umbraco.Community.Contentment.DataEditors +//{ +// internal static class ContentBlocksViewHelper +// { +// private class FakeController : Controller { } + +// private static readonly RazorViewEngine _viewEngine = new RazorViewEngine +// { +// PartialViewLocationFormats = new[] +// { +// "~/Views/Partials/Blocks/{0}.cshtml", +// "~/Views/Partials/Blocks/Default.cshtml", +// Constants.Internals.PackagePathRoot + "render/ContentBlockPreview.cshtml" +// } +// }; + +// internal static string RenderPartial(string partialName, ViewDataDictionary viewData) +// { +// using (var sw = new StringWriter()) +// { +// var httpContext = new HttpContextWrapper(HttpContext.Current); + +// var routeData = new RouteData { Values = { { "controller", nameof(FakeController) } } }; + +// var controllerContext = new ControllerContext(new RequestContext(httpContext, routeData), new FakeController()); + +// var viewResult = _viewEngine.FindPartialView(controllerContext, partialName, false); + +// if (viewResult.View == null) +// { +// return null; +// } + +// viewResult.View.Render(new ViewContext(controllerContext, viewResult.View, viewData, new TempDataDictionary(), sw), sw); + +// return sw.ToString(); +// } +// } +// } +//} diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs index 6bb2eb11..06a9ddbe 100644 --- a/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs +++ b/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs @@ -3,83 +3,89 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System; -using System.Collections.Generic; -using System.Web.Mvc; -using System.Web.Mvc.Html; -using System.Web.WebPages; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; +// TODO: [LK:2021-05-03] v9 Commenting out, as I'm (currently) unsure how to do the `HtmlHelper` bits. +// Feel that I need more understanding of .NET Core RazorPages. -namespace Umbraco.Web.Mvc -{ - public static class HtmlHelperExtensions - { - // Extension method derived from a StackOverflow answer. - // https://stackoverflow.com/a/44870370/12787 - // Licensed under the permissions of the CC BY-SA 3.0. - // https://creativecommons.org/licenses/by-sa/3.0/ - public static bool DoesPartialExist(this HtmlHelper helper, string partialViewName) - { - var controllerContext = helper.ViewContext.Controller.ControllerContext; - var result = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName); +//using System; +//using System.Collections.Generic; +//using Microsoft.AspNetCore.Html; +//using Microsoft.AspNetCore.Mvc.Razor; +//using Microsoft.AspNetCore.Mvc.Rendering; +//using Microsoft.AspNetCore.Mvc.ViewFeatures; +//using Umbraco.Cms.Core.Models.PublishedContent; +//using Umbraco.Extensions; - return result.View != null; - } +//namespace Umbraco.Web.Mvc +//{ +// public static class HtmlHelperExtensions +// { +// // Extension method derived from a StackOverflow answer. +// // https://stackoverflow.com/a/44870370/12787 +// // Licensed under the permissions of the CC BY-SA 3.0. +// // https://creativecommons.org/licenses/by-sa/3.0/ +// public static bool DoesPartialExist(this HtmlHelper helper, string partialViewName) +// { +// var controllerContext = helper.ViewContext.Controller.ControllerContext; +// var result = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName); - public static MvcHtmlString Partial(this HtmlHelper helper, string partialViewName, TModel model) - { - var viewData = new ViewDataDictionary(helper.ViewData) { Model = model }; - return PartialExtensions.Partial(helper, partialViewName, model, viewData); - } +// return result.View != null; +// } - public static HelperResult RenderElements( - this HtmlHelper helper, - IEnumerable elements, - string viewPath = null, - string fallbackPartialViewName = null, - Func predicate = null, - ViewDataDictionary viewData = null) - where TPublishedElement : IPublishedElement - { - return new HelperResult(writer => - { - if (elements != null) - { - var elementIndex = 0; +// public static IHtmlContent Partial(this HtmlHelper helper, string partialViewName, TModel model) +// { +// var viewData = new ViewDataDictionary(helper.ViewData) { Model = model }; +// return PartialExtensions.Partial(helper, partialViewName, model, viewData); +// } - if (viewData == null) - { - viewData = new ViewDataDictionary(); - } +// public static HelperResult RenderElements( +// this HtmlHelper helper, +// IEnumerable elements, +// string viewPath = null, +// string fallbackPartialViewName = null, +// Func predicate = null, +// ViewDataDictionary viewData = null) +// where TPublishedElement : IPublishedElement +// { +// return new HelperResult(writer => +// { +// if (elements != null) +// { +// var elementIndex = 0; - foreach (var element in elements) - { - viewData[nameof(elementIndex)] = elementIndex++; +// if (viewData == null) +// { +// viewData = new ViewDataDictionary(); +// } - if (predicate != null && predicate(element) == false) - { - continue; - } +// foreach (var element in elements) +// { +// viewData[nameof(elementIndex)] = elementIndex++; - var partialViewName = viewPath?.EnsureEndsWith("/") + element.ContentType.Alias; +// if (predicate != null && predicate(element) == false) +// { +// continue; +// } - if (helper.DoesPartialExist(partialViewName) == false && string.IsNullOrWhiteSpace(fallbackPartialViewName) == false) - { - partialViewName = viewPath?.EnsureEndsWith("/") + fallbackPartialViewName; - } +// var partialViewName = viewPath?.EnsureEndsWith("/") + element.ContentType.Alias; - if (helper.DoesPartialExist(partialViewName) == true) - { - writer.WriteLine(helper.Partial(partialViewName, element, viewData)); - } - else - { - writer.WriteLine($""); - } - } - } - }); - } - } -} +// if (helper.DoesPartialExist(partialViewName) == false && string.IsNullOrWhiteSpace(fallbackPartialViewName) == false) +// { +// partialViewName = viewPath?.EnsureEndsWith("/") + fallbackPartialViewName; +// } + +// if (helper.DoesPartialExist(partialViewName) == true) +// { +//#pragma warning disable MVC1000 // Use of IHtmlHelper.{0} should be avoided. +// writer.WriteLine(helper.Partial(partialViewName, element, viewData)); +//#pragma warning restore MVC1000 // Use of IHtmlHelper.{0} should be avoided. +// } +// else +// { +// writer.WriteLine($""); +// } +// } +// } +// }); +// } +// } +//} From 488fc9b4c69ff233126b2110b461f8c106020f19 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:28:10 +0100 Subject: [PATCH 11/81] ContentBlockPreviewView - made relevant amends I'm not keen on using the `ViewContext` property's setter method to do this, but it appears there isn't an equivalent to the `SetViewData()` override method. --- .../ContentBlocks/ContentBlockPreviewView.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index 148ad1b1..96833642 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -21,11 +21,17 @@ public abstract class ContentBlockPreviewView base.ViewContext; + set => base.ViewContext = SetViewData(value); + } + + protected ViewContext SetViewData(ViewContext viewCtx) { void setProperty(string key, Action action) { - if (viewData.TryGetValueAs(key, out T value) == true) + if (viewCtx.ViewData.TryGetValueAs(key, out T value) == true) { action(value); } @@ -39,7 +45,7 @@ void setProperty(string key, Action action) setProperty("contentIcon", (x) => model.ContentTypeIcon = x); setProperty("elementIcon", (x) => model.ElementTypeIcon = x); - if (model.Element == null && viewData.Model is TPublishedElement element) + if (model.Element == null && viewCtx.ViewData.Model is TPublishedElement element) { model.Element = element; } @@ -49,9 +55,9 @@ void setProperty(string key, Action action) model.Content = content; } - viewData.Model = model; + viewCtx.ViewData.Model = model; - base.SetViewData(viewData); + return viewCtx; } } } From 894be0dc854e4afb5688316aef432dab065edb4c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:30:26 +0100 Subject: [PATCH 12/81] SQL data-source - commented out the SQLCE bits Unsure what to do about those, as SQLCE support is an extra assembly dependency, so would mean that I'd need to make an assumption that everyone is using it. No idea what would break if that dependency wasn't there - probably YSoD (or whatever the new "screen-of-death" is called?) --- .../DataList/DataSources/SqlDataListSource.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index 65d10a48..b04c650d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -122,14 +122,14 @@ public IEnumerable GetItems(Dictionary config) } // NOTE: SQLCE uses a different connection/command. I'm trying to keep this as generic as possible, without resorting to using NPoco. [LK] - if (settings.ProviderName.InvariantEquals(UmbConstants.DatabaseProviders.SqlCe) == true) - { - items.AddRange(GetSqlItems(query, settings.ConnectionString)); - } - else - { + //if (settings.ProviderName.InvariantEquals(UmbConstants.DatabaseProviders.SqlCe) == true) + //{ + // items.AddRange(GetSqlItems(query, settings.ConnectionString)); + //} + //else + //{ items.AddRange(GetSqlItems(query, settings.ConnectionString)); - } + //} return items; } From 0ddb4a3c045cdc3e9c3f83c35de6d597e5c873b9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:31:32 +0100 Subject: [PATCH 13/81] Updated `GuidUdi.TryParse` calls with `UdiParser.TryParse` --- .../DataSources/UmbracoContentDataListSource.cs | 4 ++-- .../UmbracoContentPropertiesDataListSource.cs | 2 +- .../DataSources/UmbracoContentXPathDataListSource.cs | 2 +- .../DataSources/UmbracoEntityDataListSource.cs | 2 +- .../DataSources/UmbracoImageCropDataListSource.cs | 2 +- .../DataSources/UmbracoMembersDataListSource.cs | 10 ++++++---- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 2c58e8d9..74865635 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -97,7 +97,7 @@ public IEnumerable GetItems(Dictionary config) startNode = umbracoContext.Content.GetSingleByXPath(preview, parsed); } } - else if (GuidUdi.TryParse(parentNode, out var udi) == true && udi.Guid != Guid.Empty) + else if (UdiParser.TryParse(parentNode, out GuidUdi udi) == true && udi.Guid != Guid.Empty) { startNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(preview, udi.Guid); } @@ -122,7 +122,7 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - return Udi.TryParse(value, out var udi) == true + return UdiParser.TryParse(value, out var udi) == true ? _umbracoContextAccessor.UmbracoContext.Content.GetById(udi) : default; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs index 8a8d9d44..34f26212 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs @@ -89,7 +89,7 @@ public IEnumerable GetItems(Dictionary config) array.Count > 0 && array[0].Value() is string str && string.IsNullOrWhiteSpace(str) == false && - GuidUdi.TryParse(str, out var udi) == true) + UdiParser.TryParse(str, out GuidUdi udi) == true) { var contentType = _contentTypeService.Get(udi.Guid); if (contentType != null) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs index 5df37c58..9d5e2418 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs @@ -129,7 +129,7 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - return Udi.TryParse(value, out var udi) == true + return UdiParser.TryParse(value, out var udi) == true ? _umbracoContextAccessor.UmbracoContext.Content.GetById(udi) : default; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index c68d00e3..80e7d5f5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -117,7 +117,7 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - return GuidUdi.TryParse(value, out var udi) == true && udi.Guid.Equals(Guid.Empty) == false + return UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false ? _entityService.Get(udi.Guid) : default; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs index afbfe697..4bd655b6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs @@ -72,7 +72,7 @@ public IEnumerable GetItems(Dictionary config) if (config.TryGetValue("imageCropper", out var obj) == true && obj is string str && string.IsNullOrWhiteSpace(str) == false && - GuidUdi.TryParse(str, out var udi) == true) + UdiParser.TryParse(str, out GuidUdi udi) == true) { return _dataTypeService .GetDataType(udi.Guid)? diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index bebb2997..d0279aee 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -116,7 +116,7 @@ DataListItem mapMember(IMember member) array.Count > 0 && array[0].Value() is string str && string.IsNullOrWhiteSpace(str) == false && - GuidUdi.TryParse(str, out var udi) == true) + UdiParser.TryParse(str, out GuidUdi udi) == true) { var memberType = _memberTypeService.Get(udi.Guid); if (memberType != null) @@ -136,9 +136,11 @@ DataListItem mapMember(IMember member) public object ConvertValue(Type type, string value) { - return GuidUdi.TryParse(value, out var udi) == true && udi.Guid.Equals(Guid.Empty) == false - ? _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(udi.Guid) - : default; + // TODO: [LK:2021-04-30] v9 Review this, as why would it only have `Get(IMember)` odd. + //return UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false + // ? _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(udi.Guid) + // : default; + return default; } } } From 49a6bb504ce41fca0a2aa72bb893cab87a5d9e80 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:32:19 +0100 Subject: [PATCH 14/81] TIL there's a `IRequestAccessor` to access request querystring values --- .../DataList/DataSources/UmbracoContentDataListSource.cs | 4 ++-- .../DataList/DataSources/UmbracoContentXPathDataListSource.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 74865635..49a6b87b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -73,11 +73,11 @@ public IEnumerable GetItems(Dictionary config) var umbracoContext = _umbracoContextAccessor.UmbracoContext; // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). - if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("id"), out var currentId) == true) + if (int.TryParse(_requestAccessor.GetQueryStringValue("id"), out var currentId) == true) { nodeContextId = currentId; } - else if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("parentId"), out var parentId) == true) + else if (int.TryParse(_requestAccessor.GetQueryStringValue("parentId"), out var parentId) == true) { nodeContextId = parentId; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs index 9d5e2418..c650a12d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs @@ -94,11 +94,11 @@ public IEnumerable GetItems(Dictionary config) var umbracoContext = _umbracoContextAccessor.UmbracoContext; // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). - if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("id"), out var currentId) == true) + if (int.TryParse(_requestAccessor.GetQueryStringValue("id"), out var currentId) == true) { nodeContextId = currentId; } - else if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("parentId"), out var parentId) == true) + else if (int.TryParse(_requestAccessor.GetQueryStringValue("parentId"), out var parentId) == true) { nodeContextId = parentId; } From e1f4a935b6561d06bd359fd57faceffc35edea0e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:33:01 +0100 Subject: [PATCH 15/81] Updated `UdiEntityType.FromUmbracoObjectType` to .GetUdiType` extension method This makes sense. Still took me a good while to figure it out. --- .../DataList/DataSources/UmbracoEntityDataListSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index 80e7d5f5..6abad451 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -106,7 +106,7 @@ public IEnumerable GetItems(Dictionary config) { Icon = icon, Name = x.Name, - Value = Udi.Create(UmbConstants.UdiEntityType.FromUmbracoObjectType(objectType), x.Key).ToString(), + Value = Udi.Create(objectType.GetUdiType(), x.Key).ToString(), }); } From dc71e812694a998f3265e913e847fa9a515c3473 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:34:39 +0100 Subject: [PATCH 16/81] [WIP] API Controllers - struggling to get them to work This commit is where I got up to before stopping with it. Can't figure out why none of the API controllers are working in the back-office. Already burnt many hours on this. --- .../DataList/DataListApiController.cs | 6 ++--- .../Trees/ContentmentTreeController.cs | 23 ++++++++++++------- .../EnumDataListSourceApiController.cs | 3 +++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs index 1a9c6d64..cd7ea814 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs @@ -24,7 +24,7 @@ public DataListApiController(PropertyEditorCollection propertyEditors) } [HttpPost] - public HttpResponseMessage GetPreview([FromBody] JObject data) + public ActionResult GetPreview([FromBody] JObject data) { var config = data.ToObject>(); @@ -35,10 +35,10 @@ public HttpResponseMessage GetPreview([FromBody] JObject data) var valueEditorConfig = configurationEditor.ToValueEditor(config); var valueEditor = propertyEditor.GetValueEditor(config); - return Request.CreateResponse(HttpStatusCode.OK, new { config = valueEditorConfig, view = valueEditor.View, alias }); + return new ObjectResult(new { config = valueEditorConfig, view = valueEditor.View, alias }); } - return Request.CreateResponse(HttpStatusCode.NotFound); + return new NotFoundResult(); } } } diff --git a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs index 7f97d572..820c1a4b 100644 --- a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs +++ b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs @@ -26,20 +26,27 @@ namespace Umbraco.Community.Contentment.Trees [PluginController(Constants.Internals.PluginControllerName)] internal sealed class ContentmentTreeController : TreeController { - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + public ContentmentTreeController( + ILocalizedTextService localizedTextService, + UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, + IEventAggregator eventAggregator) + : base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator) + { } + + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var root = base.CreateRootNode(queryStrings); - root.Icon = "icon-fa fa-cube"; - root.HasChildren = false; - root.RoutePath = $"{SectionAlias}/{TreeAlias}/index"; - root.MenuUrl = null; + root.Value.Icon = "icon-fa fa-cube"; + root.Value.HasChildren = false; + root.Value.RoutePath = $"{SectionAlias}/{TreeAlias}/index"; + root.Value.MenuUrl = null; - return root; + return root.Value; } - protected override MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) => null; + protected override ActionResult GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) => null; - protected override TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) => null; + protected override ActionResult GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) => null; } } diff --git a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs index 8a3adb19..8a83b515 100644 --- a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs +++ b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs @@ -20,6 +20,7 @@ namespace Umbraco.Community.Contentment.Web.Controllers { [EditorBrowsable(EditorBrowsableState.Never)] [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] + [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] public sealed class EnumDataSourceApiController : UmbracoAuthorizedJsonController { internal const string GetAssembliesUrl = "backoffice/Contentment/EnumDataSourceApi/GetAssemblies"; @@ -32,6 +33,7 @@ public EnumDataSourceApiController(IShortStringHelper shortStringHelper) _shortStringHelper = shortStringHelper; } + [HttpGet] public IEnumerable GetAssemblies() { const string App_Code = "App_Code"; @@ -77,6 +79,7 @@ public IEnumerable GetAssemblies() return options.Values; } + [HttpGet] public IEnumerable GetEnums(string assembly) { var options = new SortedDictionary(); From 60ef6eb1d87afb5be52aac206706e099834fa62f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 12:35:39 +0100 Subject: [PATCH 17/81] PublishedContentContractResolver - commented out deprecated properties Will need to investigate how to populate them later. e.g. `.Url()` is an important one --- .../PublishedContentContractResolver.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs index 963dabf4..146cd0ff 100644 --- a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs +++ b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs @@ -73,9 +73,10 @@ public PublishedContentContractResolver() _systemProperties = new HashSet(StringComparer.OrdinalIgnoreCase) { nameof(IPublishedContent.CreateDate), -#pragma warning disable CS0618 // Type or member is obsolete - nameof(IPublishedContent.CreatorName), -#pragma warning restore CS0618 // Type or member is obsolete +// TODO: [LK:2021-04-30] v9 Review this. +//#pragma warning disable CS0618 // Type or member is obsolete +// nameof(IPublishedContent.CreatorName), +//#pragma warning restore CS0618 // Type or member is obsolete nameof(IPublishedContent.Id), nameof(IPublishedContent.ItemType), nameof(IPublishedElement.Key), @@ -84,13 +85,15 @@ public PublishedContentContractResolver() nameof(IPublishedContent.Path), nameof(IPublishedContent.SortOrder), nameof(IPublishedContent.UpdateDate), -#pragma warning disable CS0618 // Type or member is obsolete - nameof(IPublishedContent.Url), -#pragma warning restore CS0618 // Type or member is obsolete +// TODO: [LK:2021-04-30] v9 Review this. +//#pragma warning disable CS0618 // Type or member is obsolete +// nameof(IPublishedContent.Url), +//#pragma warning restore CS0618 // Type or member is obsolete nameof(IPublishedContent.UrlSegment), -#pragma warning disable CS0618 // Type or member is obsolete - nameof(IPublishedContent.WriterName), -#pragma warning restore CS0618 // Type or member is obsolete +// TODO: [LK:2021-04-30] v9 Review this. +//#pragma warning disable CS0618 // Type or member is obsolete +// nameof(IPublishedContent.WriterName), +//#pragma warning restore CS0618 // Type or member is obsolete }; } From 10d3e4fc617627b9df10ee3f26b58f23a70e9d4c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 17:42:49 +0100 Subject: [PATCH 18/81] Removed the Assembly attribute classes No longer required. --- .../Properties/AssemblyInfo.cs | 8 -------- .../Properties/SolutionInfo.cs | 13 ------------- .../Properties/VersionInfo.cs | 5 ----- 3 files changed, 26 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs delete mode 100644 src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs delete mode 100644 src/Umbraco.Community.Contentment/Properties/VersionInfo.cs diff --git a/src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs b/src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs deleted file mode 100644 index 0828605d..00000000 --- a/src/Umbraco.Community.Contentment/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -//using System.Reflection; -//using System.Runtime.InteropServices; -//using Umbraco.Community.Contentment; - -//[assembly: AssemblyTitle(Constants.Internals.ProjectNamespace)] -//[assembly: AssemblyDescription("Umbraco Contentment - a state of happiness and satisfaction")] - -//[assembly: Guid("7D440D19-44D0-474F-8BAB-A7DDF4062994")] diff --git a/src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs b/src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs deleted file mode 100644 index 0204ff01..00000000 --- a/src/Umbraco.Community.Contentment/Properties/SolutionInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -//using System.Reflection; -//using System.Runtime.InteropServices; -//using Umbraco.Community.Contentment; - -//[assembly: AssemblyConfiguration("")] -//[assembly: AssemblyCompany("Umbrella Inc Ltd")] -//[assembly: AssemblyProduct(Constants.Internals.ProjectNamespace)] -//[assembly: AssemblyCopyright("Copyright \xa9 2019 Lee Kelleher.")] -//[assembly: AssemblyTrademark("")] -//[assembly: AssemblyCulture("")] - -//[assembly: ComVisible(false)] - diff --git a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs deleted file mode 100644 index 66d89971..00000000 --- a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -//using System.Reflection; - -//[assembly: AssemblyVersion("3.0")] -//[assembly: AssemblyFileVersion("3.0.0")] -//[assembly: AssemblyInformationalVersion("3.0.0-develop")] From aad22d2ed5f38cbe32872de6c36e9c0df00a81fc Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 4 May 2021 17:43:11 +0100 Subject: [PATCH 19/81] Updated `.csproj` meta-data --- .../Umbraco.Community.Contentment.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 183492c8..fc245aa2 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0 + 3.0.0-develop Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher From 26339776c41b5f3ff5e51c232d0797b52131d716 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 10:30:09 +0100 Subject: [PATCH 20/81] Removed my `UmbracoXPathPathSyntaxParser` hack As the class/method has now been made public in Umbraco core. https://github.com/umbraco/Umbraco-CMS/blob/v9/9.0-beta001/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs#L11 --- .../Core/Xml/UmbracoXPathPathSyntaxParser.cs | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs diff --git a/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs deleted file mode 100644 index 99d8812a..00000000 --- a/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* Copyright © 2020 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using System; -using System.Collections.Generic; -using System.Reflection; -using Umbraco.Cms.Core.Xml; - -namespace Umbraco.Core.Xml -{ - // NOTE: Bah! `UmbracoXPathPathSyntaxParser` is marked as internal! It's either copy code, or reflection - here we go! - // https://github.com/umbraco/Umbraco-CMS/blob/release-8.6.1/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs#L11 - internal class UmbracoXPathPathSyntaxParser - { - public static string ParseXPathQuery( - string xpathExpression, - int? nodeContextId, - Func> getPath, - Func publishedContentExists) - { - try - { - var assembly = typeof(XPathVariable).Assembly; - var type = assembly.GetType("Umbraco.Core.Xml.UmbracoXPathPathSyntaxParser"); - var method = type.GetMethod(nameof(ParseXPathQuery), BindingFlags.Static | BindingFlags.Public); - return method.Invoke(null, new object[] { xpathExpression, nodeContextId, getPath, publishedContentExists }) as string; - } - catch { /* ಠ_ಠ */ } - - return xpathExpression; - } - } -} From b91db4efcd4092d29d8e67094bab74c21371a810 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 10:34:10 +0100 Subject: [PATCH 21/81] Removed "SolutionDir" parameter from build script Turns out that it's only useful if I'm building directly from Contentment's Visual Studio solution. --- build/build-assets.ps1 | 11 +++++------ .../Umbraco.Community.Contentment.csproj | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/build/build-assets.ps1 b/build/build-assets.ps1 index cf2fcfcf..24ae16a3 100644 --- a/build/build-assets.ps1 +++ b/build/build-assets.ps1 @@ -4,26 +4,25 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. param( - [string]$SolutionDir, [string]$TargetDir, [string]$ProjectName, [string]$ProjectDir, [string]$ConfigurationName ); -. "${SolutionDir}_vars.ps1"; +$rootDir = "${ProjectDir}..\.."; +. "${rootDir}\src\_vars.ps1"; Write-Host $ConfigurationName; if ($ConfigurationName -eq 'Debug') { - Write-Host $SolutionDir; Write-Host $TargetDir; Write-Host $ProjectName; Write-Host $ProjectDir; Write-Host $TargetDevWebsite; } -$targetFolder = "${SolutionDir}..\build\assets"; +$targetFolder = "${rootDir}\build\assets"; # If it already exists, delete it if (Test-Path -Path $targetFolder) { @@ -61,12 +60,12 @@ foreach($razorFile in $razorFiles){ # CSS - Bundle & Minify $targetCssPath = "${pluginFolder}contentment.css"; Get-Content -Raw -Path "${ProjectDir}**\**\*.css" | Set-Content -Encoding UTF8 -Path $targetCssPath; -& "${SolutionDir}..\tools\AjaxMinifier.exe" $targetCssPath -o $targetCssPath +& "${rootDir}\tools\AjaxMinifier.exe" $targetCssPath -o $targetCssPath # JS - Bundle & Minify $targetJsPath = "${pluginFolder}contentment.js"; Get-Content -Raw -Path "${ProjectDir}**\**\*.js" | Set-Content -Encoding UTF8 -Path $targetJsPath; -& "${SolutionDir}..\tools\AjaxMinifier.exe" $targetJsPath -o $targetJsPath +& "${rootDir}\tools\AjaxMinifier.exe" $targetJsPath -o $targetJsPath # In debug mode, copy the assets over to the local dev website if ($ConfigurationName -eq 'Debug' -AND -NOT($TargetDevWebsite -eq '')) { diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index fc245aa2..ad9f90a2 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -20,6 +20,6 @@ - + From 6bc563dd3c377418a83e2e59acc045655234b5c8 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 10:55:32 +0100 Subject: [PATCH 22/81] Fixed up more "using" namespaces --- .../Composing/CompositionExtensions.cs | 2 +- .../Composing/ContentmentServerVariablesParsing.cs | 2 +- .../Core/DictionaryExtensions.cs | 2 +- .../Core/Trees/TreeCollectionBuilderExtensions.cs | 2 +- .../CheckboxList/CheckboxListDataListEditor.cs | 2 ++ .../ContentBlocks/ContentBlockPreviewView.cs | 2 +- .../ContentBlocks/ContentBlocksConfigurationEditor.cs | 2 +- .../DataEditors/DataList/DataListApiController.cs | 2 +- .../DataList/DataListConfigurationEditor.cs | 4 ++-- .../DataEditors/DataList/DataListDataEditor.cs | 10 +++++----- .../DataList/DataSources/EnumDataListSource.cs | 2 +- .../DataList/DataSources/ExamineDataListSource.cs | 2 +- .../DataList/DataSources/JsonDataListSource.cs | 2 +- .../DataSources/PhysicalFileSystemDataSource.cs | 8 ++++---- .../DataList/DataSources/SqlDataListSource.cs | 4 +++- .../DataSources/TextDelimitedDataListSource.cs | 2 +- .../DataSources/UmbracoContentDataListSource.cs | 3 +-- .../UmbracoContentPropertiesDataListSource.cs | 5 ++--- .../DataSources/UmbracoContentXPathDataListSource.cs | 3 +-- .../DataSources/UmbracoDictionaryDataListSource.cs | 3 ++- .../DataSources/UmbracoImageCropDataListSource.cs | 1 + .../DataSources/UmbracoMembersDataListSource.cs | 1 - .../DataList/DataSources/UserDefinedDataListSource.cs | 3 ++- .../DataList/DataSources/XmlDataListSource.cs | 8 ++++---- .../DataSources/uCssClassNameDataListSource.cs | 2 +- .../DropdownList/DropdownListDataListEditor.cs | 1 + .../DataEditors/ItemPicker/ItemPickerDataListEditor.cs | 2 +- .../RadioButtonList/RadioButtonListDataListEditor.cs | 2 ++ .../DataEditors/Tags/TagsDataListEditor.cs | 2 ++ .../TemplatedList/TemplatedListDataListEditor.cs | 2 +- .../TextInput/TextInputConfigurationEditor.cs | 4 ++-- .../Telemetry/CompositionExtensions.cs | 2 +- .../Telemetry/ContentmentTelemetryComponent.cs | 2 +- 33 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs index c73fc4dc..da601210 100644 --- a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs @@ -9,7 +9,7 @@ // NOTE: This extension method class is deliberately using the Umbraco namespace, // as to reduce namespace imports and ease the developer experience. [LK] -namespace Umbraco.Core.Composing +namespace Umbraco.Cms.Core.Composing { public static partial class CompositionExtensions { diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs index 8663127f..94648cce 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Infrastructure.WebAssets; -using Umbraco.Core; +using Umbraco.Cms.Core; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Composing diff --git a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs index 2dc8b812..8f1dcf8d 100644 --- a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs +++ b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using Umbraco.Extensions; -namespace Umbraco.Core +namespace Umbraco.Cms.Core { internal static class DictionaryExtensions { diff --git a/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs index 231115b4..6d1f4955 100644 --- a/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs +++ b/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs @@ -15,7 +15,7 @@ using Umbraco.Cms.Core.Trees; using Umbraco.Cms.Web.BackOffice.Trees; -namespace Umbraco.Web +namespace Umbraco.Cms.Web { public static class TreeCollectionBuilderExtensions { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs index fb86458b..75d966ee 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs @@ -4,7 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index 96833642..de93d15f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -5,10 +5,10 @@ using System; using Microsoft.AspNetCore.Mvc.Rendering; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Web.Common.Views; using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Core; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs index e34ecf44..0e9bd278 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs @@ -7,12 +7,12 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -using Umbraco.Core; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs index cd7ea814..9d2add3a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs @@ -6,10 +6,10 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Core; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs index 71038ccb..7c7b2560 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs index 1c26835b..7b4479e3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs index 883459f1..db12cb79 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs @@ -10,11 +10,11 @@ using System.Reflection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Strings; using Umbraco.Community.Contentment.Web.Controllers; -using Umbraco.Core; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs index e25cc1e1..f70c899b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs @@ -8,10 +8,10 @@ using Examine; using Examine.LuceneEngine.Providers; using Examine.Search; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Strings; -using Umbraco.Core; using Umbraco.Extensions; using UmbConstants = Umbraco.Cms.Core.Constants; using Umbraco.Examine; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs index 486aff30..c6cbb0cc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs @@ -11,10 +11,10 @@ using System.Text; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Core; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs index c6ede9b0..b6562351 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using System.Linq; -using Umbraco.Core; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Hosting; -using Microsoft.Extensions.Logging; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index b04c650d..97a444df 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -10,10 +10,12 @@ //using System.Data.SqlServerCe; using System.IO; using System.Linq; +using Microsoft.Extensions.Configuration; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Core; using Umbraco.Extensions; using UmbConstants = Umbraco.Cms.Core.Constants; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs index 7c07ee62..dcd08d1c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs @@ -8,10 +8,10 @@ using System.IO; using System.Net; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Core; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 49a6b87b..2b05bc4f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -12,8 +12,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; -using Umbraco.Core; -using Umbraco.Core.Xml; +using Umbraco.Cms.Core.Xml; using Umbraco.Extensions; using UmbConstants = Umbraco.Cms.Core.Constants; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs index 34f26212..cf317541 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs @@ -7,13 +7,12 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; -using UmbConstants = Umbraco.Cms.Core.Constants; -using Umbraco.Cms.Core; using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs index c650a12d..78c0c473 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs @@ -12,8 +12,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; -using Umbraco.Core; -using Umbraco.Core.Xml; +using Umbraco.Cms.Core.Xml; using Umbraco.Extensions; using UmbConstants = Umbraco.Cms.Core.Constants; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs index fda58f39..ae9d241a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs @@ -7,9 +7,10 @@ using System.Globalization; using System.Linq; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; -using Umbraco.Core; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs index 4bd655b6..8dae9c0b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index d0279aee..7b40a2fa 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -14,7 +14,6 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; -using Umbraco.Core; using Umbraco.Extensions; using UmbConstants = Umbraco.Cms.Core.Constants; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs index 9d831585..b8f05aa3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs @@ -6,7 +6,8 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Core; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index 0c4c04ef..bd477e99 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -7,15 +7,15 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Xml; using System.Xml.XPath; -using Umbraco.Core; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs index 99169bb7..d89dcff9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs @@ -7,10 +7,10 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Core; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs index 5aa48fa1..e66f308d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs index 1ab96b19..d21ba595 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; diff --git a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs index a6359897..95a61b10 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs @@ -4,7 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs index 2833cd93..64960cc3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs @@ -4,7 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs index ce76e4db..7d8d5e5d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs index 979cd85e..e36371f3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs index d591f94c..1d10b742 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs @@ -10,7 +10,7 @@ // NOTE: This extension method class is deliberately using the Umbraco namespace, // as to reduce namespace imports and ease the developer experience. [LK] -namespace Umbraco.Core.Composing +namespace Umbraco.Cms.Core.Composing { public static partial class CompositionExtensions { diff --git a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs index 5dbd1ac1..7e7a1527 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs @@ -12,13 +12,13 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Community.Contentment.Configuration; using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Core; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Telemetry From 05d72789e58bc2cb0f7f432c77959fbc24956fdc Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 10:57:05 +0100 Subject: [PATCH 23/81] Fixed view paths for the DataList sources and editors Injected `IIOHelper`. --- .../DataEditors/Buttons/ButtonsDataListEditor.cs | 2 +- .../CheckboxList/CheckboxListDataListEditor.cs | 9 ++++++++- .../DataList/DataSources/ExamineDataListSource.cs | 4 ++-- .../UmbracoContentPropertiesDataListSource.cs | 2 +- .../DataSources/UmbracoDictionaryDataListSource.cs | 8 ++++++-- .../DataList/DataSources/UmbracoEntityDataListSource.cs | 2 +- .../DataList/DataSources/UmbracoMembersDataListSource.cs | 2 +- .../DataList/DataSources/UserDefinedDataListSource.cs | 9 ++++++++- .../DropdownList/DropdownListDataListEditor.cs | 2 +- .../DataEditors/ItemPicker/ItemPickerDataListEditor.cs | 2 +- .../RadioButtonList/RadioButtonListDataListEditor.cs | 9 ++++++++- .../DataEditors/Tags/TagsDataListEditor.cs | 9 ++++++++- .../TemplatedList/TemplatedListDataListEditor.cs | 2 +- 13 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs index f8adbed7..9b827819 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs @@ -101,6 +101,6 @@ public bool HasMultipleValues(Dictionary config) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => DataEditorViewPath; + public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs index 75d966ee..f3662156 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs @@ -14,6 +14,13 @@ public sealed class CheckboxListDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "checkbox-list.html"; + private readonly IIOHelper _ioHelper; + + public CheckboxListDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Checkbox List"; public string Description => "Select multiple values from a list of checkboxes."; @@ -43,6 +50,6 @@ public sealed class CheckboxListDataListEditor : IDataListEditor public OverlaySize OverlaySize => OverlaySize.Small; - public string View => DataEditorViewPath; + public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs index f70c899b..b7f9c2aa 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs @@ -88,7 +88,7 @@ public ExamineDataListSource( Key = "examineIndex", Name = "Examine Index", Description = "Select the Examine index.", - View = DropdownListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -104,7 +104,7 @@ public ExamineDataListSource( Key = "luceneQuery", Name = "Lucene query", Description = "Enter your raw Lucene expression to query Examine with.", - View = CodeEditorDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, "text" }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs index cf317541..9c153d3c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs @@ -64,7 +64,7 @@ public IEnumerable Fields Key = "contentType", Name = "Content Type", Description = "Select a Content Type to list the properties from.", - View = ItemPickerDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorViewPath), Config = new Dictionary { { "enableFilter", items.Count > 5 ? Constants.Values.True : Constants.Values.False }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs index ae9d241a..0ac96ce6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs @@ -19,10 +19,14 @@ namespace Umbraco.Community.Contentment.DataEditors public sealed class UmbracoDictionaryDataListSource : IDataListSource { private readonly ILocalizationService _localizationService; + private readonly IIOHelper _ioHelper; - public UmbracoDictionaryDataListSource(ILocalizationService localizationService) + public UmbracoDictionaryDataListSource( + ILocalizationService localizationService, + IIOHelper ioHelper) { _localizationService = localizationService; + _ioHelper = ioHelper; } public string Name => "Umbraco Dictionary Items"; @@ -40,7 +44,7 @@ public UmbracoDictionaryDataListSource(ILocalizationService localizationService) Key = "item", Name = "Dictionary item", Description = "Select a parent dictionary item to display the child items.", - View = DictionaryPickerDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DictionaryPickerDataEditor.DataEditorViewPath), Config = new Dictionary { { MaxItemsConfigurationField.MaxItems, 1 } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index 6abad451..f3fc162c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -80,7 +80,7 @@ public UmbracoEntityDataListSource( Key = "entityType", Name = "Entity type", Description = "Select the Umbraco entity type to use.", - View = DropdownListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary() { { "allowEmpty", Constants.Values.False }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index 7b40a2fa..3ece009f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -78,7 +78,7 @@ public IEnumerable Fields Key = "memberType", Name = "Member Type", Description = "Select a member type to filter the members by. If left empty, all members will be used.", - View = ItemPickerDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorViewPath), Config = new Dictionary { { "addButtonLabelKey", "defaultdialogs_selectMemberType" }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs index b8f05aa3..3984a2ee 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs @@ -16,6 +16,13 @@ namespace Umbraco.Community.Contentment.DataEditors [Cms.Core.Composing.HideFromTypeFinder] public sealed class UserDefinedDataListSource : IDataListSource { + private readonly IIOHelper _ioHelper; + + public UserDefinedDataListSource(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "User-defined List"; public string Description => "Manually configure the items for the data source."; @@ -31,7 +38,7 @@ public sealed class UserDefinedDataListSource : IDataListSource Key = "items", Name = "Options", Description = "Configure the option items for the data list.

Please try to avoid using duplicate values, as this may cause adverse issues with list editors.", - View = DataListDataEditor.DataEditorListEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataListDataEditor.DataEditorListEditorViewPath), Config = new Dictionary() { { "confirmRemoval", Constants.Values.True }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs index e66f308d..2f683133 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs @@ -57,6 +57,6 @@ public DropdownListDataListEditor(IIOHelper ioHelper) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => DataEditorViewPath; + public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs index d21ba595..ef8042ca 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs @@ -129,6 +129,6 @@ public bool HasMultipleValues(Dictionary config) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => DataEditorViewPath; + public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs index 95a61b10..1f837a31 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs @@ -14,6 +14,13 @@ public sealed class RadioButtonListDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "radio-button-list.html"; + private readonly IIOHelper _ioHelper; + + public RadioButtonListDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Radio Button List"; public string Description => "Select a single value from a list of radio buttons."; @@ -37,6 +44,6 @@ public sealed class RadioButtonListDataListEditor : IDataListEditor public OverlaySize OverlaySize => OverlaySize.Small; - public string View => DataEditorViewPath; + public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs index 64960cc3..7664afa9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs @@ -14,6 +14,13 @@ public sealed class TagsDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "tags.html"; + private readonly IIOHelper _ioHelper; + + public TagsDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Tags"; public string Description => "Select items from an Umbraco Tags-like interface."; @@ -43,6 +50,6 @@ public sealed class TagsDataListEditor : IDataListEditor public OverlaySize OverlaySize => OverlaySize.Small; - public string View => DataEditorViewPath; + public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs index 7d8d5e5d..b07bf625 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs @@ -85,6 +85,6 @@ public bool HasMultipleValues(Dictionary config) public OverlaySize OverlaySize => OverlaySize.Medium; - public string View => DataEditorViewPath; + public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); } } From 0cda4b50ef855b88b4cc4d07cdc5e5371f1b6e01 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 10:58:08 +0100 Subject: [PATCH 24/81] FileSystem data-source: Refactored to list from the webroot, as opposed to the content root. --- .../DataList/DataSources/PhysicalFileSystemDataSource.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs index b6562351..5211fbe8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { @@ -50,7 +51,7 @@ public PhysicalFileSystemDataSource( { Key = "path", Name = "Folder Path", - Description = "Enter the relative path of the folder. e.g. ~/css", + Description = "Enter the relative path of the folder. e.g. ~/css
Please note, this is relative to the web root folder, e.g. wwwroot.", View = "textstring", }, new ConfigurationField @@ -89,7 +90,7 @@ public IEnumerable GetItems(Dictionary config) ? filter : "*.*"; - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, _hostingEnvironment.MapPathContentRoot(virtualRoot), _hostingEnvironment.ToAbsolute(virtualRoot)); + var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, _hostingEnvironment.MapPathWebRoot(virtualRoot), _hostingEnvironment.ToAbsolute(virtualRoot)); var files = fs.GetFiles(".", fileFilter); return files.Select(x => new DataListItem @@ -97,7 +98,7 @@ public IEnumerable GetItems(Dictionary config) Name = friendlyName == true ? x.SplitPascalCasing(_shortStringHelper).ToFriendlyName() : x, Value = virtualRoot + x, Description = virtualRoot + x, - Icon = Cms.Core.Constants.Icons.DefaultIcon, + Icon = UmbConstants.Icons.DefaultIcon, }); } } From 0f385ef0eee197dc5c9f27bf10a8be5ece488a22 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 10:59:26 +0100 Subject: [PATCH 25/81] SQL data-source: Refactored I'm still unsure how to do agnostic SQL queries, (for either SQL Server or SQLCE). If Umbraco can do it, why can't I? --- .../DataList/DataSources/SqlDataListSource.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index 97a444df..ac583eec 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -26,25 +26,29 @@ public sealed class SqlDataListSource : IDataListSource private readonly string _codeEditorMode; private readonly IEnumerable _connectionStrings; private readonly IIOHelper _ioHelper; + private readonly IConfiguration _configuration; public SqlDataListSource( IHostingEnvironment hostingEnvironment, - IIOHelper ioHelper) + IIOHelper ioHelper, + IConfiguration configuration) { // NOTE: Umbraco doesn't ship with SqlServer mode, so we check if its been added manually, otherwise defautls to Razor. _codeEditorMode = File.Exists(hostingEnvironment.MapPathWebRoot("~/umbraco/lib/ace-builds/src-min-noconflict/mode-sqlserver.js")) ? "sqlserver" : "razor"; - _connectionStrings = ConfigurationManager.ConnectionStrings - .Cast() + _connectionStrings = configuration + .GetSection("ConnectionStrings") + .GetChildren() .Select(x => new DataListItem { - Name = x.Name, - Value = x.Name + Name = x.Key, + Value = x.Key }); _ioHelper = ioHelper; + _configuration = configuration; } public string Name => "SQL Data"; @@ -110,27 +114,38 @@ public IEnumerable GetItems(Dictionary config) var items = new List(); var query = config.GetValueAs("query", string.Empty); - var connectionString = config.GetValueAs("connectionString", string.Empty); + var connectionStringName = config.GetValueAs("connectionString", string.Empty); - if (string.IsNullOrWhiteSpace(query) == true || string.IsNullOrWhiteSpace(connectionString) == true) + if (string.IsNullOrWhiteSpace(query) == true || string.IsNullOrWhiteSpace(connectionStringName) == true) { return items; } - var settings = ConfigurationManager.ConnectionStrings[connectionString]; - if (settings == null) + var connectionString = _configuration.GetConnectionString(connectionStringName); + if (string.IsNullOrWhiteSpace(connectionString) == true) { return items; } + var configConnectionString = new ConfigConnectionString(connectionStringName, connectionString); + if (configConnectionString.IsConnectionStringConfigured() == false) + { + return items; + } + + // TODO: [LK:2021-05-07] Review SQLCE // NOTE: SQLCE uses a different connection/command. I'm trying to keep this as generic as possible, without resorting to using NPoco. [LK] + // I've tried digging around Umbraco's `IUmbracoDatabase` layer, but I couldn't get my head around it. + // At the end of the day, if the user has SQLCE configured, it'd be nice for them to query it. + // But I don't want to add an assembly dependency (for SQLCE) to Contentment itself. I'd like to leverage Umbraco's code. + //if (settings.ProviderName.InvariantEquals(UmbConstants.DatabaseProviders.SqlCe) == true) //{ // items.AddRange(GetSqlItems(query, settings.ConnectionString)); //} //else //{ - items.AddRange(GetSqlItems(query, settings.ConnectionString)); + items.AddRange(GetSqlItems(query, connectionString)); //} return items; From af475f49f272f5bff9d06e0a89975e79218aa188 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 11:00:39 +0100 Subject: [PATCH 26/81] UmbracoEntity data-source: Refactored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As injecting `IEntityService` directly caused some internal timeout. Wrapping in a `Lazy<>` works around this. `¯\_(ツ)_/¯` --- .../DataList/DataSources/UmbracoEntityDataListSource.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index f3fc162c..9116a4c6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -46,12 +46,12 @@ public sealed class UmbracoEntityDataListSource : IDataListSource, IDataListSour { nameof(UmbracoObjectTypes.MemberType), UmbConstants.Icons.MemberType }, }; - private readonly IEntityService _entityService; + private readonly Lazy _entityService; private readonly IShortStringHelper _shortStringHelper; private readonly IIOHelper _ioHelper; public UmbracoEntityDataListSource( - IEntityService entityService, + Lazy entityService, IShortStringHelper shortStringHelper, IIOHelper ioHelper) { @@ -99,7 +99,7 @@ public IEnumerable GetItems(Dictionary config) { var icon = EntityTypeIcons.GetValueAs(entityType, UmbConstants.Icons.DefaultIcon); - return _entityService + return _entityService.Value .GetAll(objectType) .OrderBy(x => x.Name) .Select(x => new DataListItem @@ -118,7 +118,7 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { return UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false - ? _entityService.Get(udi.Guid) + ? _entityService.Value.Get(udi.Guid) : default; } } From 4cc1e4a03658f9a9b172cccaf6e3dda142386844 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 11:03:01 +0100 Subject: [PATCH 27/81] XML data-source: Found a bug Calling `XPathDocument` with a HTTPS URL throws an exception. Regular HTTP is fine. The JSON data-source uses `WebClient`, which is fine with a HTTPS URL. Unsure whether this is a bug with my code, my machine or `XPathDocument`. I'll investigate later and refactor as necessary. --- .../DataEditors/DataList/DataSources/XmlDataListSource.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index bd477e99..adca8068 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -123,15 +123,20 @@ public IEnumerable GetItems(Dictionary config) try { + // TODO: [LK:2021-05-07] Doesn't work for HTTPS URLs, throws a `HttpRequestException`. doc = new XPathDocument(path); } + catch(HttpRequestException ex) + { + _logger.LogError(ex, $"Unable to retrieve data from '{path}'."); + } catch (WebException ex) { _logger.LogError(ex, $"Unable to retrieve data from '{path}'."); } catch (XmlException ex) { - _logger.LogError(ex, "Unable to load XML data."); + _logger.LogError(ex, $"Unable to load XML data from '{path}'."); } if (doc == null) From 3f5087fda19ddea82247aa4d89ca08f14ebfb86f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 11:03:37 +0100 Subject: [PATCH 28/81] UmbracoImageCrop data-source: Fixed view paths Injected `IIOHelper`. --- .../DataList/DataSources/UmbracoImageCropDataListSource.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs index 8dae9c0b..eb8eaeb5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs @@ -18,10 +18,12 @@ namespace Umbraco.Community.Contentment.DataEditors public sealed class UmbracoImageCropDataListSource : IDataListSource { private readonly IDataTypeService _dataTypeService; + private readonly IIOHelper _ioHelper; - public UmbracoImageCropDataListSource(IDataTypeService dataTypeService) + public UmbracoImageCropDataListSource(IDataTypeService dataTypeService, IIOHelper ioHelper) { _dataTypeService = dataTypeService; + _ioHelper = ioHelper; } public string Name => "Umbraco Image Crops"; @@ -52,7 +54,7 @@ public IEnumerable Fields Key = "imageCropper", Name = "Image Cropper", Description = "Select a Data Type that uses the Image Cropper.", - View = RadioButtonListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, items }, From cf3612e36abeaffcd8d9c4315dbf889fcec0a859 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 11:04:26 +0100 Subject: [PATCH 29/81] ContentmentTreeController - made class public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems that Umbraco v9 doesn't like `internal` Tree classes. This worked on v8. `¯\_(ツ)_/¯` --- .../Trees/ContentmentTreeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs index 820c1a4b..cebea305 100644 --- a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs +++ b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs @@ -24,7 +24,7 @@ namespace Umbraco.Community.Contentment.Trees TreeTitle = Constants.Internals.ProjectName, TreeUse = TreeUse.Main)] [PluginController(Constants.Internals.PluginControllerName)] - internal sealed class ContentmentTreeController : TreeController + public sealed class ContentmentTreeController : TreeController { public ContentmentTreeController( ILocalizedTextService localizedTextService, From cf6dc943c84ed24b93af73a5786b48591fe5ba42 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 11:06:27 +0100 Subject: [PATCH 30/81] Enum data-source API: Refactored I expected subtle changes with Type Reflection for .NET Core. I'd prefer to not have to rely on silently swallowing an exception, in fact I'd prefer not to have a try/catch at all - and do it "properly" --- .../EnumDataListSourceApiController.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs index 8a83b515..f56b1b27 100644 --- a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs +++ b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs @@ -6,13 +6,12 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Reflection; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Community.Contentment.DataEditors; using Umbraco.Extensions; @@ -20,7 +19,6 @@ namespace Umbraco.Community.Contentment.Web.Controllers { [EditorBrowsable(EditorBrowsableState.Never)] [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] - [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] public sealed class EnumDataSourceApiController : UmbracoAuthorizedJsonController { internal const string GetAssembliesUrl = "backoffice/Contentment/EnumDataSourceApi/GetAssemblies"; @@ -41,28 +39,37 @@ public IEnumerable GetAssemblies() var options = new SortedDictionary(); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - if (assemblies?.Length > 0) + if (assemblies?.Any() == true) { foreach (var assembly in assemblies) { if (options.ContainsKey(assembly.FullName) == true || assembly.IsDynamic == true) + { continue; + } var hasEnums = false; - if (assembly.ExportedTypes != null) + try { - foreach (var exportedType in assembly.ExportedTypes) + var exportedTypes = assembly.GetExportedTypes(); + if (exportedTypes != null) { - if (exportedType.IsEnum == true) + foreach (var exportedType in exportedTypes) { - hasEnums = true; - break; + if (exportedType.IsEnum == true) + { + hasEnums = true; + break; + } } } } + catch (TypeLoadException) { /* ¯\_(ツ)_/¯ */ } if (hasEnums == false) + { continue; + } if (assembly.FullName.StartsWith(App_Code) == true && options.ContainsKey(App_Code) == false) { @@ -85,10 +92,13 @@ public IEnumerable GetEnums(string assembly) var options = new SortedDictionary(); var types = Assembly.Load(assembly).GetTypes(); + foreach (var type in types) { if (type.IsEnum == false) + { continue; + } options.Add(type.FullName, new DataListItem { Name = type.Name.SplitPascalCasing(_shortStringHelper), Value = type.FullName }); } From f596dde5b1c58b674c77f0b329d4a2a3caaa1619 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 May 2021 12:05:16 +0100 Subject: [PATCH 31/81] `CultureTypes.SpecificCultures` returns "World" on .NET 5 I wasn't expecting that. --- .../DataList/DataSources/CountriesDataListSource.cs | 3 ++- .../DataList/DataSources/CurrenciesDataListSource.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs index e14490f2..540af63a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs @@ -33,11 +33,12 @@ public IEnumerable GetItems(Dictionary config) return CultureInfo .GetCultures(CultureTypes.SpecificCultures) .Select(x => new RegionInfo(x.Name)) + .Where(x => x.GeoId != 39070) // Excludes "World/001" .DistinctBy(x => x.DisplayName) .OrderBy(x => x.DisplayName) .Select(x => new DataListItem { - Name = x.DisplayName, + Name = x.EnglishName, Value = x.TwoLetterISORegionName }); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs index d4626939..9fd5a811 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs @@ -33,6 +33,7 @@ public IEnumerable GetItems(Dictionary config) return CultureInfo .GetCultures(CultureTypes.SpecificCultures) .Select(x => new RegionInfo(x.Name)) + .Where(x => x.GeoId != 39070) // Excludes "World/001" .DistinctBy(x => x.ISOCurrencySymbol) .OrderBy(x => x.ISOCurrencySymbol) .Select(x => new DataListItem From 30ea2ed27c76b511624b174fe8e9151d425994d3 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 11 May 2021 15:20:45 +0100 Subject: [PATCH 32/81] Globalization data-source: amended to use the English name. --- .../DataList/DataSources/CountriesDataListSource.cs | 4 ++-- .../DataList/DataSources/CurrenciesDataListSource.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs index 540af63a..967c46a9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs @@ -34,8 +34,8 @@ public IEnumerable GetItems(Dictionary config) .GetCultures(CultureTypes.SpecificCultures) .Select(x => new RegionInfo(x.Name)) .Where(x => x.GeoId != 39070) // Excludes "World/001" - .DistinctBy(x => x.DisplayName) - .OrderBy(x => x.DisplayName) + .DistinctBy(x => x.TwoLetterISORegionName) + .OrderBy(x => x.EnglishName) .Select(x => new DataListItem { Name = x.EnglishName, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs index 9fd5a811..f55c7e6f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Umbraco.Extensions; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -35,7 +35,7 @@ public IEnumerable GetItems(Dictionary config) .Select(x => new RegionInfo(x.Name)) .Where(x => x.GeoId != 39070) // Excludes "World/001" .DistinctBy(x => x.ISOCurrencySymbol) - .OrderBy(x => x.ISOCurrencySymbol) + .OrderBy(x => x.CurrencyEnglishName) .Select(x => new DataListItem { Name = x.CurrencyEnglishName, From 50ce09795350995f29c2837f6e32de4c16237377 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 11 May 2021 15:24:37 +0100 Subject: [PATCH 33/81] Added ContentmentSettings class --- .../Configuration/ContentmentSettings.cs | 16 ++++++++++++++++ src/Umbraco.Community.Contentment/Constants.cs | 2 ++ 2 files changed, 18 insertions(+) create mode 100644 src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs diff --git a/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs b/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs new file mode 100644 index 00000000..ce9e70e5 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs @@ -0,0 +1,16 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +namespace Umbraco.Community.Contentment +{ + public class ContentmentSettings + { + public bool DisableTree { get; set; } = false; + + public bool DisableTelemetry { get; set; } = false; + + public bool UnlockFeatures { get; set; } = false; + } +} diff --git a/src/Umbraco.Community.Contentment/Constants.cs b/src/Umbraco.Community.Contentment/Constants.cs index 88724dac..e1d4209f 100644 --- a/src/Umbraco.Community.Contentment/Constants.cs +++ b/src/Umbraco.Community.Contentment/Constants.cs @@ -28,6 +28,8 @@ internal static partial class Internals internal const string BackOfficePathRoot = PackagePathRoot + "backoffice/" + TreeAlias + "/"; internal const string TreeAlias = ProjectAlias; + + internal const string ConfigurationSection = "Umbraco:Contentment"; } internal static partial class Conventions From 47978144a83f2e99af156177b244bd4792c52057 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 11 May 2021 15:25:03 +0100 Subject: [PATCH 34/81] =?UTF-8?q?=E2=9A=A0=20=20[BREAKING-CHANGE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed `CompositionExtensions` class to `UmbracoBuilderExtensions` Added extension method for `AddContentment` to configure the settings. Changed the `ContentmentListItems` extension method to be `WithContentmentListItems`, it can now accept an `Action<>` and return the `IUmbracoBuilder`. Meaning it's more useful in the Startup code. --- .../Composing/CompositionExtensions.cs | 44 ----------- .../Composing/ContentmentComposer.cs | 34 +------- .../Composing/UmbracoBuilderExtensions.cs | 78 +++++++++++++++++++ 3 files changed, 80 insertions(+), 76 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs create mode 100644 src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs diff --git a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs deleted file mode 100644 index da601210..00000000 --- a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright © 2019 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Community.Contentment.Composing; -using Umbraco.Community.Contentment.DataEditors; - -// NOTE: This extension method class is deliberately using the Umbraco namespace, -// as to reduce namespace imports and ease the developer experience. [LK] -namespace Umbraco.Cms.Core.Composing -{ - public static partial class CompositionExtensions - { - public static ContentmentListItemCollectionBuilder ContentmentListItems(this IUmbracoBuilder builder) - { - return builder.WithCollectionBuilder(); - } - - public static IUmbracoBuilder UnlockContentment(this IUmbracoBuilder builder) - { - builder - .WithCollectionBuilder() - // Data List - Data Sources - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - ; - - return builder; - } - } -} diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs index e6e0c0bd..4e1a09a2 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs @@ -3,47 +3,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.WebAssets; -using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Core.Composing; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Composing { - // TODO: [LK:2021-04-03] v9 Review this. - //[ComposeAfter(typeof(WebInitialComposer))] - internal sealed class ContentmentComposer : IUserComposer + internal sealed class ContentmentComposer : IComposer { public void Compose(IUmbracoBuilder builder) { - builder - .ContentmentListItems() - .Add(() => builder.TypeLoader.GetTypes()) - ; - - builder.Services.AddUnique(); - - //if (_runtimeState.Level > RuntimeLevel.Install) - { - builder - .Components() - .Append() - ; - - builder.AddNotificationHandler(); - } - - //if (_runtimeState.Level == RuntimeLevel.Run) - { - // if (ContentmentTelemetryComponent.Disabled == false) - // { - // builder.EnableContentmentTelemetry(); - // } - } + builder.AddContentment(); } } } diff --git a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs new file mode 100644 index 00000000..2300482d --- /dev/null +++ b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs @@ -0,0 +1,78 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.WebAssets; +using Umbraco.Community.Contentment; +using Umbraco.Community.Contentment.Composing; +using Umbraco.Community.Contentment.DataEditors; +using Umbraco.Community.Contentment.Telemetry; + +namespace Umbraco.Extensions +{ + public static partial class UmbracoBuilderExtensions + { + public static IUmbracoBuilder AddContentment(this IUmbracoBuilder builder, Action configure = default) + { + // TODO: [LK:2021-05-10] Is there a way to combine these? e.g. `configure` will fallback on values from appSettings? + _ = configure is not null + ? builder.Services.Configure(configure) + : builder.Services.Configure(builder.Config.GetSection(Constants.Internals.ConfigurationSection)); + + builder + .WithCollectionBuilder() + .Add(() => builder.TypeLoader.GetTypes()) + ; + + builder.Services.AddUnique(); + + builder.Components().Append(); + + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + + return builder; + } + + public static IUmbracoBuilder WithContentmentListItems(this IUmbracoBuilder builder, Action configure) + { + var items = builder.WithCollectionBuilder(); + + if (configure is not null) + { + configure(items); + } + + return builder; + } + + public static IUmbracoBuilder UnlockContentment(this IUmbracoBuilder builder) + { + builder + .WithCollectionBuilder() + // Data List - Data Sources + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + ; + + return builder; + } + } +} From 48ede32e5f84cdd8c68297d8b22b548cf3a64982 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 11 May 2021 15:27:25 +0100 Subject: [PATCH 35/81] =?UTF-8?q?=E2=9A=A0=20=20[BREAKING-CHANGE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed (reduced) the namespace for the `ContentmentVersion` class --- .../Composing/ContentmentServerVariablesParsing.cs | 2 +- .../Configuration/ContentmentVersion.cs | 2 +- .../Migrations/Install/RegisterUmbracoPackageEntry.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs index 94648cce..7215bbc1 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs @@ -21,8 +21,8 @@ public void Handle(ServerVariablesParsing notification) umbracoPlugins.Add(Constants.Internals.ProjectAlias, new { name = Constants.Internals.ProjectName, - version = Configuration.ContentmentVersion.SemanticVersion.ToSemanticString(), telemetry = Telemetry.ContentmentTelemetryComponent.Disabled == false, + version = ContentmentVersion.SemanticVersion.ToSemanticString(), }); } } diff --git a/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs b/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs index 23fcfd66..32fdf867 100644 --- a/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs +++ b/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs @@ -12,7 +12,7 @@ using System.Reflection; using Umbraco.Cms.Core.Semver; -namespace Umbraco.Community.Contentment.Configuration +namespace Umbraco.Community.Contentment { public static class ContentmentVersion { diff --git a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs index c937cad9..5e91ce35 100644 --- a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs +++ b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs @@ -39,7 +39,7 @@ public override void Migrate() License = Constants.Package.License, LicenseUrl = Constants.Package.LicenseUrl, UmbracoVersion = Constants.Package.MinimumSupportedUmbracoVersion, - Version = Configuration.ContentmentVersion.Version.ToString(), + Version = ContentmentVersion.Version.ToString(), Readme = "", }); } From cfd421a99b25ad4f1fafa7159f82d20f1b1f59d7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 11 May 2021 15:28:09 +0100 Subject: [PATCH 36/81] ServerVariables - injected Contentment settings To flag if the Telemetry feature is enabled. --- .../Composing/ContentmentServerVariablesParsing.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs index 7215bbc1..c1fe9e46 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs @@ -8,11 +8,19 @@ using Umbraco.Cms.Infrastructure.WebAssets; using Umbraco.Cms.Core; using Umbraco.Extensions; +using Microsoft.Extensions.Options; namespace Umbraco.Community.Contentment.Composing { internal sealed class ContentmentServerVariablesParsing : INotificationHandler { + private readonly ContentmentSettings _contentmentSettings; + + public ContentmentServerVariablesParsing(IOptions contentmentSettings) + { + _contentmentSettings = contentmentSettings.Value; + } + public void Handle(ServerVariablesParsing notification) { if (notification.ServerVariables.TryGetValueAs("umbracoPlugins", out Dictionary umbracoPlugins) == true && @@ -21,8 +29,8 @@ public void Handle(ServerVariablesParsing notification) umbracoPlugins.Add(Constants.Internals.ProjectAlias, new { name = Constants.Internals.ProjectName, - telemetry = Telemetry.ContentmentTelemetryComponent.Disabled == false, version = ContentmentVersion.SemanticVersion.ToSemanticString(), + telemetry = _contentmentSettings.DisableTelemetry == false, }); } } From 0a4728073b0c27449bde35b2dda57ccad06cf05a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 11 May 2021 15:29:13 +0100 Subject: [PATCH 37/81] Renamed `ContentmentTelemetryComponent` to `ContentmentTelemetryHandler` Aligns better with it being an `INotificationHandler`. Refactored to use the `DisableTelemetry` option from the settings. --- .../Telemetry/CompositionExtensions.cs | 46 ------------------- ...nent.cs => ContentmentTelemetryHandler.cs} | 22 ++++----- 2 files changed, 11 insertions(+), 57 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs rename src/Umbraco.Community.Contentment/Telemetry/{ContentmentTelemetryComponent.cs => ContentmentTelemetryHandler.cs} (88%) diff --git a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs deleted file mode 100644 index 1d10b742..00000000 --- a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright © 2021 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Community.Contentment.Telemetry; - -// NOTE: This extension method class is deliberately using the Umbraco namespace, -// as to reduce namespace imports and ease the developer experience. [LK] -namespace Umbraco.Cms.Core.Composing -{ - public static partial class CompositionExtensions - { - public static IUmbracoBuilder EnableContentmentTelemetry(this IUmbracoBuilder builder) - { - ContentmentTelemetryComponent.Disabled = false; - - // TODO: [LK:2021-04-30] v9 Review this. - //builder - // .Components() - // .Append() - //; - - // TODO: [LK:2021-04-30] v9 Maybe renamed this to `ContentmentTelemetryHandler` - builder.AddNotificationHandler, ContentmentTelemetryComponent>(); - - return builder; - } - - public static IUmbracoBuilder DisableContentmentTelemetry(this IUmbracoBuilder builder) - { - ContentmentTelemetryComponent.Disabled = true; - - // TODO: [LK:2021-04-30] v9 Review this. - //builder - // .Components() - // .Remove() - //; - - return builder; - } - } -} diff --git a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs similarity index 88% rename from src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs rename to src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs index 7e7a1527..59b6a1dd 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs @@ -17,30 +17,30 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -using Umbraco.Community.Contentment.Configuration; using Umbraco.Community.Contentment.DataEditors; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Telemetry { - // TODO: [LK:2021-04-30] v9 Maybe renamed this to `ContentmentTelemetryHandler` - // Currently keeping as `ContentmentTelemetryComponent` for version-control tracking. - internal sealed class ContentmentTelemetryComponent : INotificationHandler> + internal sealed class ContentmentTelemetryHandler : INotificationHandler> { + private readonly ContentmentSettings _contentmentSettings; + private readonly GlobalSettings _globalSettings; private readonly IUmbracoVersion _umbracoVersion; - private readonly IOptions _globalSettings; - internal static bool Disabled { get; set; } - - public ContentmentTelemetryComponent(IOptions globalSettings, IUmbracoVersion umbracoVersion) + public ContentmentTelemetryHandler( + IOptions contentmentSettings, + IOptions globalSettings, + IUmbracoVersion umbracoVersion) { - _globalSettings = globalSettings; + _contentmentSettings = contentmentSettings.Value; + _globalSettings = globalSettings.Value; _umbracoVersion = umbracoVersion; } public void Handle(SavedNotification notification) { - if (Disabled == true) + if (_contentmentSettings.DisableTelemetry == true) { return; } @@ -96,7 +96,7 @@ array[0] is JObject item && } } - var umbracoId = Guid.TryParse(_globalSettings.Value.Id, out var telemetrySiteIdentifier) == true + var umbracoId = Guid.TryParse(_globalSettings.Id, out var telemetrySiteIdentifier) == true ? telemetrySiteIdentifier : Guid.Empty; From d9b0e063a3106f5c8e4276fca0c1ae79eb65011f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 11 May 2021 15:29:34 +0100 Subject: [PATCH 38/81] Re-enabled the `DisableContentmentTree()` extension --- .../Trees/CompositionExtensions.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs index 2a742aef..ca5519c8 100644 --- a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs @@ -4,24 +4,20 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web; using Umbraco.Community.Contentment.Trees; using Umbraco.Extensions; -using Umbraco.Web; -// NOTE: This extension method class is deliberately using the Umbraco namespace, -// as to reduce namespace imports and ease the developer experience. [LK] namespace Umbraco.Core.Composing { public static partial class CompositionExtensions { public static IUmbracoBuilder DisableContentmentTree(this IUmbracoBuilder builder) { - // TODO: [LK:2021-05-03] Commented out, as I had to comment out `ContentmentTreeController` for now. - - //builder - // .Trees() - // .RemoveTreeController() - //; + builder + .Trees() + .RemoveTreeController() + ; return builder; } From ea723d77975f327427041b67ff02dc97a3e4c0d6 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:30:35 +0100 Subject: [PATCH 39/81] Upgraded Umbraco.Cms dependency to v9.0.0-beta002 --- .../Umbraco.Community.Contentment.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index ad9f90a2..531f707e 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -16,8 +16,8 @@ git - - + + From 10ad90a460f44f366de4410c8fa18b74bc2fbf54 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:30:46 +0100 Subject: [PATCH 40/81] Renamed `ServerVariablesParsing` to `ServerVariablesParsingNotification` I moan a little bit about it here: :wink: https://github.com/leekelleher/umbraco-contentment/discussions/105#discussioncomment-730011 --- .../Composing/ContentmentServerVariablesParsing.cs | 6 +++--- .../Composing/UmbracoBuilderExtensions.cs | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs index c1fe9e46..67ac6da9 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs @@ -5,14 +5,14 @@ using System.Collections.Generic; using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Infrastructure.WebAssets; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core; using Umbraco.Extensions; using Microsoft.Extensions.Options; namespace Umbraco.Community.Contentment.Composing { - internal sealed class ContentmentServerVariablesParsing : INotificationHandler + internal sealed class ContentmentServerVariablesParsing : INotificationHandler { private readonly ContentmentSettings _contentmentSettings; @@ -21,7 +21,7 @@ public ContentmentServerVariablesParsing(IOptions contentme _contentmentSettings = contentmentSettings.Value; } - public void Handle(ServerVariablesParsing notification) + public void Handle(ServerVariablesParsingNotification notification) { if (notification.ServerVariables.TryGetValueAs("umbracoPlugins", out Dictionary umbracoPlugins) == true && umbracoPlugins.ContainsKey(Constants.Internals.ProjectAlias) == false) diff --git a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs index 2300482d..6ffa4b2f 100644 --- a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs @@ -6,9 +6,7 @@ using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Infrastructure.WebAssets; +using Umbraco.Cms.Core.Notifications; using Umbraco.Community.Contentment; using Umbraco.Community.Contentment.Composing; using Umbraco.Community.Contentment.DataEditors; @@ -20,7 +18,7 @@ public static partial class UmbracoBuilderExtensions { public static IUmbracoBuilder AddContentment(this IUmbracoBuilder builder, Action configure = default) { - // TODO: [LK:2021-05-10] Is there a way to combine these? e.g. `configure` will fallback on values from appSettings? + // TODO: [v9] [LK:2021-05-10] Is there a way to combine these? e.g. `configure` will fallback on values from appSettings? _ = configure is not null ? builder.Services.Configure(configure) : builder.Services.Configure(builder.Config.GetSection(Constants.Internals.ConfigurationSection)); @@ -34,7 +32,7 @@ public static IUmbracoBuilder AddContentment(this IUmbracoBuilder builder, Actio builder.Components().Append(); - builder.AddNotificationHandler(); + builder.AddNotificationHandler(); builder.AddNotificationHandler(); return builder; From 43af9a7a8bb9f51c15012f3e9596d021a47cafd5 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:31:38 +0100 Subject: [PATCH 41/81] Telemetry - tiny refactor Noticed that I was using `SavedNotification` instead of `DataTypeSavedNotification`. --- .../Telemetry/ContentmentTelemetryHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs index 59b6a1dd..7dbe5b58 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs @@ -16,13 +16,13 @@ using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; using Umbraco.Community.Contentment.DataEditors; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Telemetry { - internal sealed class ContentmentTelemetryHandler : INotificationHandler> + internal sealed class ContentmentTelemetryHandler : INotificationHandler { private readonly ContentmentSettings _contentmentSettings; private readonly GlobalSettings _globalSettings; @@ -38,7 +38,7 @@ public ContentmentTelemetryHandler( _umbracoVersion = umbracoVersion; } - public void Handle(SavedNotification notification) + public void Handle(DataTypeSavedNotification notification) { if (_contentmentSettings.DisableTelemetry == true) { From e54dca55946f9c7878ab3e26214dbc22dc6bef83 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:35:05 +0100 Subject: [PATCH 42/81] Constants - temporarily removed leading tilde. There's a regression issue with Umbraco v9 for when using the `DataEditorAttribute`. https://github.com/umbraco/Umbraco-CMS/issues/10265 This currently means that Contentment will not currently work within virtual folders. --- src/Umbraco.Community.Contentment/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/Constants.cs b/src/Umbraco.Community.Contentment/Constants.cs index e1d4209f..434a538e 100644 --- a/src/Umbraco.Community.Contentment/Constants.cs +++ b/src/Umbraco.Community.Contentment/Constants.cs @@ -21,7 +21,7 @@ internal static partial class Internals internal const string EditorsPathRoot = PackagePathRoot + "editors/"; - internal const string PackagePathRoot = "~/App_Plugins/" + ProjectName + "/"; + internal const string PackagePathRoot = "/App_Plugins/" + ProjectName + "/"; // TODO: [v9] [LK:2021-05-11] Add the ~/ back in later. internal const string PluginControllerName = ProjectName; From cf3dba5e9cd5bec75a88030d705a348d3b45d82d Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:36:28 +0100 Subject: [PATCH 43/81] Removed the `HtmlHelperExtensions` I'd started looking at porting them over, but they are outdated. Tag Helpers would be a better approach for this. --- .../Web/Extensions/HtmlHelperExtensions.cs | 91 ------------------- 1 file changed, 91 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs deleted file mode 100644 index 06a9ddbe..00000000 --- a/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs +++ /dev/null @@ -1,91 +0,0 @@ -/* Copyright © 2021 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -// TODO: [LK:2021-05-03] v9 Commenting out, as I'm (currently) unsure how to do the `HtmlHelper` bits. -// Feel that I need more understanding of .NET Core RazorPages. - -//using System; -//using System.Collections.Generic; -//using Microsoft.AspNetCore.Html; -//using Microsoft.AspNetCore.Mvc.Razor; -//using Microsoft.AspNetCore.Mvc.Rendering; -//using Microsoft.AspNetCore.Mvc.ViewFeatures; -//using Umbraco.Cms.Core.Models.PublishedContent; -//using Umbraco.Extensions; - -//namespace Umbraco.Web.Mvc -//{ -// public static class HtmlHelperExtensions -// { -// // Extension method derived from a StackOverflow answer. -// // https://stackoverflow.com/a/44870370/12787 -// // Licensed under the permissions of the CC BY-SA 3.0. -// // https://creativecommons.org/licenses/by-sa/3.0/ -// public static bool DoesPartialExist(this HtmlHelper helper, string partialViewName) -// { -// var controllerContext = helper.ViewContext.Controller.ControllerContext; -// var result = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName); - -// return result.View != null; -// } - -// public static IHtmlContent Partial(this HtmlHelper helper, string partialViewName, TModel model) -// { -// var viewData = new ViewDataDictionary(helper.ViewData) { Model = model }; -// return PartialExtensions.Partial(helper, partialViewName, model, viewData); -// } - -// public static HelperResult RenderElements( -// this HtmlHelper helper, -// IEnumerable elements, -// string viewPath = null, -// string fallbackPartialViewName = null, -// Func predicate = null, -// ViewDataDictionary viewData = null) -// where TPublishedElement : IPublishedElement -// { -// return new HelperResult(writer => -// { -// if (elements != null) -// { -// var elementIndex = 0; - -// if (viewData == null) -// { -// viewData = new ViewDataDictionary(); -// } - -// foreach (var element in elements) -// { -// viewData[nameof(elementIndex)] = elementIndex++; - -// if (predicate != null && predicate(element) == false) -// { -// continue; -// } - -// var partialViewName = viewPath?.EnsureEndsWith("/") + element.ContentType.Alias; - -// if (helper.DoesPartialExist(partialViewName) == false && string.IsNullOrWhiteSpace(fallbackPartialViewName) == false) -// { -// partialViewName = viewPath?.EnsureEndsWith("/") + fallbackPartialViewName; -// } - -// if (helper.DoesPartialExist(partialViewName) == true) -// { -//#pragma warning disable MVC1000 // Use of IHtmlHelper.{0} should be avoided. -// writer.WriteLine(helper.Partial(partialViewName, element, viewData)); -//#pragma warning restore MVC1000 // Use of IHtmlHelper.{0} should be avoided. -// } -// else -// { -// writer.WriteLine($""); -// } -// } -// } -// }); -// } -// } -//} From 6109d80736502a6e4822bf11963974ada9bb0229 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:38:38 +0100 Subject: [PATCH 44/81] ContentBlocks - Preview API controller works Figured out how to render a partial-view to a `string` in ASP MVC Core. I'm not completely happy with the code, but it's a start. --- .../ContentBlocks/ContentBlockPreviewView.cs | 3 +- .../ContentBlocksApiController.cs | 292 +++++++++++------- .../ContentBlocks/ContentBlocksViewHelper.cs | 55 ---- .../ContentBlocks/content-blocks.overlay.js | 5 +- 4 files changed, 184 insertions(+), 171 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index de93d15f..e90d9f85 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -7,10 +7,9 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Web.Common.Views; using Umbraco.Community.Contentment.DataEditors; -namespace Umbraco.Web.Mvc +namespace Umbraco.Cms.Web.Common.Views { public abstract class ContentBlockPreviewView : ContentBlockPreviewView diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs index eb4056f9..62dde4c2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -3,116 +3,182 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// TODO: [LK:2021-05-03] v9 Commenting out, as I'm (currently) unsure how to do the `RazorViewEngine` bits. -// Feel that I need more understanding of .NET Core RazorPages. - -//using System; -//using System.Collections.Generic; -//using System.Net; -//using System.Net.Http; -//using Microsoft.AspNetCore.Mvc; -//using Microsoft.Extensions.Logging; -//using Newtonsoft.Json.Linq; -//using Umbraco.Cms.Core.Models.PublishedContent; -//using Umbraco.Cms.Core.Services; -//using Umbraco.Cms.Web.BackOffice.Controllers; -//using Umbraco.Cms.Web.Common.Attributes; -//using Umbraco.Community.Contentment.Web.PublishedCache; - -//namespace Umbraco.Community.Contentment.DataEditors -//{ -// [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] -// public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController -// { -// private readonly ILogger _logger; -// private readonly IPublishedModelFactory _publishedModelFactory; -// private readonly IContentTypeService _contentTypeService; - -// public ContentBlocksApiController( -// ILogger logger, -// IPublishedModelFactory publishedModelFactory, -// IContentTypeService contentTypeService) -// { -// _logger = logger; -// _publishedModelFactory = publishedModelFactory; -// _contentTypeService = contentTypeService; -// } - -// [HttpPost] -// public HttpResponseMessage GetPreviewMarkup([FromBody] JObject item, int elementIndex, Guid elementKey, int contentId) -// { -// var preview = true; - -// var content = UmbracoContext.Content.GetById(true, contentId); -// if (content == null) -// { -// _logger.LogDebug($"Unable to retrieve content for ID '{contentId}', it is most likely a new unsaved page."); -// } - -// var element = default(IPublishedElement); -// var block = item.ToObject(); -// if (block != null && block.ElementType.Equals(Guid.Empty) == false) -// { -// if (ContentTypeCacheHelper.TryGetAlias(block.ElementType, out var alias, _contentTypeService) == true) -// { -// var contentType = UmbracoContext.PublishedSnapshot.Content.GetContentType(alias); -// if (contentType != null && contentType.IsElement == true) -// { -// var properties = new List(); - -// foreach (var thing in block.Value) -// { -// var propType = contentType.GetPropertyType(thing.Key); -// if (propType != null) -// { -// properties.Add(new DetachedPublishedProperty(propType, null, thing.Value, preview)); -// } -// } - -// element = _publishedModelFactory.CreateModel(new DetachedPublishedElement(block.Key, contentType, properties)); -// } -// } -// } - -// var viewData = new System.Web.Mvc.ViewDataDictionary(element) -// { -// { nameof(content), content }, -// { nameof(element), element }, -// { nameof(elementIndex), elementIndex }, -// }; - -// if (ContentTypeCacheHelper.TryGetIcon(content.ContentType.Alias, out var contentIcon, _contentTypeService) == true) -// { -// viewData.Add(nameof(contentIcon), contentIcon); -// } - -// if (ContentTypeCacheHelper.TryGetIcon(element.ContentType.Alias, out var elementIcon, _contentTypeService) == true) -// { -// viewData.Add(nameof(elementIcon), elementIcon); -// } - -// var markup = default(string); - -// try -// { -// markup = ContentBlocksViewHelper.RenderPartial(element.ContentType.Alias, viewData); -// } -// catch (InvalidCastException icex) -// { -// // NOTE: This type of exception happens on a new (unsaved) page, when the context becomes the parent page, -// // and the preview view is strongly typed to the current page's model type. -// markup = "

Unable to render the preview until the page has been saved.

"; - -// _logger.LogError(icex, "Error rendering preview view."); -// } -// catch (Exception ex) -// { -// markup = $"
{ex}
"; - -// _logger.LogError(ex, "Error rendering preview view."); -// } - -// return Request.CreateResponse(HttpStatusCode.OK, new { elementKey, markup }); -// } -// } -//} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Community.Contentment.Web.PublishedCache; + +namespace Umbraco.Community.Contentment.DataEditors +{ + [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] + public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController + { + private readonly ILogger _logger; + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly IContentTypeService _contentTypeService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly IRazorViewEngine _viewEngine; + private readonly ITempDataProvider _tempDataProvider; + private readonly IHtmlHelper _htmlHelper; + + public ContentBlocksApiController( + ILogger logger, + IPublishedModelFactory publishedModelFactory, + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IModelMetadataProvider modelMetadataProvider, + IRazorViewEngine viewEngine, + ITempDataProvider tempDataProvider) + { + _logger = logger; + _publishedModelFactory = publishedModelFactory; + _contentTypeService = contentTypeService; + _umbracoContextAccessor = umbracoContextAccessor; + _modelMetadataProvider = modelMetadataProvider; + _viewEngine = viewEngine; + _tempDataProvider = tempDataProvider; + } + + [HttpPost] + public ActionResult GetPreviewMarkup([FromBody] JObject item, int elementIndex, Guid elementKey, int contentId) + { + var preview = true; + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + + var content = umbracoContext.Content.GetById(true, contentId); + if (content == null) + { + _logger.LogDebug($"Unable to retrieve content for ID '{contentId}', it is most likely a new unsaved page."); + } + + var element = default(IPublishedElement); + var block = item.ToObject(); + if (block != null && block.ElementType.Equals(Guid.Empty) == false) + { + if (ContentTypeCacheHelper.TryGetAlias(block.ElementType, out var alias, _contentTypeService) == true) + { + var contentType = umbracoContext.PublishedSnapshot.Content.GetContentType(alias); + if (contentType != null && contentType.IsElement == true) + { + var properties = new List(); + + foreach (var thing in block.Value) + { + var propType = contentType.GetPropertyType(thing.Key); + if (propType != null) + { + properties.Add(new DetachedPublishedProperty(propType, null, thing.Value, preview)); + } + } + + element = _publishedModelFactory.CreateModel(new DetachedPublishedElement(block.Key, contentType, properties)); + } + } + } + + var viewData = new ViewDataDictionary(_modelMetadataProvider, new ModelStateDictionary()) + { + Model = element, + [nameof(content)] = content, + [nameof(element)] = element, + [nameof(elementIndex)] = elementIndex, + + }; + + if (ContentTypeCacheHelper.TryGetIcon(content.ContentType.Alias, out var contentIcon, _contentTypeService) == true) + { + viewData.Add(nameof(contentIcon), contentIcon); + } + + if (ContentTypeCacheHelper.TryGetIcon(element.ContentType.Alias, out var elementIcon, _contentTypeService) == true) + { + viewData.Add(nameof(elementIcon), elementIcon); + } + + var markup = default(string); + + try + { + markup = RenderPartialViewToString(element.ContentType.Alias, viewData); + } + catch (InvalidCastException icex) + { + // NOTE: This type of exception happens on a new (unsaved) page, when the context becomes the parent page, + // and the preview view is strongly typed to the current page's model type. + markup = "

Unable to render the preview until the page has been saved.

"; + + _logger.LogError(icex, "Error rendering preview view."); + } + catch (Exception ex) + { + markup = $"
{ex}
"; + + _logger.LogError(ex, "Error rendering preview view."); + } + + return new ObjectResult(new { elementKey, markup }); + } + + // HACK: [v9] [LK:2021-05-13] Got it working. Future rewrite, make nicer. + // The following code has been hacked and butchered from: + // https://github.com/aspnet/Entropy/blob/master/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs + // https://gist.github.com/ahmad-moussawi/1643d703c11699a6a4046e57247b4d09 + private string RenderPartialViewToString(string viewName, ViewDataDictionary viewData) + { + IView view = default; + + // TODO: [v9] [LK:2021-05-13] Implement the custom partial-view paths. + // e.g. "~/Views/Partials/Blocks/{0}.cshtml", "~/Views/Partials/Blocks/Default.cshtml", "~/App_Plugins/Contentment/render/ContentBlockPreview.cshtml" + + var getViewResult = _viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true); + if (getViewResult.Success) + { + view = getViewResult.View; + } + + var actionContext = new ActionContext(HttpContext, new RouteData(), new ActionDescriptor()); + var findViewResult = _viewEngine.FindView(actionContext, viewName, isMainPage: true); + if (findViewResult.Success) + { + view = findViewResult.View; + } + + if (view == default) + { + var messages = new List { $"Unable to find view '{viewName}'. The following locations were searched:" }; + messages.AddRange(getViewResult.SearchedLocations); + messages.AddRange(findViewResult.SearchedLocations); + + var errorMessage = string.Join(Environment.NewLine, messages); + + throw new InvalidOperationException(errorMessage); + } + + using var output = new StringWriter(); + + var tempDataDictionary = new TempDataDictionary(actionContext.HttpContext, _tempDataProvider); + var viewContext = new ViewContext(actionContext, view, viewData, tempDataDictionary, output, new HtmlHelperOptions()); + + view.RenderAsync(viewContext).GetAwaiter().GetResult(); + + return output.ToString(); + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs deleted file mode 100644 index b0420091..00000000 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright © 2020 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -// TODO: [LK:2021-05-03] v9 Commenting out, as I'm (currently) unsure how to do the `RazorViewEngine` bits. -// Feel that I need more understanding of .NET Core RazorPages. - -//using System.IO; -//using System.Web; -//using System.Web.Mvc; -//using System.Web.Routing; -//using Microsoft.AspNetCore.Mvc; -//using Microsoft.AspNetCore.Mvc.Razor; - -//namespace Umbraco.Community.Contentment.DataEditors -//{ -// internal static class ContentBlocksViewHelper -// { -// private class FakeController : Controller { } - -// private static readonly RazorViewEngine _viewEngine = new RazorViewEngine -// { -// PartialViewLocationFormats = new[] -// { -// "~/Views/Partials/Blocks/{0}.cshtml", -// "~/Views/Partials/Blocks/Default.cshtml", -// Constants.Internals.PackagePathRoot + "render/ContentBlockPreview.cshtml" -// } -// }; - -// internal static string RenderPartial(string partialName, ViewDataDictionary viewData) -// { -// using (var sw = new StringWriter()) -// { -// var httpContext = new HttpContextWrapper(HttpContext.Current); - -// var routeData = new RouteData { Values = { { "controller", nameof(FakeController) } } }; - -// var controllerContext = new ControllerContext(new RequestContext(httpContext, routeData), new FakeController()); - -// var viewResult = _viewEngine.FindPartialView(controllerContext, partialName, false); - -// if (viewResult.View == null) -// { -// return null; -// } - -// viewResult.View.Render(new ViewContext(controllerContext, viewResult.View, viewData, new TempDataDictionary(), sw), sw); - -// return sw.ToString(); -// } -// } -// } -//} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js index d36583e5..14f75647 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js @@ -111,7 +111,10 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con icon: elementType.icon, key: String.CreateGuid() }; - + + // TODO: [v9] [LK] Review this, get error with blueprint API request, 404. + // "Failed to retrieve blueprint for id 1082" + // e.g. /umbraco/backoffice/umbracoapi/content/GetEmpty?blueprintId=1082&parentId=1076 var getScaffold = blueprint && blueprint.id > 0 ? contentResource.getBlueprintScaffold(config.currentPageId, blueprint.id) : contentResource.getScaffold(config.currentPageId, elementType.alias); From fbbf095b94ccf459c35e39a60fd9978d6d6075b3 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:39:14 +0100 Subject: [PATCH 45/81] Updated TODO comments with v9 prefixes --- .../DataEditors/DataList/DataSources/SqlDataListSource.cs | 2 +- .../DataEditors/DataList/DataSources/XmlDataListSource.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index ac583eec..982e12fa 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -133,7 +133,7 @@ public IEnumerable GetItems(Dictionary config) return items; } - // TODO: [LK:2021-05-07] Review SQLCE + // TODO: [v9] [LK:2021-05-07] Review SQLCE // NOTE: SQLCE uses a different connection/command. I'm trying to keep this as generic as possible, without resorting to using NPoco. [LK] // I've tried digging around Umbraco's `IUmbracoDatabase` layer, but I couldn't get my head around it. // At the end of the day, if the user has SQLCE configured, it'd be nice for them to query it. diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index adca8068..ad275856 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -123,7 +123,7 @@ public IEnumerable GetItems(Dictionary config) try { - // TODO: [LK:2021-05-07] Doesn't work for HTTPS URLs, throws a `HttpRequestException`. + // TODO: [v9] [LK:2021-05-07] Doesn't work for HTTPS URLs, throws a `HttpRequestException`. doc = new XPathDocument(path); } catch(HttpRequestException ex) From 05480ea2becf342678f5f5b0bdf379bf07f4e4d6 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 14:40:05 +0100 Subject: [PATCH 46/81] Members data-source - got `ConvertValue` working I'm not completely happy with the code, but it's a start. --- .../DataList/DataSources/UmbracoMembersDataListSource.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index 3ece009f..27473026 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -135,11 +135,10 @@ DataListItem mapMember(IMember member) public object ConvertValue(Type type, string value) { - // TODO: [LK:2021-04-30] v9 Review this, as why would it only have `Get(IMember)` odd. - //return UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false - // ? _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(udi.Guid) - // : default; - return default; + // TODO: [v9] [LK:2021-04-30] Review this, as why would it only have `Get(IMember)`? + return UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false + ? _publishedSnapshotAccessor.PublishedSnapshot.Members.Get(_memberService.GetByKey(udi.Guid)) + : default; } } } From 89b22478217b0e0957dc6b09112cde296b4c7f37 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 13 May 2021 17:21:09 +0100 Subject: [PATCH 47/81] PublishedContentContractResolver - amends Found a nice way to include the properties that had been moved to extension methods, e.g. `.CreatorName()`, `.WriterName()` and `.Url()`. Slight duplication on the prefixing system properties with an underscore --- .../PublishedContentContractResolver.cs | 72 +++++++++++++++---- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs index 146cd0ff..772fd8a6 100644 --- a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs +++ b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs @@ -18,6 +18,7 @@ using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Web.Serialization { @@ -31,6 +32,7 @@ public sealed class PublishedContentContractResolver : DefaultContractResolver private readonly HashSet _ignoreFromContent; private readonly HashSet _ignoreFromProperty; private readonly HashSet _systemProperties; + private readonly Dictionary> _systemMethods; public PublishedContentContractResolver() : base() @@ -73,10 +75,7 @@ public PublishedContentContractResolver() _systemProperties = new HashSet(StringComparer.OrdinalIgnoreCase) { nameof(IPublishedContent.CreateDate), -// TODO: [LK:2021-04-30] v9 Review this. -//#pragma warning disable CS0618 // Type or member is obsolete -// nameof(IPublishedContent.CreatorName), -//#pragma warning restore CS0618 // Type or member is obsolete + nameof(FriendlyPublishedContentExtensions.CreatorName), nameof(IPublishedContent.Id), nameof(IPublishedContent.ItemType), nameof(IPublishedElement.Key), @@ -85,15 +84,16 @@ public PublishedContentContractResolver() nameof(IPublishedContent.Path), nameof(IPublishedContent.SortOrder), nameof(IPublishedContent.UpdateDate), -// TODO: [LK:2021-04-30] v9 Review this. -//#pragma warning disable CS0618 // Type or member is obsolete -// nameof(IPublishedContent.Url), -//#pragma warning restore CS0618 // Type or member is obsolete + nameof(FriendlyPublishedContentExtensions.Url), nameof(IPublishedContent.UrlSegment), -// TODO: [LK:2021-04-30] v9 Review this. -//#pragma warning disable CS0618 // Type or member is obsolete -// nameof(IPublishedContent.WriterName), -//#pragma warning restore CS0618 // Type or member is obsolete + nameof(FriendlyPublishedContentExtensions.WriterName), + }; + + _systemMethods = new Dictionary> + { + { nameof(FriendlyPublishedContentExtensions.CreatorName), x => x.CreatorName() }, + { nameof(FriendlyPublishedContentExtensions.Url), x => x.Url() }, + { nameof(FriendlyPublishedContentExtensions.WriterName), x => x.WriterName() }, }; } @@ -120,7 +120,28 @@ public Dictionary PropertyTypeConverters protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { - return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList(); + var properties = base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList(); + + if (typeof(IPublishedContent).IsAssignableFrom(type) == true) + { + var noAttributeProvider = new NoAttributeProvider(); + + properties.AddRange(_systemMethods.Select(x => new JsonProperty + { + DeclaringType = type, + PropertyName = ResolvePropertyName(x.Key), + UnderlyingName = x.Key, + PropertyType = typeof(string), + ValueProvider = new PublishedContentValueProvider(x.Value), + AttributeProvider = noAttributeProvider, + Readable = true, + Writable = false, + ItemIsReference = false, + TypeNameHandling = TypeNameHandling.None, + })); + } + + return properties; } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) @@ -156,5 +177,30 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ return property; } + + protected override string ResolvePropertyName(string propertyName) + { + return PrefixSystemPropertyNamesWithUnderscore == true && _systemProperties.Contains(propertyName) == true + ? "_" + base.ResolvePropertyName(propertyName) + : base.ResolvePropertyName(propertyName); + } + + private class PublishedContentValueProvider : IValueProvider + { + private readonly Func _func; + + public PublishedContentValueProvider(Func func) => _func = func; + + public object GetValue(object target) => _func((IPublishedContent)target); + + public void SetValue(object target, object value) => throw new NotImplementedException(); + } + + private class NoAttributeProvider : IAttributeProvider + { + public IList GetAttributes(bool inherit) => Array.Empty(); + + public IList GetAttributes(Type attributeType, bool inherit) => Array.Empty(); + } } } From 362dc311eb5420aed6c4a3a457fdefea65daac5d Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 14 May 2021 11:00:24 +0100 Subject: [PATCH 48/81] :notebook: :roller_coaster: Updated README+ROADMAP Information about Umbraco v9 migration, .NET Core. --- .github/README.md | 6 ++++++ .github/ROADMAP.md | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/.github/README.md b/.github/README.md index da0fdc57..cc209ef9 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,3 +1,9 @@ +:warning: **`v3.0.0-develop` (aka migration to Umbraco v9, .NET Core) is under active development!** :warning: + +[See my developer's journal for the latest updates.](https://github.com/leekelleher/umbraco-contentment/discussions/105) + +--- + Contentment for Umbraco logo ## Contentment for Umbraco diff --git a/.github/ROADMAP.md b/.github/ROADMAP.md index bf725e58..cee1c69d 100644 --- a/.github/ROADMAP.md +++ b/.github/ROADMAP.md @@ -56,6 +56,13 @@ Property Editors are: - Cascading Dropdown List _(uses API endpoints to populate the dropdowns)_ +## v3 + +### v3.0 + +- _Under active development!_ [See my developer's journal for the latest updates.](https://github.com/leekelleher/umbraco-contentment/discussions/105) A breaking-change release _(following SemVer guidelines),_ of v2.x features that will compile against Umbraco CMS v9.0.0. + + ## Future feature releases _Who knows?!_ `¯\_(ツ)_/¯` From 75e7227775b96cffe991aa4e0e0aa2a32927e8a0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 28 May 2021 16:26:56 +0100 Subject: [PATCH 49/81] Upgraded Umbraco.Cms dependency to v9.0.0-beta003 --- .../DataEditors/Bytes/BytesDataEditor.cs | 20 ++----------------- .../CodeEditor/CodeEditorDataEditor.cs | 8 ++++---- .../ContentBlocksApiController.cs | 1 - .../ContentBlocks/ContentBlocksDataEditor.cs | 5 ----- .../ContentBlocksDataValueEditor.cs | 3 +-- .../DataList/DataListDataEditor.cs | 4 ---- .../DataSources/ExamineDataListSource.cs | 2 +- .../IconPicker/IconPickerDataEditor.cs | 13 ++---------- .../DataEditors/Notes/NotesDataEditor.cs | 10 ---------- .../NumberInput/NumberInputDataEditor.cs | 14 ++----------- .../ReadOnly/ReadOnlyDataValueEditor.cs | 4 ---- .../RenderMacro/RenderMacroDataEditor.cs | 10 ---------- .../TextInput/TextInputDataEditor.cs | 12 ++--------- .../Umbraco.Community.Contentment.csproj | 4 ++-- 14 files changed, 16 insertions(+), 94 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs index 2cf53322..ed7197c1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs @@ -3,12 +3,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { @@ -31,21 +27,9 @@ public sealed class BytesDataEditor : DataEditor public BytesDataEditor( IIOHelper ioHelper, - ILoggerFactory loggerFactory, - IDataTypeService dataTypeService, - ILocalizationService localizationService, - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, + IDataValueEditorFactory dataValueEditorFactory, EditorType type = EditorType.PropertyValue) - : base( - loggerFactory, - dataTypeService, - localizationService, - localizedTextService, - shortStringHelper, - jsonSerializer, - type) + : base(dataValueEditorFactory, type) { _ioHelper = ioHelper; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs index 52eace6a..c6c9d064 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs @@ -38,6 +38,7 @@ internal sealed class CodeEditorDataEditor : DataEditor private readonly IJsonSerializer _jsonSerializer; public CodeEditorDataEditor( + IDataValueEditorFactory dataValueEditorFactory, IHostingEnvironment hostingEnvironment, IIOHelper ioHelper, ILoggerFactory loggerFactory, @@ -47,7 +48,7 @@ public CodeEditorDataEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, EditorType type = EditorType.PropertyValue) - : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer, type) + : base(dataValueEditorFactory, type) { _hostingEnvironment = hostingEnvironment; _ioHelper = ioHelper; @@ -63,11 +64,10 @@ public CodeEditorDataEditor( _ioHelper); protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor( - _dataTypeService, - _localizationService, Attribute, _localizedTextService, _shortStringHelper, - _jsonSerializer); + _jsonSerializer, + _ioHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs index 62dde4c2..ee6318a4 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -36,7 +36,6 @@ public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController private readonly IModelMetadataProvider _modelMetadataProvider; private readonly IRazorViewEngine _viewEngine; private readonly ITempDataProvider _tempDataProvider; - private readonly IHtmlHelper _htmlHelper; public ContentBlocksApiController( ILogger logger, diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index 4c51bb40..f9800596 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -27,7 +27,6 @@ public sealed class ContentBlocksDataEditor : IDataEditor private readonly IContentService _contentService; private readonly IContentTypeService _contentTypeService; private readonly IDataTypeService _dataTypeService; - private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _localizedTextService; private readonly IShortStringHelper _shortStringHelper; private readonly IJsonSerializer _jsonSerializer; @@ -40,7 +39,6 @@ public ContentBlocksDataEditor( IContentTypeService contentTypeService, Lazy propertyEditors, IDataTypeService dataTypeService, - ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, @@ -50,7 +48,6 @@ public ContentBlocksDataEditor( _contentService = contentService; _contentTypeService = contentTypeService; _dataTypeService = dataTypeService; - _localizationService = localizationService; _localizedTextService = localizedTextService; _shortStringHelper = shortStringHelper; _jsonSerializer = jsonSerializer; @@ -88,7 +85,6 @@ public IDataValueEditor GetValueEditor() _contentTypeService, _propertyEditors.Value, _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) @@ -128,7 +124,6 @@ public IDataValueEditor GetValueEditor(object configuration) _contentTypeService, _propertyEditors.Value, _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs index 804e64f4..10c1bb63 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs @@ -35,11 +35,10 @@ public ContentBlocksDataValueEditor( IContentTypeService contentTypeService, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, - ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer) - : base(dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer) + : base(localizedTextService, shortStringHelper, jsonSerializer) { _dataTypeService = dataTypeService; _elementTypes = new Lazy>(() => contentTypeService.GetAllElementTypes().ToDictionary(x => x.Key)); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs index 7b4479e3..cdcfa32d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs @@ -71,8 +71,6 @@ public DataListDataEditor( public IDataValueEditor GetValueEditor() { return new DataValueEditor( - _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) @@ -106,8 +104,6 @@ public IDataValueEditor GetValueEditor(object configuration) } return new DataValueEditor( - _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs index b7f9c2aa..b037bb89 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs @@ -170,7 +170,7 @@ public IEnumerable GetItems(Dictionary config) var descriptionField = config.GetValueAs("descriptionField", string.Empty); var results = index - .GetSearcher() + .Searcher .CreateQuery() .NativeQuery(luceneQuery) // NOTE: For any `OrderBy` complaints, refer to: https://github.com/Shazwazza/Examine/issues/126 diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs index ae56a99f..05013999 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs @@ -3,12 +3,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { @@ -30,14 +26,9 @@ public sealed class IconPickerDataEditor : DataEditor public IconPickerDataEditor( IIOHelper ioHelper, - ILoggerFactory loggerFactory, - IDataTypeService dataTypeService, - ILocalizationService localizationService, - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, + IDataValueEditorFactory dataValueEditorFactory, EditorType type = EditorType.PropertyValue) - : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer, type) + : base(dataValueEditorFactory, type) { _ioHelper = ioHelper; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs index d37e7dd6..0d39001d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs @@ -21,23 +21,17 @@ public sealed class NotesDataEditor : IDataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "notes.html"; internal const string DataEditorIcon = "icon-fa fa-sticky-note-o"; - private readonly IDataTypeService _dataTypeService; - private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _localizedTextService; private readonly IShortStringHelper _shortStringHelper; private readonly IJsonSerializer _jsonSerializer; private readonly IIOHelper _ioHelper; public NotesDataEditor( - IDataTypeService dataTypeService, - ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) { - _dataTypeService = dataTypeService; - _localizationService = localizationService; _localizedTextService = localizedTextService; _shortStringHelper = shortStringHelper; _jsonSerializer = jsonSerializer; @@ -65,8 +59,6 @@ public NotesDataEditor( public IDataValueEditor GetValueEditor() { return new ReadOnlyDataValueEditor( - _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) @@ -86,8 +78,6 @@ public IDataValueEditor GetValueEditor(object configuration) } return new ReadOnlyDataValueEditor( - _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs index 4fa9833c..e82ec1d8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs @@ -29,21 +29,11 @@ internal sealed class NumberInputDataEditor : DataEditor private readonly IIOHelper _ioHelper; public NumberInputDataEditor( - ILoggerFactory loggerFactory, - IDataTypeService dataTypeService, - ILocalizationService localizationService, - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, IIOHelper ioHelper, + IDataValueEditorFactory dataValueEditorFactory, EditorType type = EditorType.PropertyValue) : base( - loggerFactory, - dataTypeService, - localizationService, - localizedTextService, - shortStringHelper, - jsonSerializer, + dataValueEditorFactory, type) { _ioHelper = ioHelper; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs index 2190d846..be47e59e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs @@ -13,14 +13,10 @@ namespace Umbraco.Community.Contentment.DataEditors internal sealed class ReadOnlyDataValueEditor : DataValueEditor { public ReadOnlyDataValueEditor( - IDataTypeService dataTypeService, - ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer) : base( - dataTypeService, - localizationService, localizedTextService, shortStringHelper, jsonSerializer) diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs index 54381af6..f9a57fc3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs @@ -21,23 +21,17 @@ public sealed class RenderMacroDataEditor : IDataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "render-macro.html"; internal const string DataEditorIcon = Cms.Core.Constants.Icons.Macro; - private readonly IDataTypeService _dataTypeService; - private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _localizedTextService; private readonly IShortStringHelper _shortStringHelper; private readonly IJsonSerializer _jsonSerializer; private readonly IIOHelper _ioHelper; public RenderMacroDataEditor( - IDataTypeService dataTypeService, - ILocalizationService localizationService, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) { - _dataTypeService = dataTypeService; - _localizationService = localizationService; _localizedTextService = localizedTextService; _shortStringHelper = shortStringHelper; _jsonSerializer = jsonSerializer; @@ -65,8 +59,6 @@ public RenderMacroDataEditor( public IDataValueEditor GetValueEditor() { return new ReadOnlyDataValueEditor( - _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) @@ -86,8 +78,6 @@ public IDataValueEditor GetValueEditor(object configuration) } return new ReadOnlyDataValueEditor( - _dataTypeService, - _localizationService, _localizedTextService, _shortStringHelper, _jsonSerializer) diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs index 7cdca13a..37f5fa49 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs @@ -3,12 +3,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors @@ -35,14 +31,10 @@ public sealed class TextInputDataEditor : DataEditor public TextInputDataEditor( ConfigurationEditorUtility utility, IIOHelper ioHelper, - ILoggerFactory loggerFactory, - IDataTypeService dataTypeService, - ILocalizationService localizationService, - ILocalizedTextService localizedTextService, + IDataValueEditorFactory dataValueEditorFactory, IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, EditorType type = EditorType.PropertyValue) - : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer, type) + : base(dataValueEditorFactory, type) { _utility = utility; _ioHelper = ioHelper; diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 531f707e..68d4a84c 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -16,8 +16,8 @@ git - - + + From 8ce57c01ece73a6b84c6f90b3474c2739558af44 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 8 Jun 2021 16:37:48 +0100 Subject: [PATCH 50/81] Rebased on latest Contentment v2.0 branch Fixed up broken code. --- .../Composing/UmbracoBuilderExtensions.cs | 2 +- .../DisplayModes/BlocksDisplayMode.cs | 14 +++++++-- .../DataSources/EnumDataListSource.cs | 1 + .../DataSources/ExamineDataListSource.cs | 31 +++++++++---------- .../UmbracoContentTypesDataListSource.cs | 29 ++++++++++------- .../DataSources/UserDefinedDataListSource.cs | 1 - .../PublishedContentTypeExtensions.cs | 6 ++-- 7 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs index 6ffa4b2f..e90317e1 100644 --- a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs @@ -61,13 +61,13 @@ public static IUmbracoBuilder UnlockContentment(this IUmbracoBuilder builder) .Add() .Add() .Add() + .Add() .Add() .Add() .Add() .Add() .Add() .Add() - .Add() ; return builder; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs index 00f680ed..f9cfeb00 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs @@ -4,13 +4,21 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; -using UmbIcons = Umbraco.Core.Constants.Icons; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using UmbIcons = Umbraco.Cms.Core.Constants.Icons; namespace Umbraco.Community.Contentment.DataEditors { internal class BlocksDisplayMode : IContentBlocksDisplayMode { + private readonly IIOHelper _ioHelper; + + public BlocksDisplayMode(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Blocks"; public string Description => "Blocks will be displayed in a list similar to the Block List editor."; @@ -36,7 +44,7 @@ internal class BlocksDisplayMode : IContentBlocksDisplayMode public IEnumerable Fields => new[] { - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
A note about block type previews.

Currently, the preview feature for block types has not been implemented for the {Name} display mode and has been temporarily disabled.

", true), diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs index db12cb79..b1d5da56 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs @@ -8,6 +8,7 @@ using System.ComponentModel; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs index b037bb89..50570b34 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs @@ -6,15 +6,14 @@ using System.Collections.Generic; using System.Linq; using Examine; -using Examine.LuceneEngine.Providers; using Examine.Search; using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Extensions; using UmbConstants = Umbraco.Cms.Core.Constants; -using Umbraco.Examine; namespace Umbraco.Community.Contentment.DataEditors { @@ -25,8 +24,8 @@ public sealed class ExamineDataListSource : IDataListSource private readonly IShortStringHelper _shortStringHelper; private readonly IIOHelper _ioHelper; private const string _defaultNameField = "nodeName"; - private const string _defaultValueField = UmbracoExamineIndex.NodeKeyFieldName; - private const string _defaultIconField = UmbracoExamineIndex.IconFieldName; + private const string _defaultValueField = UmbracoExamineFieldNames.NodeKeyFieldName; + private const string _defaultIconField = UmbracoExamineFieldNames.IconFieldName; private readonly Dictionary _examineFieldConfig = new Dictionary { @@ -34,14 +33,14 @@ public sealed class ExamineDataListSource : IDataListSource Constants.Conventions.ConfigurationFieldAliases.Items, new[] { - LuceneIndex.CategoryFieldName, - LuceneIndex.ItemIdFieldName, - LuceneIndex.ItemTypeFieldName, - UmbracoExamineIndex.IconFieldName, - UmbracoExamineIndex.IndexPathFieldName, - UmbracoExamineIndex.NodeKeyFieldName, - UmbracoExamineIndex.PublishedFieldName, - UmbracoExamineIndex.UmbracoFileFieldName, + ExamineFieldNames.CategoryFieldName, + ExamineFieldNames.ItemIdFieldName, + ExamineFieldNames.ItemTypeFieldName, + UmbracoExamineFieldNames.IconFieldName, + UmbracoExamineFieldNames.IndexPathFieldName, + UmbracoExamineFieldNames.NodeKeyFieldName, + UmbracoExamineFieldNames.PublishedFieldName, + UmbracoExamineFieldNames.UmbracoFileFieldName, "createDate", "creatorID", "creatorName", @@ -117,7 +116,7 @@ public ExamineDataListSource( Key = "nameField", Name = "Name Field", Description = "Enter the field name to select the name from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, new ConfigurationField @@ -125,7 +124,7 @@ public ExamineDataListSource( Key = "valueField", Name = "Value Field", Description = "Enter the field name to select the value (key) from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, new ConfigurationField @@ -133,7 +132,7 @@ public ExamineDataListSource( Key = "iconField", Name = "Icon Field", Description = "(optional) Enter the field name to select the icon from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, new ConfigurationField @@ -141,7 +140,7 @@ public ExamineDataListSource( Key = "descriptionField", Name = "Description Field", Description = "(optional) Enter the field name to select the description from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs index 49b8f2ae..95310d00 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs @@ -7,27 +7,32 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Core.Xml; -using Umbraco.Web; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] + [Cms.Core.Composing.HideFromTypeFinder] public sealed class UmbracoContentTypesDataListSource : IDataListSource, IDataListSourceValueConverter { private readonly IContentTypeService _contentTypeService; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoContentTypesDataListSource(IContentTypeService contentTypeService, IUmbracoContextAccessor umbracoContextAccessor) + public UmbracoContentTypesDataListSource( + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; } public string Name => "Umbraco Content Types"; @@ -46,7 +51,7 @@ public UmbracoContentTypesDataListSource(IContentTypeService contentTypeService, { Key = "contentTypes", Name = "Content types", - View = IOHelper.ResolveUrl(CheckboxListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(CheckboxListDataListEditor.DataEditorViewPath), Description = "Select the types to use.", Config = new Dictionary { @@ -129,7 +134,7 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - if (GuidUdi.TryParse(value, out var udi) == true && ContentTypeCacheHelper.TryGetAlias(udi.Guid, out var alias, _contentTypeService) == true) + if (UdiParser.TryParse(value, out GuidUdi udi) == true && ContentTypeCacheHelper.TryGetAlias(udi.Guid, out var alias, _contentTypeService) == true) { return _umbracoContextAccessor.UmbracoContext.Content.GetContentType(alias); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs index 3984a2ee..eef77391 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs @@ -13,7 +13,6 @@ namespace Umbraco.Community.Contentment.DataEditors { - [Cms.Core.Composing.HideFromTypeFinder] public sealed class UserDefinedDataListSource : IDataListSource { private readonly IIOHelper _ioHelper; diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs index 8ea74232..f625483d 100644 --- a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs +++ b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs @@ -4,9 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Web { From 3050523429c03c039764867d420bb6a30fe478ac Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 22 Jun 2021 17:15:27 +0100 Subject: [PATCH 51/81] Upgraded Umbraco.Cms dependency to v9.0.0-beta004 --- .../Umbraco.Community.Contentment.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 68d4a84c..165444f7 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -16,8 +16,8 @@ git - - + + From 8c23c87383ce8c403ce8b17653d5fd7203b3fc48 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 22 Jun 2021 17:16:05 +0100 Subject: [PATCH 52/81] Replaced registering CSS/JS assets in package.manifest with C# registration. --- .../Composing/UmbracoBuilderExtensions.cs | 13 +++++++++++-- src/Umbraco.Community.Contentment/Constants.cs | 7 +++++++ .../Web/Assets/ContentmentCssFile.cs | 14 ++++++++++++++ .../Web/Assets/ContentmentJsFile.cs | 14 ++++++++++++++ .../UI/App_Plugins/Contentment/package.manifest | 4 ---- 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs create mode 100644 src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs delete mode 100644 src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest diff --git a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs index e90317e1..61114e20 100644 --- a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.WebAssets; using Umbraco.Community.Contentment; using Umbraco.Community.Contentment.Composing; using Umbraco.Community.Contentment.DataEditors; @@ -23,6 +24,12 @@ public static IUmbracoBuilder AddContentment(this IUmbracoBuilder builder, Actio ? builder.Services.Configure(configure) : builder.Services.Configure(builder.Config.GetSection(Constants.Internals.ConfigurationSection)); + builder + .BackOfficeAssets() + .Append() + .Append() + ; + builder .WithCollectionBuilder() .Add(() => builder.TypeLoader.GetTypes()) @@ -32,8 +39,10 @@ public static IUmbracoBuilder AddContentment(this IUmbracoBuilder builder, Actio builder.Components().Append(); - builder.AddNotificationHandler(); - builder.AddNotificationHandler(); + builder + .AddNotificationHandler() + .AddNotificationHandler() + ; return builder; } diff --git a/src/Umbraco.Community.Contentment/Constants.cs b/src/Umbraco.Community.Contentment/Constants.cs index 434a538e..9b80d908 100644 --- a/src/Umbraco.Community.Contentment/Constants.cs +++ b/src/Umbraco.Community.Contentment/Constants.cs @@ -111,5 +111,12 @@ internal static partial class Values public const string False = "0"; } + + internal static partial class WebAssets + { + internal const string CssPath = Internals.PackagePathRoot + Internals.ProjectAlias + ".css"; + + internal const string JsPath = Internals.PackagePathRoot + Internals.ProjectAlias + ".js"; + } } } diff --git a/src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs b/src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs new file mode 100644 index 00000000..23f801ae --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs @@ -0,0 +1,14 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +namespace Umbraco.Cms.Core.WebAssets +{ + internal class ContentmentCssFile : CssFile + { + public ContentmentCssFile() + : base(Community.Contentment.Constants.WebAssets.CssPath) + { } + } +} diff --git a/src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs b/src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs new file mode 100644 index 00000000..0f06cc3c --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs @@ -0,0 +1,14 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +namespace Umbraco.Cms.Core.WebAssets +{ + internal class ContentmentJsFile : JavaScriptFile + { + public ContentmentJsFile() + : base(Community.Contentment.Constants.WebAssets.JsPath) + { } + } +} diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest deleted file mode 100644 index 591007fd..00000000 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest +++ /dev/null @@ -1,4 +0,0 @@ -{ - "css": [ "~/App_Plugins/Contentment/contentment.css" ], - "javascript": [ "~/App_Plugins/Contentment/contentment.js" ] -} From 3da966b3643bcc8921201d564130e8af7e6048f6 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:28:05 +0100 Subject: [PATCH 53/81] Reworked CSS/JS assets Previously, I'd been bundling/minifying the CSS and JS so that they would be production-ready AND that the number of HTTP requests in the CMS backoffice would be reduced during development. Given that Smidge will bundle any pkg assets (when debug=false), the overhead of multiple HTTP requests doesn't seem too detrimental for v9. An advantage of this is that now the individual property-editor JS files are available, it may encourage collaboration. I have a feeling that bundling/minifying put folk off from digging into the codebase. This change also means that I'm removing the `WebAssets` code and deleting the AjaxMinifier.exe tool (goodbye old chum). --- build/build-assets.ps1 | 20 +++-- .../Composing/UmbracoBuilderExtensions.cs | 6 -- .../Constants.cs | 7 -- .../DataEditors/_/_directives.js | 49 ------------ .../Web/Assets/ContentmentCssFile.cs | 14 ---- .../Web/Assets/ContentmentJsFile.cs | 14 ---- .../backoffice}/backoffice-ui-shim.css | 0 .../backoffice/contentment/index.html | 20 ++--- .../backoffice/contentment/index.js} | 28 +------ .../Contentment/components}/_components.js | 0 .../Contentment/components/_directives.js} | 73 +++++++++--------- .../Contentment/components}/_filters.js | 0 .../Contentment/components/_services.js | 52 +++++++++++++ .../App_Plugins/Contentment/contentment.css | 7 -- .../UI/App_Plugins/Contentment/contentment.js | 7 -- .../Contentment/editors}/_json-editor.html | 0 .../App_Plugins/Contentment/package.manifest | 50 ++++++++++++ tools/AjaxMinifier.exe | Bin 617472 -> 0 bytes 18 files changed, 163 insertions(+), 184 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/DataEditors/_/_directives.js delete mode 100644 src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs delete mode 100644 src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs rename src/Umbraco.Community.Contentment/Web/UI/{ => App_Plugins/Contentment/backoffice}/backoffice-ui-shim.css (100%) rename src/Umbraco.Community.Contentment/Web/UI/{backoffice.js => App_Plugins/Contentment/backoffice/contentment/index.js} (85%) rename src/Umbraco.Community.Contentment/{DataEditors/_ => Web/UI/App_Plugins/Contentment/components}/_components.js (100%) rename src/Umbraco.Community.Contentment/{DataEditors/_/_dev-mode.js => Web/UI/App_Plugins/Contentment/components/_directives.js} (72%) rename src/Umbraco.Community.Contentment/{DataEditors/_ => Web/UI/App_Plugins/Contentment/components}/_filters.js (100%) create mode 100644 src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_services.js delete mode 100644 src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.css delete mode 100644 src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.js rename src/Umbraco.Community.Contentment/{DataEditors/_ => Web/UI/App_Plugins/Contentment/editors}/_json-editor.html (100%) create mode 100644 src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest delete mode 100644 tools/AjaxMinifier.exe diff --git a/build/build-assets.ps1 b/build/build-assets.ps1 index 24ae16a3..062c44d5 100644 --- a/build/build-assets.ps1 +++ b/build/build-assets.ps1 @@ -57,15 +57,19 @@ foreach($razorFile in $razorFiles){ [IO.File]::WriteAllLines("${pluginFolder}\render\$($razorFile.Name)", $contents); } -# CSS - Bundle & Minify -$targetCssPath = "${pluginFolder}contentment.css"; -Get-Content -Raw -Path "${ProjectDir}**\**\*.css" | Set-Content -Encoding UTF8 -Path $targetCssPath; -& "${rootDir}\tools\AjaxMinifier.exe" $targetCssPath -o $targetCssPath +# CSS (Property Editors) - Copy +$cssFiles = Get-ChildItem -Path "${ProjectDir}DataEditors" -Recurse -Force -Include *.css; +foreach($cssFile in $cssFiles){ + $contents = Get-Content -Raw -Path $cssFile.FullName; + [IO.File]::WriteAllLines("${pluginFolder}\editors\$($cssFile.Name)", $contents); +} -# JS - Bundle & Minify -$targetJsPath = "${pluginFolder}contentment.js"; -Get-Content -Raw -Path "${ProjectDir}**\**\*.js" | Set-Content -Encoding UTF8 -Path $targetJsPath; -& "${rootDir}\tools\AjaxMinifier.exe" $targetJsPath -o $targetJsPath +# JS (Property Editors) - Copy +$jsFiles = Get-ChildItem -Path "${ProjectDir}DataEditors" -Recurse -Force -Include *.js; +foreach($jsFile in $jsFiles){ + $contents = Get-Content -Raw -Path $jsFile.FullName; + [IO.File]::WriteAllLines("${pluginFolder}\editors\$($jsFile.Name)", $contents); +} # In debug mode, copy the assets over to the local dev website if ($ConfigurationName -eq 'Debug' -AND -NOT($TargetDevWebsite -eq '')) { diff --git a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs index 20564569..afc8d2bb 100644 --- a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs @@ -24,12 +24,6 @@ public static IUmbracoBuilder AddContentment(this IUmbracoBuilder builder, Actio ? builder.Services.Configure(configure) : builder.Services.Configure(builder.Config.GetSection(Constants.Internals.ConfigurationSection)); - builder - .BackOfficeAssets() - .Append() - .Append() - ; - builder .WithCollectionBuilder() .Add(() => builder.TypeLoader.GetTypes()) diff --git a/src/Umbraco.Community.Contentment/Constants.cs b/src/Umbraco.Community.Contentment/Constants.cs index f85842bf..dafc7c92 100644 --- a/src/Umbraco.Community.Contentment/Constants.cs +++ b/src/Umbraco.Community.Contentment/Constants.cs @@ -122,12 +122,5 @@ internal static partial class Values public const string False = "0"; } - - internal static partial class WebAssets - { - internal const string CssPath = Internals.PackagePathRoot + Internals.ProjectAlias + ".css"; - - internal const string JsPath = Internals.PackagePathRoot + Internals.ProjectAlias + ".js"; - } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_directives.js b/src/Umbraco.Community.Contentment/DataEditors/_/_directives.js deleted file mode 100644 index 5e977cfb..00000000 --- a/src/Umbraco.Community.Contentment/DataEditors/_/_directives.js +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright © 2019 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -angular.module("umbraco.directives.html").directive("lkHtmlAttributes", [ - function () { - return { - restrict: "A", - scope: { - attributes: "&lkHtmlAttributes" - }, - link: function (scope, element, attrs) { - var attributes = scope.attributes(); - if (Array.isArray(attributes) && attributes.length > 0) { - attributes.forEach(function (x) { - if (x.name === "class") { - // NOTE: Slight bug, it did not account for existing class values. - element.addClass(x.value); - } else { - element.attr(x.name, x.value); - } - }); - } - } - }; - } -]); - -angular.module("umbraco.directives.html").directive("lkBindHtmlTemplate", [ - "$compile", - function ($compile) { - return { - restrict: "A", - replace: true, - link: function (scope, element, attrs) { - scope.$watch( - function (scope) { - return scope.$eval(attrs.lkBindHtmlTemplate); - }, - function (value) { - element.html(value); - $compile(element.contents())(scope); - } - ); - } - }; - } -]); diff --git a/src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs b/src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs deleted file mode 100644 index 23f801ae..00000000 --- a/src/Umbraco.Community.Contentment/Web/Assets/ContentmentCssFile.cs +++ /dev/null @@ -1,14 +0,0 @@ -/* Copyright © 2021 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -namespace Umbraco.Cms.Core.WebAssets -{ - internal class ContentmentCssFile : CssFile - { - public ContentmentCssFile() - : base(Community.Contentment.Constants.WebAssets.CssPath) - { } - } -} diff --git a/src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs b/src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs deleted file mode 100644 index 0f06cc3c..00000000 --- a/src/Umbraco.Community.Contentment/Web/Assets/ContentmentJsFile.cs +++ /dev/null @@ -1,14 +0,0 @@ -/* Copyright © 2021 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -namespace Umbraco.Cms.Core.WebAssets -{ - internal class ContentmentJsFile : JavaScriptFile - { - public ContentmentJsFile() - : base(Community.Contentment.Constants.WebAssets.JsPath) - { } - } -} diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/backoffice-ui-shim.css similarity index 100% rename from src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css rename to src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/backoffice-ui-shim.css diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html index 6d96a3ee..bf39516b 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html @@ -69,13 +69,13 @@
Telemetry

By default, the package sends telemetry data about which property-editors are being used (from Contentment only). For more details about the data being captured and transparency on the analysis, please visit leekelleher.com/umbraco/contentment/telemetry.

-

If you would prefer to opt-out and disable the telemetry feature, you can use the following code snippet to do so.

+

If you would prefer to opt-out and disable the telemetry feature, you can set an option to do so.

- Code snippet to disable Contentment telemetry -

Copy the C# class below. You can either save this to your ~/App_Code/ folder, or add it to your own code library.

- {{vm.disableTelemetryCode}} -

If you are already using a composer class, you can add the composition.DisableContentmentTelemetry(); line to it.

+ Configuration to disable Contentment telemetry +

In your appsettings.json file, add this option inside the "Umbraco" section, add the following.

+ {{vm.disableTelemetryCode | json}} +

If you prefer to use a strongly-typed configuration in C# code, you can do this with the .AddContentment(opts => { opts.DisableTelemetry = true; }) extension method in your Startup.cs file ConfigureServices() method.

@@ -88,13 +88,13 @@
Telemetry
Tree dashboard
-

If you would like to remove this page and tree item from the Settings section, you can use the following code snippet to do so.

+

If you would like to remove this page and tree item from the Settings section, you can set an option to do so.

- Code snippet to disable Contentment tree dashboard -

Copy the C# class below. You can either save this to your ~/App_Code/ folder, or add it to your own code library.

- {{vm.disableTreeCode}} -

If you are already using a composer class, you can add the composition.DisableContentmentTree(); line to it.

+ Configuration to disable Contentment tree dashboard +

In your appsettings.json file, add this option inside the "Umbraco" section, add the following.

+ {{vm.disableTreeCode | json}} +

If you prefer to use a strongly-typed configuration in C# code, you can do this with the .AddContentment(opts => { opts.DisableTree = true; }) extension method in your Startup.cs file ConfigureServices() method.

diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.js similarity index 85% rename from src/Umbraco.Community.Contentment/Web/UI/backoffice.js rename to src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.js index 1cac7b64..69a4b722 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.js @@ -85,32 +85,10 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Tree.Control } }; - vm.csharp = "csharp"; + vm.codeSnippetLanguage = "JSON"; vm.telemetryEnabled = config.telemetry === true; - vm.disableTelemetryCode = `using Umbraco.Core.Composing; - -namespace Our.Umbraco.Web -{ - public class DisableContentmentTelemetryComposer : IUserComposer - { - public void Compose(Composition composition) - { - composition.DisableContentmentTelemetry(); - } - } -}`; - vm.disableTreeCode = `using Umbraco.Core.Composing; - -namespace Our.Umbraco.Web -{ - public class DisableContentmentTreeComposer : IUserComposer - { - public void Compose(Composition composition) - { - composition.DisableContentmentTree(); - } - } -}`; + vm.disableTelemetryCode = { "Umbraco": { "Contentment": { "DisableTelemetry": true } } }; + vm.disableTreeCode = { "Umbraco": { "Contentment": { "DisableTree": true } } }; }; init(); diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_components.js b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_components.js similarity index 100% rename from src/Umbraco.Community.Contentment/DataEditors/_/_components.js rename to src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_components.js diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_dev-mode.js b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_directives.js similarity index 72% rename from src/Umbraco.Community.Contentment/DataEditors/_/_dev-mode.js rename to src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_directives.js index 9c2fc200..a1dfe0f5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/_dev-mode.js +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_directives.js @@ -3,49 +3,48 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -angular.module("umbraco.services").factory("Umbraco.Community.Contentment.Services.DevMode", [ - "$timeout", - "editorService", - function ($timeout, editorService) { +angular.module("umbraco.directives.html").directive("lkHtmlAttributes", [ + function () { return { - editValue: function (model, callback) { - editorService.open({ - title: "Edit raw value", - value: Utilities.toJson(model.value, true), - ace: { - showGutter: true, - useWrapMode: true, - useSoftTabs: true, - theme: "chrome", - mode: "javascript", - advanced: { - fontSize: "14px", - wrap: true - }, - onLoad: function (_editor) { - $timeout(function () { - _editor.focus(); - }); - }, - }, - view: "/App_Plugins/Contentment/editors/_json-editor.html", - size: "medium", - submit: function (value) { - - model.value = Utilities.fromJson(value); - - if (callback) { - callback(); + restrict: "A", + scope: { + attributes: "&lkHtmlAttributes" + }, + link: function (scope, element, attrs) { + var attributes = scope.attributes(); + if (Array.isArray(attributes) && attributes.length > 0) { + attributes.forEach(function (x) { + if (x.name === "class") { + // NOTE: Slight bug, it did not account for existing class values. + element.addClass(x.value); + } else { + element.attr(x.name, x.value); } + }); + } + } + }; + } +]); - editorService.close(); +angular.module("umbraco.directives.html").directive("lkBindHtmlTemplate", [ + "$compile", + function ($compile) { + return { + restrict: "A", + replace: true, + link: function (scope, element, attrs) { + scope.$watch( + function (scope) { + return scope.$eval(attrs.lkBindHtmlTemplate); }, - close: function () { - editorService.close(); + function (value) { + element.html(value); + $compile(element.contents())(scope); } - }); + ); } - } + }; } ]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_filters.js b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_filters.js similarity index 100% rename from src/Umbraco.Community.Contentment/DataEditors/_/_filters.js rename to src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_filters.js diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_services.js b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_services.js new file mode 100644 index 00000000..4d2d73c6 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/_services.js @@ -0,0 +1,52 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco.services").factory("Umbraco.Community.Contentment.Services.DevMode", [ + "$timeout", + "editorService", + function ($timeout, editorService) { + return { + editValue: function (model, callback) { + editorService.open({ + title: "Edit raw value", + value: Utilities.toJson(model.value, true), + ace: { + showGutter: true, + useWrapMode: true, + useSoftTabs: true, + theme: "chrome", + mode: "javascript", + advanced: { + fontSize: "14px", + wrap: true + }, + onLoad: function (_editor) { + $timeout(function () { + _editor.focus(); + }); + }, + }, + view: "/App_Plugins/Contentment/editors/_json-editor.html", + size: "medium", + submit: function (value) { + + model.value = Utilities.fromJson(value); + + if (callback) { + callback(); + } + + editorService.close(); + }, + close: function () { + editorService.close(); + } + }); + } + } + } +]); + + diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.css b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.css deleted file mode 100644 index 1b9bbfe7..00000000 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.css +++ /dev/null @@ -1,7 +0,0 @@ -/* Copyright © 2019 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -/* This file is intentionally empty. The contents is fully populated when the project is built. */ - diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.js b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.js deleted file mode 100644 index a294f17a..00000000 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/contentment.js +++ /dev/null @@ -1,7 +0,0 @@ -/* Copyright © 2019 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -// This file is intentionally empty. The contents is fully populated when the project is built. - diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_json-editor.html b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/editors/_json-editor.html similarity index 100% rename from src/Umbraco.Community.Contentment/DataEditors/_/_json-editor.html rename to src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/editors/_json-editor.html diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest new file mode 100644 index 00000000..829906e7 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/package.manifest @@ -0,0 +1,50 @@ +{ + "css": [ + "~/App_Plugins/Contentment/backoffice/backoffice-ui-shim.css", + "~/App_Plugins/Contentment/editors/buttons.css", + "~/App_Plugins/Contentment/editors/checkbox-list.css", + "~/App_Plugins/Contentment/editors/configuration-editor.css", + "~/App_Plugins/Contentment/editors/content-blocks.css", + "~/App_Plugins/Contentment/editors/data-list.editor.css", + "~/App_Plugins/Contentment/editors/data-list.preview.css", + "~/App_Plugins/Contentment/editors/icon-picker.css", + "~/App_Plugins/Contentment/editors/item-picker.overlay.css", + "~/App_Plugins/Contentment/editors/notes.css", + "~/App_Plugins/Contentment/editors/radio-button-list.css", + "~/App_Plugins/Contentment/editors/tags.css", + "~/App_Plugins/Contentment/editors/templated-list.css", + "~/App_Plugins/Contentment/editors/text-input.css" + ], + "javascript": [ + "~/App_Plugins/Contentment/backoffice/contentment/index.js", + "~/App_Plugins/Contentment/components/_components.js", + "~/App_Plugins/Contentment/components/_directives.js", + "~/App_Plugins/Contentment/components/_filters.js", + "~/App_Plugins/Contentment/components/_services.js", + "~/App_Plugins/Contentment/editors/buttons.js", + "~/App_Plugins/Contentment/editors/bytes.js", + "~/App_Plugins/Contentment/editors/cascading-dropdown-list.js", + "~/App_Plugins/Contentment/editors/checkbox-list.js", + "~/App_Plugins/Contentment/editors/code-editor.js", + "~/App_Plugins/Contentment/editors/configuration-editor.js", + "~/App_Plugins/Contentment/editors/configuration-editor.overlay.js", + "~/App_Plugins/Contentment/editors/content-blocks.js", + "~/App_Plugins/Contentment/editors/content-blocks.overlay.js", + "~/App_Plugins/Contentment/editors/content-source.js", + "~/App_Plugins/Contentment/editors/data-list.editor.js", + "~/App_Plugins/Contentment/editors/data-list.preview.js", + "~/App_Plugins/Contentment/editors/data-table.js", + "~/App_Plugins/Contentment/editors/dictionary-picker.js", + "~/App_Plugins/Contentment/editors/dropdown-list.js", + "~/App_Plugins/Contentment/editors/icon-picker.js", + "~/App_Plugins/Contentment/editors/item-picker.js", + "~/App_Plugins/Contentment/editors/item-picker.overlay.js", + "~/App_Plugins/Contentment/editors/macro-picker.js", + "~/App_Plugins/Contentment/editors/number-input.js", + "~/App_Plugins/Contentment/editors/radio-button-list.js", + "~/App_Plugins/Contentment/editors/render-macro.js", + "~/App_Plugins/Contentment/editors/tags.js", + "~/App_Plugins/Contentment/editors/templated-list.js", + "~/App_Plugins/Contentment/editors/text-input.js" + ] +} diff --git a/tools/AjaxMinifier.exe b/tools/AjaxMinifier.exe deleted file mode 100644 index 75f96484bdeed610d0969e8a483e3ae2922fe938..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 617472 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

}X#uZjQ5*L^rA7SfQL(JSq26>h%v;b& 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�>Yr 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𝔠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 wGO6Qisil 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 From 00e791c4b76eb12382d733248ddff35bb23777ce Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:31:59 +0100 Subject: [PATCH 54/81] Notifications - relocated the classes Easier for me to find them all together. --- .../ContentmentServerVariablesParsing.cs | 2 +- .../ContentmentTelemetryHandler.cs | 2 +- ...tUmbracoApplicationStartingNotification.cs | 38 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) rename src/Umbraco.Community.Contentment/{Composing => Notifications}/ContentmentServerVariablesParsing.cs (96%) rename src/Umbraco.Community.Contentment/{Telemetry => Notifications}/ContentmentTelemetryHandler.cs (99%) create mode 100644 src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs b/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsing.cs similarity index 96% rename from src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs rename to src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsing.cs index 67ac6da9..021350a3 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentServerVariablesParsing.cs +++ b/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsing.cs @@ -10,7 +10,7 @@ using Umbraco.Extensions; using Microsoft.Extensions.Options; -namespace Umbraco.Community.Contentment.Composing +namespace Umbraco.Community.Contentment.Notifications { internal sealed class ContentmentServerVariablesParsing : INotificationHandler { diff --git a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs b/src/Umbraco.Community.Contentment/Notifications/ContentmentTelemetryHandler.cs similarity index 99% rename from src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs rename to src/Umbraco.Community.Contentment/Notifications/ContentmentTelemetryHandler.cs index 7dbe5b58..a3b6a19b 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryHandler.cs +++ b/src/Umbraco.Community.Contentment/Notifications/ContentmentTelemetryHandler.cs @@ -20,7 +20,7 @@ using Umbraco.Community.Contentment.DataEditors; using Umbraco.Extensions; -namespace Umbraco.Community.Contentment.Telemetry +namespace Umbraco.Community.Contentment.Notifications { internal sealed class ContentmentTelemetryHandler : INotificationHandler { diff --git a/src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs b/src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs new file mode 100644 index 00000000..1417e731 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs @@ -0,0 +1,38 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Web.BackOffice.Trees; +using Umbraco.Community.Contentment.Trees; + +namespace Umbraco.Community.Contentment.Notifications +{ + internal class ContentmentUmbracoApplicationStartingNotification : INotificationHandler + { + private readonly ContentmentSettings _contentmentSettings; + private readonly TreeCollection _treeCollection; + + public ContentmentUmbracoApplicationStartingNotification( + IOptions contentmentSettings, + TreeCollection treeCollection) + { + _contentmentSettings = contentmentSettings.Value; + _treeCollection = treeCollection; + } + + public void Handle(UmbracoApplicationStartingNotification notification) + { + if (notification.RuntimeLevel == Cms.Core.RuntimeLevel.Run && + _contentmentSettings.DisableTree == true && + _treeCollection != null) + { + _treeCollection.RemoveTreeController(); + } + } + } +} From 883bb93108c148ed20afa4ddfb21e1ca162cd3de Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:37:43 +0100 Subject: [PATCH 55/81] Moved the startup logic back to the Composer class I've had a dilemma on how this should work. By having the `.AddContentment()` extension method to all the work, if a developer uses it, then it will be called twice. Once by the developer, the other by the Composer, so I'd need to code more defensively. By moving the configuration/service-registration back to the Composer, this will happen only once. Leaving the `.AddContentment()` method to only handle the user settings. --- .../Composing/ContentmentComposer.cs | 27 ++++++++++- .../Composing/UmbracoBuilderExtensions.cs | 48 ++++--------------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs index 4e1a09a2..e3365767 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs @@ -3,8 +3,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Community.Contentment.DataEditors; +using Umbraco.Community.Contentment.Notifications; using Umbraco.Extensions; namespace Umbraco.Community.Contentment.Composing @@ -13,7 +17,28 @@ internal sealed class ContentmentComposer : IComposer { public void Compose(IUmbracoBuilder builder) { - builder.AddContentment(); + builder + .Services + .Configure(builder.Config.GetSection(Constants.Internals.ConfigurationSection)) + ; + + builder + .WithCollectionBuilder() + .Add(() => builder.TypeLoader.GetTypes()) + ; + + builder.Services.AddUnique(); + + builder + .Components() + .Append() + ; + + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + ; } } } diff --git a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs index afc8d2bb..5d46fad9 100644 --- a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs @@ -6,56 +6,28 @@ using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.WebAssets; using Umbraco.Community.Contentment; using Umbraco.Community.Contentment.Composing; -using Umbraco.Community.Contentment.DataEditors; -using Umbraco.Community.Contentment.Telemetry; namespace Umbraco.Extensions { public static partial class UmbracoBuilderExtensions { - public static IUmbracoBuilder AddContentment(this IUmbracoBuilder builder, Action configure = default) + public static IUmbracoBuilder AddContentment( + this IUmbracoBuilder builder, + Action settings = default, + Action listItems = default) { - // TODO: [v9] [LK:2021-05-10] Is there a way to combine these? e.g. `configure` will fallback on values from appSettings? - _ = configure is not null - ? builder.Services.Configure(configure) - : builder.Services.Configure(builder.Config.GetSection(Constants.Internals.ConfigurationSection)); - - builder - .WithCollectionBuilder() - .Add(() => builder.TypeLoader.GetTypes()) - ; - - builder.Services.AddUnique(); - - builder.Components().Append(); - - builder - .AddNotificationHandler() - .AddNotificationHandler() - ; - - return builder; - } - - public static IUmbracoBuilder WithContentmentListItems(this IUmbracoBuilder builder, Action configure) - { - var items = builder.WithCollectionBuilder(); - - if (configure is not null) + if (settings is not null) { - configure(items); + _ = builder.Services.PostConfigure(settings); } - return builder; - } + if (listItems is not null) + { + listItems(builder.WithCollectionBuilder()); + } - public static IUmbracoBuilder UnlockContentment(this IUmbracoBuilder builder) - { - // NOTE: All of the Data List Sources have now been unlocked, this extension method is redundant. return builder; } } From 4e37e47fb920ba6a1d7e6d2eb57a0dafb5cf8a07 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:39:43 +0100 Subject: [PATCH 56/81] Removed "Unlock Features" option Wasn't being used. --- .../Configuration/ContentmentSettings.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs b/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs index ce9e70e5..2697224f 100644 --- a/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs +++ b/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs @@ -10,7 +10,5 @@ public class ContentmentSettings public bool DisableTree { get; set; } = false; public bool DisableTelemetry { get; set; } = false; - - public bool UnlockFeatures { get; set; } = false; } } From 2771a222093eacb22e9587ea372422f6df221d69 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:39:55 +0100 Subject: [PATCH 57/81] Updated Constants --- src/Umbraco.Community.Contentment/Constants.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Constants.cs b/src/Umbraco.Community.Contentment/Constants.cs index dafc7c92..0ba0e48f 100644 --- a/src/Umbraco.Community.Contentment/Constants.cs +++ b/src/Umbraco.Community.Contentment/Constants.cs @@ -21,7 +21,7 @@ internal static partial class Internals internal const string EditorsPathRoot = PackagePathRoot + "editors/"; - internal const string PackagePathRoot = "/App_Plugins/" + ProjectName + "/"; // TODO: [v9] [LK:2021-05-11] Add the ~/ back in later. + internal const string PackagePathRoot = "~/App_Plugins/" + ProjectName + "/"; internal const string PluginControllerName = ProjectName; @@ -111,7 +111,7 @@ internal static partial class Package public const string LicenseUrl = "https://mozilla.org/MPL/2.0/"; - public static readonly System.Version MinimumSupportedUmbracoVersion = new System.Version(8, 6, 1); + public static readonly System.Version MinimumSupportedUmbracoVersion = new System.Version(9, 0, 0); public const string RepositoryUrl = "https://github.com/leekelleher/umbraco-contentment"; } From 7737f6ccef1df9a3d5062fde8ebe5a55546b3a2f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:40:59 +0100 Subject: [PATCH 58/81] Refactored the `RemoveTreeController` hack I needed it work on the `TreeCollection` instead of the `TreeCollectionBuilder`. It is a little bit more hacker, but still, needs must. --- .../Trees/TreeCollectionBuilderExtensions.cs | 59 ------------------- .../Core/Trees/TreeCollectionExtensions.cs | 59 +++++++++++++++++++ .../Trees/CompositionExtensions.cs | 25 -------- 3 files changed, 59 insertions(+), 84 deletions(-) delete mode 100644 src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs create mode 100644 src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionExtensions.cs delete mode 100644 src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs diff --git a/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs deleted file mode 100644 index 6d1f4955..00000000 --- a/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionBuilderExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright © 2013-present Umbraco. - * This Source Code has been derived from Umbraco CMS. - * https://github.com/umbraco/Umbraco-CMS/blob/release-8.4.0/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs - * Modified under the permissions of the MIT License. - * Modifications are licensed under the Mozilla Public License. - * Copyright © 2020 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Umbraco.Cms.Core.Trees; -using Umbraco.Cms.Web.BackOffice.Trees; - -namespace Umbraco.Cms.Web -{ - public static class TreeCollectionBuilderExtensions - { - public static TreeCollectionBuilder RemoveTreeController(this TreeCollectionBuilder collection) - where TController : TreeControllerBase - { - return RemoveTreeController(collection, typeof(TController)); - } - - public static TreeCollectionBuilder RemoveTreeController(this TreeCollectionBuilder collection, Type controllerType) - { - var type = typeof(TreeCollectionBuilder); - - // https://github.com/umbraco/Umbraco-CMS/blob/release-8.4.0/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs#L13 - var field = type.GetField("_trees", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); - if (field == null) - { - return collection; - } - - var trees = (List)field.GetValue(collection); - if (trees == null) - { - return collection; - } - - if (typeof(TreeControllerBase).IsAssignableFrom(controllerType) == false) - { - throw new ArgumentException($"Type {controllerType} does not inherit from {nameof(TreeControllerBase)}."); - } - - var exists = trees.FirstOrDefault(x => x.TreeControllerType == controllerType); - if (exists != null) - { - trees.Remove(exists); - } - - return collection; - } - } -} diff --git a/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionExtensions.cs b/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionExtensions.cs new file mode 100644 index 00000000..9cc32bdc --- /dev/null +++ b/src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionExtensions.cs @@ -0,0 +1,59 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Reflection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Trees; + +namespace Umbraco.Cms.Web.BackOffice.Trees +{ + internal static class TreeCollectionExtensions + { + public static TreeCollection RemoveTreeController(this TreeCollection collection) + where TController : TreeControllerBase + { + var controllerType = typeof(TController); + var type = typeof(BuilderCollectionBase); + + // https://github.com/umbraco/Umbraco-CMS/blob/release-9.0.0-beta004/src/Umbraco.Core/Composing/BuilderCollectionBase.cs#L13 + var field = type.GetField("_items", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) + { + return collection; + } + + var trees = (Tree[])field.GetValue(collection); + if (trees == null) + { + return collection; + } + + if (typeof(TreeControllerBase).IsAssignableFrom(controllerType) == false) + { + throw new ArgumentException($"Type {controllerType} does not inherit from {nameof(TreeControllerBase)}."); + } + + var idx = Array.FindIndex(trees, x => x.TreeControllerType == controllerType); + if (idx > -1) + { + var tmp = new Tree[trees.Length - 1]; + + if (idx > 0) + { + Array.Copy(trees, 0, tmp, 0, idx); + } + else + { + Array.Copy(trees, idx + 1, tmp, idx, trees.Length - idx - 1); + } + + field.SetValue(collection, tmp); + } + + return collection; + } + } +} diff --git a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs deleted file mode 100644 index ca5519c8..00000000 --- a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -/* Copyright © 2021 Lee Kelleher. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Web; -using Umbraco.Community.Contentment.Trees; -using Umbraco.Extensions; - -namespace Umbraco.Core.Composing -{ - public static partial class CompositionExtensions - { - public static IUmbracoBuilder DisableContentmentTree(this IUmbracoBuilder builder) - { - builder - .Trees() - .RemoveTreeController() - ; - - return builder; - } - } -} From b539cc3f34d0eaf2a33e7d5fd1b5f182ca4f975f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:42:44 +0100 Subject: [PATCH 59/81] Ran "Code Cleanup" Removed unused namespaces, etc. --- .../CodeEditor/CodeEditorConfigurationEditor.cs | 3 +-- .../ConfigurationEditor/ConfigurationEditorUtility.cs | 5 ++--- .../ContentBlocks/ContentBlocksApiController.cs | 3 +-- .../DataEditors/ContentBlocks/ContentBlocksDataEditor.cs | 1 + .../ContentBlocksTypesConfigurationField.cs | 1 - .../DataList/DataSources/SqlDataListSource.cs | 1 - .../DataList/DataSources/UmbracoEntityDataListSource.cs | 9 ++++----- .../DataList/DataSources/UmbracoTagsDataListSource.cs | 1 - .../DataList/DataSources/XmlDataListSource.cs | 2 +- .../DataEditors/NumberInput/NumberInputDataEditor.cs | 4 ---- .../Notifications/ContentmentServerVariablesParsing.cs | 4 ++-- .../Web/Extensions/PublishedContentTypeExtensions.cs | 2 +- 12 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs index 1385e4ee..78cbfd5e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs @@ -5,11 +5,10 @@ using System.Collections.Generic; using System.IO; -using Umbraco.Core; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -using Umbraco.Cms.Core.Hosting; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs index 1f9422a7..7a992c61 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs @@ -7,11 +7,10 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using Umbraco.Community.Contentment.Composing; -using Umbraco.Core; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; using Umbraco.Cms.Core.Strings; +using Umbraco.Community.Contentment.Composing; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs index ee6318a4..1328aa68 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -111,7 +110,7 @@ public ActionResult GetPreviewMarkup([FromBody] JObject item, int elementIndex, viewData.Add(nameof(elementIcon), elementIcon); } - var markup = default(string); + string markup; try { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index f9800596..577a2b80 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -16,6 +16,7 @@ namespace Umbraco.Community.Contentment.DataEditors { + // TODO: [v9] [LK:2021-07-06] Implement the `ComplexPropertyEditorContentNotificationHandler` abstraction class. public sealed class ContentBlocksDataEditor : IDataEditor { internal const string DataEditorAlias = Constants.Internals.DataEditorAliasPrefix + "ContentBlocks"; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index 9e0d64a7..0f5388a5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; -using Umbraco.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index 1cfeef4b..ba9f3594 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -4,7 +4,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using System.Configuration; using System.Data.Common; using System.Data.SqlClient; //using System.Data.SqlServerCe; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index 58dba466..b0a146aa 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -6,16 +6,15 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; -using UmbConstants = Umbraco.Cms.Core.Constants; -using Umbraco.Extensions; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.IO; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs index 81e1b6b0..154d50ac 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs @@ -9,7 +9,6 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Web; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index d81ad18a..6332a5f6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -128,7 +128,7 @@ public IEnumerable GetItems(Dictionary config) { doc = new XPathDocument(path); } - catch(HttpRequestException ex) + catch (HttpRequestException ex) { _logger.LogError(ex, $"Unable to retrieve data from '{path}'."); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs index e82ec1d8..5e793c4c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs @@ -3,12 +3,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsing.cs b/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsing.cs index 021350a3..bc9b30d5 100644 --- a/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsing.cs +++ b/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsing.cs @@ -4,11 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core; using Umbraco.Extensions; -using Microsoft.Extensions.Options; namespace Umbraco.Community.Contentment.Notifications { diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs index f625483d..f3c28feb 100644 --- a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs +++ b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs @@ -3,9 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Umbraco.Community.Contentment.DataEditors; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; +using Umbraco.Community.Contentment.DataEditors; using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Web From 3d2e0fe22595fa3d4c2a3736f0a4f86e51313d43 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:44:39 +0100 Subject: [PATCH 60/81] Some `IOHelper` cleanup Added `IOHelper` bits on DataList, which meant that I could remove the reference from various list-editors. --- .../DataEditors/Buttons/ButtonsDataListEditor.cs | 2 +- .../CheckboxList/CheckboxListDataListEditor.cs | 11 +---------- .../ContentBlocks/ContentBlocksDataEditor.cs | 6 +++--- .../DataEditors/DataList/DataListDataEditor.cs | 7 ++++--- .../DropdownList/DropdownListDataListEditor.cs | 3 +-- .../ItemPicker/ItemPickerDataListEditor.cs | 2 +- .../DataEditors/Notes/NotesDataEditor.cs | 4 ++-- .../RadioButtonList/RadioButtonListDataListEditor.cs | 11 +---------- .../DataEditors/RenderMacro/RenderMacroDataEditor.cs | 4 ++-- .../DataEditors/Tags/TagsDataListEditor.cs | 11 +---------- .../TemplatedList/TemplatedListDataListEditor.cs | 2 +- 11 files changed, 18 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs index 9b827819..f8adbed7 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs @@ -101,6 +101,6 @@ public bool HasMultipleValues(Dictionary config) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); + public string View => DataEditorViewPath; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs index f3662156..fb86458b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs @@ -4,9 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -14,13 +12,6 @@ public sealed class CheckboxListDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "checkbox-list.html"; - private readonly IIOHelper _ioHelper; - - public CheckboxListDataListEditor(IIOHelper ioHelper) - { - _ioHelper = ioHelper; - } - public string Name => "Checkbox List"; public string Description => "Select multiple values from a list of checkboxes."; @@ -50,6 +41,6 @@ public CheckboxListDataListEditor(IIOHelper ioHelper) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); + public string View => DataEditorViewPath; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index 577a2b80..a0f504b8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -91,7 +91,7 @@ public IDataValueEditor GetValueEditor() _jsonSerializer) { ValueType = ValueTypes.Json, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } @@ -116,7 +116,7 @@ public IDataValueEditor GetValueEditor(object configuration) if (displayMode != null) { - view = displayMode.View; + view = _ioHelper.ResolveRelativeOrVirtualUrl(displayMode.View); } } } @@ -131,7 +131,7 @@ public IDataValueEditor GetValueEditor(object configuration) { Configuration = configuration, ValueType = ValueTypes.Json, - View = view, + View = _ioHelper.ResolveRelativeOrVirtualUrl(view), }; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs index cdcfa32d..5ba54913 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -76,7 +77,7 @@ public IDataValueEditor GetValueEditor() _jsonSerializer) { ValueType = ValueTypes.Json, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } @@ -99,7 +100,7 @@ public IDataValueEditor GetValueEditor(object configuration) var editor = _utility.GetConfigurationEditor(item.Value("key")); if (editor != null) { - view = editor.View; + view = _ioHelper.ResolveRelativeOrVirtualUrl(editor.View); } } @@ -110,7 +111,7 @@ public IDataValueEditor GetValueEditor(object configuration) { Configuration = configuration, ValueType = ValueTypes.Json, - View = view ?? DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(view ?? DataEditorViewPath), }; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs index 2f683133..5aa48fa1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -57,6 +56,6 @@ public DropdownListDataListEditor(IIOHelper ioHelper) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); + public string View => DataEditorViewPath; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs index ef8042ca..d21ba595 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs @@ -129,6 +129,6 @@ public bool HasMultipleValues(Dictionary config) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); + public string View => DataEditorViewPath; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs index 0d39001d..c81d4c67 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs @@ -64,7 +64,7 @@ public IDataValueEditor GetValueEditor() _jsonSerializer) { ValueType = ValueTypes.Integer, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } @@ -85,7 +85,7 @@ public IDataValueEditor GetValueEditor(object configuration) Configuration = configuration, HideLabel = hideLabel, ValueType = ValueTypes.Integer, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs index 1f837a31..a6359897 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs @@ -4,9 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -14,13 +12,6 @@ public sealed class RadioButtonListDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "radio-button-list.html"; - private readonly IIOHelper _ioHelper; - - public RadioButtonListDataListEditor(IIOHelper ioHelper) - { - _ioHelper = ioHelper; - } - public string Name => "Radio Button List"; public string Description => "Select a single value from a list of radio buttons."; @@ -44,6 +35,6 @@ public RadioButtonListDataListEditor(IIOHelper ioHelper) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); + public string View => DataEditorViewPath; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs index f9a57fc3..f47a9d39 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs @@ -64,7 +64,7 @@ public IDataValueEditor GetValueEditor() _jsonSerializer) { ValueType = ValueTypes.Integer, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } @@ -85,7 +85,7 @@ public IDataValueEditor GetValueEditor(object configuration) Configuration = configuration, HideLabel = hideLabel, ValueType = ValueTypes.Integer, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs index 7664afa9..2833cd93 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs @@ -4,9 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -14,13 +12,6 @@ public sealed class TagsDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "tags.html"; - private readonly IIOHelper _ioHelper; - - public TagsDataListEditor(IIOHelper ioHelper) - { - _ioHelper = ioHelper; - } - public string Name => "Tags"; public string Description => "Select items from an Umbraco Tags-like interface."; @@ -50,6 +41,6 @@ public TagsDataListEditor(IIOHelper ioHelper) public OverlaySize OverlaySize => OverlaySize.Small; - public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); + public string View => DataEditorViewPath; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs index b07bf625..7d8d5e5d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs @@ -85,6 +85,6 @@ public bool HasMultipleValues(Dictionary config) public OverlaySize OverlaySize => OverlaySize.Medium; - public string View => _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath); + public string View => DataEditorViewPath; } } From 7f8d79995e2baac4565f9872bfbedef57e7cdde4 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:45:35 +0100 Subject: [PATCH 61/81] DataList - Members data-source Refactored, just in case the Member was null. e.g. it may have been deleted since being picked. --- .../DataSources/UmbracoMembersDataListSource.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index 47aefafe..c925fe57 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -134,10 +134,16 @@ DataListItem mapMember(IMember member) public object ConvertValue(Type type, string value) { - // TODO: [v9] [LK:2021-04-30] Review this, as why would it only have `Get(IMember)`? - return UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false - ? _publishedSnapshotAccessor.PublishedSnapshot.Members.Get(_memberService.GetByKey(udi.Guid)) - : default; + if (UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false) + { + var member = _memberService.GetByKey(udi.Guid); + if (member != null) + { + return _publishedSnapshotAccessor.PublishedSnapshot?.Members.Get(_memberService.GetByKey(udi.Guid)); + } + } + + return default(IPublishedContent); } } } From 41c169df114a0223e9a7de64acdb56482b7c345f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:46:09 +0100 Subject: [PATCH 62/81] DataList - Number Range data-source refactored This can be backported to Contentment v2.x --- .../DataSources/NumberRangeDataListSource.cs | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs index e41e654d..6a049114 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs @@ -5,11 +5,10 @@ using System; using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core; -using Umbraco.Extensions; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors @@ -42,7 +41,7 @@ public NumberRangeDataListSource(IIOHelper ioHelper) Config = new Dictionary { { "step", 0.1D }, - { UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, "DECIMAL" } + { UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, nameof(Decimal).ToUpper() } }, }, new ConfigurationField @@ -54,10 +53,9 @@ public NumberRangeDataListSource(IIOHelper ioHelper) Config = new Dictionary { { "step", 0.1D }, - { UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, "DECIMAL" } + { UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, nameof(Decimal).ToUpper() } }, }, - new ConfigurationField { Key = "step", @@ -67,9 +65,23 @@ public NumberRangeDataListSource(IIOHelper ioHelper) Config = new Dictionary { { "step", 0.1D }, - { UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, "DECIMAL" } + { UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, nameof(Decimal).ToUpper() } }, }, + new ConfigurationField + { + Key = "decimals", + Name = "Decimal places", + Description = "How many decimal places would you like?", + View = _ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/slider/slider.html"), + Config = new Dictionary + { + { "initVal1", 0 }, + { "minVal", 0 }, + { "maxVal", 10 }, + { "step", 1 } + } + } }; public Dictionary DefaultValues => new Dictionary @@ -77,6 +89,7 @@ public NumberRangeDataListSource(IIOHelper ioHelper) { "start", 1 }, { "end", 10 }, { "step", 1 }, + { "decimals", 0 }, }; public OverlaySize OverlaySize => OverlaySize.Small; @@ -86,35 +99,34 @@ public IEnumerable GetItems(Dictionary config) var start = config.GetValueAs("start", defaultValue: default(double)); var end = config.GetValueAs("end", defaultValue: default(double)); var step = config.GetValueAs("step", defaultValue: default(double)); + var decimals = config.GetValueAs("decimals", defaultValue: default(int)); + var format = string.Concat("N", decimals); - return GetRange(start, end, step).Select(x => new DataListItem { Name = x.ToString(), Value = x.ToString() }); - } - - public Type GetValueType(Dictionary config) => typeof(double); - - public object ConvertValue(Type type, string value) => value.TryConvertTo(type).ResultOr(default(double)); - - private IEnumerable GetRange(double start, double end, double step) - { if (step <= default(double)) { step = step == default ? 1D : -step; } + DataListItem newItem(double i) => new DataListItem { Name = i.ToString(format), Value = i.ToString(format) }; + if (start <= end) { for (var i = start; i <= end; i += step) { - yield return i; + yield return newItem(i); } } else { for (var i = start; i >= end; i -= step) { - yield return i; + yield return newItem(i); } } } + + public Type GetValueType(Dictionary config) => typeof(double); + + public object ConvertValue(Type type, string value) => value.TryConvertTo(type).ResultOr(default(double)); } } From 13c59670637b3ae7781267a1703812d0f09f92b0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Jul 2021 15:46:38 +0100 Subject: [PATCH 63/81] ConfigurationEditor - help note, closed the details This can be backported to Contentment v2.x --- .../ConfigurationEditor/configuration-editor.overlay.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html index 45605a5e..4ccc019d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html @@ -52,7 +52,7 @@

-
+
From 89ce4d50aa920142f2723254384741e044cb8149 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 8 Jul 2021 11:44:14 +0100 Subject: [PATCH 64/81] Upgraded Umbraco.Cms dependency to v9.0.0-rc001 Resolved any API breaking-changes. --- .../Composing/ContentmentComponent.cs | 19 ++++------- .../Install/RegisterUmbracoPackageEntry.cs | 34 +++---------------- .../Umbraco.Community.Contentment.csproj | 4 +-- 3 files changed, 13 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs index f258798e..7648477f 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs @@ -3,11 +3,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Community.Contentment.Migrations; @@ -15,30 +14,24 @@ namespace Umbraco.Community.Contentment.Composing { internal sealed class ContentmentComponent : IComponent { + private readonly IMigrationPlanExecutor _migrationPlanExecutor; private readonly IScopeProvider _scopeProvider; - private readonly IMigrationBuilder _migrationBuilder; private readonly IKeyValueService _keyValueService; - private readonly ILogger _logger; - private readonly ILoggerFactory _loggerFactory; public ContentmentComponent( + IMigrationPlanExecutor migrationPlanExecutor, IScopeProvider scopeProvider, - IMigrationBuilder migrationBuilder, - IKeyValueService keyValueService, - ILogger logger, - ILoggerFactory loggerFactory) + IKeyValueService keyValueService) { + _migrationPlanExecutor = migrationPlanExecutor; _scopeProvider = scopeProvider; - _migrationBuilder = migrationBuilder; _keyValueService = keyValueService; - _logger = logger; - _loggerFactory = loggerFactory; } public void Initialize() { var upgrader = new Upgrader(new ContentmentPlan()); - upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger, _loggerFactory); + upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); } public void Terminate() diff --git a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs index 5e91ce35..717e92af 100644 --- a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs +++ b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs @@ -3,46 +3,22 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Linq; -using Umbraco.Cms.Core.Packaging; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Migrations; namespace Umbraco.Community.Contentment.Migrations { internal sealed class RegisterUmbracoPackageEntry : MigrationBase { - private readonly IPackagingService _packagingService; - public const string State = "{contentment-reg-umb-pkg-entry}"; - public RegisterUmbracoPackageEntry(IPackagingService packagingService, IMigrationContext context) + public RegisterUmbracoPackageEntry(IMigrationContext context) : base(context) - { - _packagingService = packagingService; - } + { } - public override void Migrate() + protected override void Migrate() { - // Check if the package has already been installed. - var pkgs = _packagingService.GetInstalledPackageByName(Constants.Internals.ProjectName); - if (pkgs?.Any() == false) - { - // If not, then make a package definition and save it to the "installedPackages.config". - _packagingService.SaveInstalledPackage(new PackageDefinition - { - Name = Constants.Internals.ProjectName, - Url = Constants.Package.RepositoryUrl, - Author = Constants.Package.Author, - AuthorUrl = Constants.Package.AuthorUrl, - IconUrl = Constants.Package.IconUrl, - License = Constants.Package.License, - LicenseUrl = Constants.Package.LicenseUrl, - UmbracoVersion = Constants.Package.MinimumSupportedUmbracoVersion, - Version = ContentmentVersion.Version.ToString(), - Readme = "", - }); - } + // NOTE: This migration does nothing. It is a left over from code targeting Umbraco 8. + // It needs to remain, as Umbraco instances that have been upgraded from v8 to v9 will have reached this migration state. } } } diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 165444f7..9595c952 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -16,8 +16,8 @@ git - - + + From 9ef9b5e6a1ce75d4abcb53f290468b4e1ec94453 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 8 Jul 2021 12:56:56 +0100 Subject: [PATCH 65/81] Incremented version number, v3.0.0-alpha001 I'm going with "alpha" for this initial release against Umbraco v9.0.0-RC001. As I'm unsure what I'll come up against. My aim is to keep the alphas/betas short lived. --- VERSION | 2 +- .../Umbraco.Community.Contentment.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 223d33e2..54bc18ba 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-develop \ No newline at end of file +3.0.0-alpha001 \ No newline at end of file diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 9595c952..08da7e60 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-develop + 3.0.0-alpha001 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher From 833ca9fb915977c942ce3a752a2a48ca2a3042a6 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 8 Jul 2021 14:36:26 +0100 Subject: [PATCH 66/81] Build script - tweaks and fixes Hit this issue... https://docs.microsoft.com/en-us/dotnet/core/tools/sdk-errors/netsdk1005 Replaced the old "vswhere.exe" and MSBuild.exe calls with `dotnet` cli. Removed the Umbraco package XML manifest - no longer needed. --- .gitignore | 5 +- build/build-assets.ps1 | 6 -- build/build-pkgs.ps1 | 97 +++++-------------- build/manifest-nuget-core.nuspec | 8 +- build/manifest-umbraco.xml | 37 ------- .../Umbraco.Community.Contentment.csproj | 2 +- 6 files changed, 29 insertions(+), 126 deletions(-) delete mode 100644 build/manifest-umbraco.xml diff --git a/.gitignore b/.gitignore index 77bfbedd..1d3edc2e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,5 @@ artifacts/ build/assets/ src/packages/*/** /tools/*.exe -/build/__umb/* -/build/__nuget/* -/build/build-push.ps1 +/build/build-push-nuget.ps1 +/build/build-push-umb.ps1 diff --git a/build/build-assets.ps1 b/build/build-assets.ps1 index 062c44d5..cdf9ed1f 100644 --- a/build/build-assets.ps1 +++ b/build/build-assets.ps1 @@ -29,12 +29,6 @@ if (Test-Path -Path $targetFolder) { Remove-Item -Recurse -Force $targetFolder; } -# Copy DLL / PDB -$binFolder = "${targetFolder}\bin\Debug\net5.0"; - -if (!(Test-Path -Path $binFolder)) {New-Item -Path $binFolder -Type Directory;} -Copy-Item -Path "${TargetDir}${ProjectName}.*" -Destination $binFolder; - # Copy package front-end files assets $pluginFolder = "${targetFolder}\App_Plugins\Contentment\"; if (!(Test-Path -Path $pluginFolder)) {New-Item -Path $pluginFolder -Type Directory;} diff --git a/build/build-pkgs.ps1 b/build/build-pkgs.ps1 index 5d26d696..5623f3b0 100644 --- a/build/build-pkgs.ps1 +++ b/build/build-pkgs.ps1 @@ -8,17 +8,12 @@ $nugetPackageId = 'Our.Umbraco.Community.Contentment'; $projectNamespace = 'Umbraco.Community.Contentment'; -$packageName = 'Contentment'; -$nugetTitle = "${packageName} for Umbraco"; -$packageDescription = "${packageName}, a collection of components for Umbraco 8."; +$nugetTitle = "Contentment for Umbraco"; +$packageDescription = "Contentment, a collection of components for Umbraco."; $packageUrl = 'https://github.com/leekelleher/umbraco-contentment'; -$iconUrl = 'https://raw.githubusercontent.com/leekelleher/umbraco-contentment/master/docs/assets/img/logo.png'; -$licenseName = 'Mozilla Public License Version 2.0'; -$licenseUrl = 'https://mozilla.org/MPL/2.0/'; $authorName = 'Lee Kelleher'; -$authorUrl = 'https://leekelleher.com/'; -$minUmbracoVersion = 8,14,0; -$copyright = "Copyright " + [char]0x00A9 + " " + (Get-Date).year + " $authorName"; +$minUmbracoVersion = "9.0.0-rc001"; +$copyright = "" + [char]0x00A9 + " " + (Get-Date).year + " $authorName"; $tags = "umbraco"; $rootFolder = (Get-Item($MyInvocation.MyCommand.Path)).Directory.Parent.FullName; @@ -32,84 +27,36 @@ $csprojXml = [xml](Get-Content -Path "${srcFolder}\${projectNamespace}\${project $version = $csprojXml.Project.PropertyGroup.Version; Write-Host "Package version: $version"; -# Build the VS project - -# Ensure NuGet.exe -$nuget_exe = "${rootFolder}\tools\nuget.exe"; -If (-NOT(Test-Path -Path $nuget_exe)) { - Write-Host "Retrieving nuget.exe..."; - Invoke-WebRequest "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile $nuget_exe; -} -# vswhere.exe is part of VS2017 (v15.2)+ -$vswhere_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"; - -$msbuild_exe = & "$vswhere_exe" -Latest -Requires Microsoft.Component.MSBuild -Find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1 -if (-NOT(Test-Path $msbuild_exe)) { - throw 'MSBuild not found!'; -} - -Write-Host 'Restoring NuGet packages...'; -& $nuget_exe restore "${srcFolder}\${projectNamespace}.sln"; +# Build the VS project +Write-Host 'Cleaning Visual Studio solution.'; +& dotnet clean "${srcFolder}\${projectNamespace}.sln"; Write-Host 'Compiling Visual Studio solution.'; -& $msbuild_exe "${srcFolder}\${projectNamespace}.sln" /p:Configuration=Release +& dotnet build "${srcFolder}\${projectNamespace}.sln" --configuration Release if (-NOT $?) { - throw 'The MSBuild process returned an error code.'; + throw 'The dotnet CLI returned an error code.'; } -# Populate the Umbraco package manifest - -$umbFolder = Join-Path -Path $buildFolder -ChildPath "__umb"; -if (!(Test-Path -Path $umbFolder)) {New-Item -Path $umbFolder -Type Directory;} - -$umbracoManifest = Join-Path -Path $buildFolder -ChildPath "manifest-umbraco.xml"; -$umbracoPackageXml = [xml](Get-Content $umbracoManifest); -$umbracoPackageXml.umbPackage.info.package.version = "$($version)"; -$umbracoPackageXml.umbPackage.info.package.name = $packageName; -$umbracoPackageXml.umbPackage.info.package.iconUrl = $iconUrl; -$umbracoPackageXml.umbPackage.info.package.license.set_InnerText($licenseName); -$umbracoPackageXml.umbPackage.info.package.license.url = $licenseUrl; -$umbracoPackageXml.umbPackage.info.package.url = $packageUrl; -$umbracoPackageXml.umbPackage.info.package.requirements.major = "$($minUmbracoVersion[0])"; -$umbracoPackageXml.umbPackage.info.package.requirements.minor = "$($minUmbracoVersion[1])"; -$umbracoPackageXml.umbPackage.info.package.requirements.patch = "$($minUmbracoVersion[2])"; -$umbracoPackageXml.umbPackage.info.author.name = $authorName; -$umbracoPackageXml.umbPackage.info.author.website = $authorUrl; -$umbracoPackageXml.umbPackage.info.readme."#cdata-section" = $packageDescription; - -$filesXml = $umbracoPackageXml.CreateElement("files"); +# Copy DLL to assets folder +$binFolder = "${assetsFolder}\bin"; +if (!(Test-Path -Path $binFolder)) {New-Item -Path $binFolder -Type Directory;} +Copy-Item -Path "${srcFolder}\${projectNamespace}\bin\Release\net5.0\${projectNamespace}.dll" -Destination $binFolder; -$assetFiles = Get-ChildItem -Path $assetsFolder -File -Recurse; -foreach($assetFile in $assetFiles){ - - $hash = Get-FileHash -Path $assetFile.FullName -Algorithm MD5; - $guid = $hash.Hash.ToLower() + $assetFile.Extension; - $orgPath = "~" + $assetFile.Directory.FullName.Replace($assetsFolder, "").Replace("\", "/"); - - $fileXml = $umbracoPackageXml.CreateElement("file"); - $fileXml.set_InnerXML("${guid}${orgPath}$($assetFile.Name)"); - $filesXml.AppendChild($fileXml); - - Copy-Item -Path $assetFile.FullName -Destination "${umbFolder}\${guid}"; -} - -$umbracoPackageXml.umbPackage.ReplaceChild($filesXml, $umbracoPackageXml.SelectSingleNode("/umbPackage/files")) | Out-Null; -$umbracoPackageXml.Save("${umbFolder}\package.xml"); +# Ensure the artifacts folder $artifactsFolder = Join-Path -Path $rootFolder -ChildPath "artifacts"; if (!(Test-Path -Path $artifactsFolder)) {New-Item -Path $artifactsFolder -Type Directory;} -Compress-Archive -Path "${umbFolder}\*" -DestinationPath "${artifactsFolder}\Contentment_$version.zip" -Force; +# Ensure NuGet.exe +$nuget_exe = "${rootFolder}\tools\nuget.exe"; +If (-NOT(Test-Path -Path $nuget_exe)) { + Write-Host "Retrieving nuget.exe..."; + Invoke-WebRequest "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile $nuget_exe; +} # Populate the NuGet package manifest - Copy-Item -Path "${rootFolder}\docs\assets\img\logo.png" -Destination "${assetsFolder}\icon.png"; -& $nuget_exe pack "${buildFolder}\manifest-nuget-core.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$($minUmbracoVersion[0]).$($minUmbracoVersion[1]).$($minUmbracoVersion[2]);repositoryUrl=$packageUrl;" -& $nuget_exe pack "${buildFolder}\manifest-nuget-web.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$($minUmbracoVersion[0]).$($minUmbracoVersion[1]).$($minUmbracoVersion[2]);repositoryUrl=$packageUrl;" - - -# Tidy up folders -Remove-Item -Recurse -Force $umbFolder; - +& $nuget_exe pack "${buildFolder}\manifest-nuget-core.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$minUmbracoVersion;repositoryUrl=$packageUrl;" +& $nuget_exe pack "${buildFolder}\manifest-nuget-web.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$minUmbracoVersion;repositoryUrl=$packageUrl;" diff --git a/build/manifest-nuget-core.nuspec b/build/manifest-nuget-core.nuspec index ecfd2a75..486dbee5 100644 --- a/build/manifest-nuget-core.nuspec +++ b/build/manifest-nuget-core.nuspec @@ -16,14 +16,14 @@ $tags$ - - - + + + - + \ No newline at end of file diff --git a/build/manifest-umbraco.xml b/build/manifest-umbraco.xml deleted file mode 100644 index d050e5aa..00000000 --- a/build/manifest-umbraco.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - 0.0.0 - - - - - 0 - 0 - 0 - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 08da7e60..5b5172cf 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -1,6 +1,6 @@  - net5.0 + net5.0 Umbraco.Community.Contentment Our.Umbraco.Community.Contentment Contentment for Umbraco From e08743ce19088284fed23c555d4dee7e8aec6698 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 8 Jul 2021 16:57:44 +0100 Subject: [PATCH 67/81] Fixed Umbraco NuGet package dependency names Kicking myself! :facepalm: --- VERSION | 2 +- build/manifest-nuget-core.nuspec | 4 ++-- .../Umbraco.Community.Contentment.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 54bc18ba..f4dd6903 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha001 \ No newline at end of file +3.0.0-alpha002 \ No newline at end of file diff --git a/build/manifest-nuget-core.nuspec b/build/manifest-nuget-core.nuspec index 486dbee5..1711a517 100644 --- a/build/manifest-nuget-core.nuspec +++ b/build/manifest-nuget-core.nuspec @@ -17,8 +17,8 @@ - - + + diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 5b5172cf..178b9078 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-alpha001 + 3.0.0-alpha002 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher From 8171c71188b46d22dd02117d27e3719c6f33fc1b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 9 Jul 2021 18:09:47 +0100 Subject: [PATCH 68/81] NuGet - fixed bug with post-install content copy Thanks to @KevinJump and @bergmania for help for me to resolve this. Fixes #124 --- VERSION | 2 +- build/_nuget-post-install.targets | 26 +++++++++++++++++++ build/build-pkgs.ps1 | 2 ++ build/manifest-nuget-web.nuspec | 1 + .../Umbraco.Community.Contentment.csproj | 2 +- 5 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 build/_nuget-post-install.targets diff --git a/VERSION b/VERSION index f4dd6903..48edf95e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha002 \ No newline at end of file +3.0.0-alpha003 \ No newline at end of file diff --git a/build/_nuget-post-install.targets b/build/_nuget-post-install.targets new file mode 100644 index 00000000..4a41a772 --- /dev/null +++ b/build/_nuget-post-install.targets @@ -0,0 +1,26 @@ + + + + $(MSBuildThisFileDirectory)..\content\App_Plugins\Contentment\**\*.* + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/build-pkgs.ps1 b/build/build-pkgs.ps1 index 5623f3b0..6a4a149a 100644 --- a/build/build-pkgs.ps1 +++ b/build/build-pkgs.ps1 @@ -30,6 +30,7 @@ Write-Host "Package version: $version"; # Build the VS project Write-Host 'Cleaning Visual Studio solution.'; +Remove-Item -Recurse -Force "${srcFolder}\${projectNamespace}\obj"; & dotnet clean "${srcFolder}\${projectNamespace}.sln"; Write-Host 'Compiling Visual Studio solution.'; @@ -58,5 +59,6 @@ If (-NOT(Test-Path -Path $nuget_exe)) { # Populate the NuGet package manifest Copy-Item -Path "${rootFolder}\docs\assets\img\logo.png" -Destination "${assetsFolder}\icon.png"; +Copy-Item -Path "${buildFolder}\_nuget-post-install.targets" -Destination "${assetsFolder}\${nugetPackageId}.targets"; & $nuget_exe pack "${buildFolder}\manifest-nuget-core.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$minUmbracoVersion;repositoryUrl=$packageUrl;" & $nuget_exe pack "${buildFolder}\manifest-nuget-web.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$minUmbracoVersion;repositoryUrl=$packageUrl;" diff --git a/build/manifest-nuget-web.nuspec b/build/manifest-nuget-web.nuspec index 1ad4282c..f1c9c4b4 100644 --- a/build/manifest-nuget-web.nuspec +++ b/build/manifest-nuget-web.nuspec @@ -20,6 +20,7 @@ + diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 178b9078..7a815e2d 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-alpha002 + 3.0.0-alpha003 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher From c6ab15984b10d51f83e025b592221fe084099531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Thu, 19 Aug 2021 15:03:01 +0200 Subject: [PATCH 69/81] Update to Umbraco 9 RC2 and fix breaking changes --- .../Composing/ContentmentListItemCollectionBuilder.cs | 2 +- .../DataEditors/ContentBlocks/ContentBlocksApiController.cs | 3 ++- .../ContentBlocks/ContentBlocksValueConverter.cs | 2 +- .../DataList/DataSources/UmbracoContentDataListSource.cs | 6 +++--- .../DataSources/UmbracoContentTypesDataListSource.cs | 2 +- .../DataSources/UmbracoContentXPathDataListSource.cs | 4 ++-- .../DataList/DataSources/UmbracoMembersDataListSource.cs | 2 +- .../Umbraco.Community.Contentment.csproj | 4 ++-- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs index 82eb6cd2..a059f731 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs @@ -24,7 +24,7 @@ public sealed class ContentmentListItemCollection : BuilderCollectionBase _lookup; - public ContentmentListItemCollection(IEnumerable items) + public ContentmentListItemCollection(Func> items) : base(items) { _lookup = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs index 1328aa68..b5cf26d1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -22,6 +22,7 @@ using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Community.Contentment.Web.PublishedCache; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -58,7 +59,7 @@ public ContentBlocksApiController( public ActionResult GetPreviewMarkup([FromBody] JObject item, int elementIndex, Guid elementKey, int contentId) { var preview = true; - var umbracoContext = _umbracoContextAccessor.UmbracoContext; + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); var content = umbracoContext.Content.GetById(true, contentId); if (content == null) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs index 691dcb67..9859c770 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs @@ -62,7 +62,7 @@ public override object ConvertIntermediateToObject(IPublishedElement owner, IPub if (ContentTypeCacheHelper.TryGetAlias(item.ElementType, out var alias, _contentTypeService) == false) continue; - var contentType = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetContentType(alias); + var contentType = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Content.GetContentType(alias); if (contentType == null || contentType.IsElement == false) continue; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 3a0636a5..3b2be36a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -69,7 +69,7 @@ public IEnumerable GetItems(Dictionary config) if (parentNode.InvariantStartsWith("umb://document/") == false) { var nodeContextId = default(int?); - var umbracoContext = _umbracoContextAccessor.UmbracoContext; + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). if (int.TryParse(_requestAccessor.GetQueryStringValue("id"), out var currentId) == true) @@ -98,7 +98,7 @@ public IEnumerable GetItems(Dictionary config) } else if (UdiParser.TryParse(parentNode, out GuidUdi udi) == true && udi.Guid != Guid.Empty) { - startNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(preview, udi.Guid); + startNode = _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetById(preview, udi.Guid); } if (startNode != null) @@ -122,7 +122,7 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { return UdiParser.TryParse(value, out var udi) == true - ? _umbracoContextAccessor.UmbracoContext.Content.GetById(udi) + ? _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetById(udi) : default; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs index 51b049eb..59d6af56 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs @@ -135,7 +135,7 @@ public object ConvertValue(Type type, string value) { if (UdiParser.TryParse(value, out GuidUdi udi) == true && ContentTypeCacheHelper.TryGetAlias(udi.Guid, out var alias, _contentTypeService) == true) { - return _umbracoContextAccessor.UmbracoContext.Content.GetContentType(alias); + return _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetContentType(alias); } return default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs index cb7f92f5..3dcd4d20 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs @@ -89,7 +89,7 @@ public IEnumerable GetItems(Dictionary config) { var nodeContextId = default(int?); var preview = true; - var umbracoContext = _umbracoContextAccessor.UmbracoContext; + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). if (int.TryParse(_requestAccessor.GetQueryStringValue("id"), out var currentId) == true) @@ -128,7 +128,7 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { return UdiParser.TryParse(value, out var udi) == true - ? _umbracoContextAccessor.UmbracoContext.Content.GetById(udi) + ? _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetById(udi) : default; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index c925fe57..18dd8efc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -139,7 +139,7 @@ public object ConvertValue(Type type, string value) var member = _memberService.GetByKey(udi.Guid); if (member != null) { - return _publishedSnapshotAccessor.PublishedSnapshot?.Members.Get(_memberService.GetByKey(udi.Guid)); + return _publishedSnapshotAccessor.GetRequiredPublishedSnapshot()?.Members.Get(_memberService.GetByKey(udi.Guid)); } } diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 7a815e2d..5739e71c 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -16,8 +16,8 @@ git - - + + From d70262a6dab688a66feb9593bdfb96469b7c1d4a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 8 Sep 2021 06:47:52 +0100 Subject: [PATCH 70/81] ContentBlocks - corrected namespace in Razor view Fixes #125 --- .../DataEditors/ContentBlocks/ContentBlockPreview.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml index 2426238d..9a3802c3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml @@ -2,7 +2,7 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. *@ -@inherits Umbraco.Web.Mvc.ContentBlockPreviewView +@inherits Umbraco.Cms.Web.Common.Views.ContentBlockPreviewView
@Model.Element.Key
From 91c1f75e2e230e709e227b6cc5d85933f82db04f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 8 Sep 2021 06:53:56 +0100 Subject: [PATCH 71/81] Incremented version number, v3.0.0-alpha004 Bumped minimum Umbraco version dependency to v9.0.0-rc002 --- VERSION | 2 +- build/build-pkgs.ps1 | 2 +- .../Umbraco.Community.Contentment.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 48edf95e..f2ef0398 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha003 \ No newline at end of file +3.0.0-alpha004 \ No newline at end of file diff --git a/build/build-pkgs.ps1 b/build/build-pkgs.ps1 index 6a4a149a..89b341a5 100644 --- a/build/build-pkgs.ps1 +++ b/build/build-pkgs.ps1 @@ -12,7 +12,7 @@ $nugetTitle = "Contentment for Umbraco"; $packageDescription = "Contentment, a collection of components for Umbraco."; $packageUrl = 'https://github.com/leekelleher/umbraco-contentment'; $authorName = 'Lee Kelleher'; -$minUmbracoVersion = "9.0.0-rc001"; +$minUmbracoVersion = "9.0.0-rc002"; $copyright = "" + [char]0x00A9 + " " + (Get-Date).year + " $authorName"; $tags = "umbraco"; diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 5739e71c..853286f4 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-alpha003 + 3.0.0-alpha004 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher From b57bf92e731107290278886380df18618fed2511 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 8 Sep 2021 07:13:57 +0100 Subject: [PATCH 72/81] DataList - UmbracoContent, UmbracoContext refactor Small tweak to save calling `_umbracoContextAccessor.GetRequiredUmbracoContext()` twice. --- .../DataList/DataSources/UmbracoContentDataListSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 3b2be36a..2e50bd7e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -65,11 +65,11 @@ public IEnumerable GetItems(Dictionary config) var preview = true; var parentNode = config.GetValueAs("parentNode", string.Empty); var startNode = default(IPublishedContent); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); if (parentNode.InvariantStartsWith("umb://document/") == false) { var nodeContextId = default(int?); - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). if (int.TryParse(_requestAccessor.GetQueryStringValue("id"), out var currentId) == true) @@ -98,7 +98,7 @@ public IEnumerable GetItems(Dictionary config) } else if (UdiParser.TryParse(parentNode, out GuidUdi udi) == true && udi.Guid != Guid.Empty) { - startNode = _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetById(preview, udi.Guid); + startNode = umbracoContext.Content.GetById(preview, udi.Guid); } if (startNode != null) From 0f60d6a2fd79a24174c5c5388a38c78be40363a2 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 9 Sep 2021 12:51:11 +0100 Subject: [PATCH 73/81] Incremented version number, v3.0.0-alpha005 Bumped minimum Umbraco version dependency to v9.0.0-rc003. I'm not sure why, but Contentment v3.0.0-alpha004 didn't work with Umbraco v9.0.0-rc003. --- VERSION | 2 +- build/build-pkgs.ps1 | 2 +- .../Umbraco.Community.Contentment.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index f2ef0398..3b0d85ac 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha004 \ No newline at end of file +3.0.0-alpha005 \ No newline at end of file diff --git a/build/build-pkgs.ps1 b/build/build-pkgs.ps1 index 89b341a5..adbd7fbc 100644 --- a/build/build-pkgs.ps1 +++ b/build/build-pkgs.ps1 @@ -12,7 +12,7 @@ $nugetTitle = "Contentment for Umbraco"; $packageDescription = "Contentment, a collection of components for Umbraco."; $packageUrl = 'https://github.com/leekelleher/umbraco-contentment'; $authorName = 'Lee Kelleher'; -$minUmbracoVersion = "9.0.0-rc002"; +$minUmbracoVersion = "9.0.0-rc003"; $copyright = "" + [char]0x00A9 + " " + (Get-Date).year + " $authorName"; $tags = "umbraco"; diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 853286f4..433fc6c4 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-alpha004 + 3.0.0-alpha005 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher @@ -16,8 +16,8 @@ git - - + + From 46b8b60608a6b0da0ca82f82da216bbced450103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Wed, 29 Sep 2021 08:31:01 +0200 Subject: [PATCH 74/81] Don't run upgrades if Umbraco isn't set up #135 --- .../Composing/ContentmentComponent.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs index 7648477f..77bb2f21 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs @@ -17,21 +17,27 @@ internal sealed class ContentmentComponent : IComponent private readonly IMigrationPlanExecutor _migrationPlanExecutor; private readonly IScopeProvider _scopeProvider; private readonly IKeyValueService _keyValueService; + private readonly IRuntimeState _runtimeState; public ContentmentComponent( IMigrationPlanExecutor migrationPlanExecutor, IScopeProvider scopeProvider, - IKeyValueService keyValueService) + IKeyValueService keyValueService, + IRuntimeState runtimeState) { _migrationPlanExecutor = migrationPlanExecutor; _scopeProvider = scopeProvider; _keyValueService = keyValueService; + _runtimeState = runtimeState; } public void Initialize() { - var upgrader = new Upgrader(new ContentmentPlan()); - upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); + if (_runtimeState.Level == Cms.Core.RuntimeLevel.Upgrade || _runtimeState.Level == Cms.Core.RuntimeLevel.Run) + { + var upgrader = new Upgrader(new ContentmentPlan()); + upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); + } } public void Terminate() From 2218f95b509616003d0663b771962d5936c55177 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sat, 2 Oct 2021 12:24:56 +0100 Subject: [PATCH 75/81] Incremented version number, v3.0.0-alpha006 Bumped minimum Umbraco version dependency to v9.0.0. --- .../Umbraco.Community.Contentment.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 433fc6c4..2e5729ca 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -6,7 +6,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-alpha005 + 3.0.0-alpha006 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher @@ -16,8 +16,8 @@ git - - + + From de3e709439e0e8189b5a960c38ac4589cdad43a5 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 5 Oct 2021 09:05:36 +0100 Subject: [PATCH 76/81] Code amends to be v9.0.0 compatible --- .../UmbracoUserGroupDataListSource.cs | 8 ++-- .../DataSources/UmbracoUsersDataListSource.cs | 19 +++++---- .../TemplatedLabelConfigurationEditor.cs | 15 +++---- .../TemplatedLabelDataEditor.cs | 42 +++++++++++++++---- .../TemplatedLabelValueConverter.cs | 11 ++--- .../Extensions/PublishedElementExtensions.cs | 10 ++--- 6 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs index 221c6a43..15522e06 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs @@ -6,10 +6,10 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs index f2200e56..57295fcc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs @@ -7,21 +7,24 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { public sealed class UmbracoUsersDataListSource : IDataListSource, IDataListSourceValueConverter { + private readonly IIOHelper _ioHelper; private readonly IUserService _userService; - public UmbracoUsersDataListSource(IUserService userService) + public UmbracoUsersDataListSource(IIOHelper ioHelper, IUserService userService) { + _ioHelper = ioHelper; _userService = userService; } @@ -61,7 +64,7 @@ public IEnumerable Fields { "enableFilter", items.Count > 5 ? Constants.Values.True : Constants.Values.False }, { "items", items }, { "listType", "list" }, - { "overlayView", IOHelper.ResolveUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, + { "overlayView", _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, { "maxItems", 1 }, } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs index 38bab5cf..9e315458 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs @@ -4,15 +4,16 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { internal sealed class TemplatedLabelConfigurationEditor : ConfigurationEditor { - public TemplatedLabelConfigurationEditor() + public TemplatedLabelConfigurationEditor(IIOHelper ioHelper) : base() { var valueTypes = new[] @@ -36,7 +37,7 @@ public TemplatedLabelConfigurationEditor() Key = UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, Name = "Value type", Description = "Select the value's type. This defines how the underlying value is stored in the database.", - View = IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -44,7 +45,7 @@ public TemplatedLabelConfigurationEditor() } }); - Fields.Add(new NotesConfigurationField(@"
+ Fields.Add(new NotesConfigurationField(ioHelper, @"
Do you need help with your custom template?

Your custom template will be used to display the label on the property from the underlying value.

If you are familiar with AngularJS template syntax, you can display the value using an expression: e.g. {{ model.value }}.

@@ -62,7 +63,7 @@ public TemplatedLabelConfigurationEditor() Key = "notes", Name = "Template", Description = "Enter the AngularJS template to be displayed for the label.", - View = IOHelper.ResolveUrl(CodeEditorDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, "razor" }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs index 3c03bcc8..289636fe 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs @@ -4,8 +4,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Community.Contentment.DataEditors { @@ -16,6 +21,23 @@ public sealed class TemplatedLabelDataEditor : IDataEditor internal const string DataEditorViewPath = NotesDataEditor.DataEditorViewPath; internal const string DataEditorIcon = "icon-fa fa-codepen"; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + private readonly IIOHelper _ioHelper; + + public TemplatedLabelDataEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + { + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _ioHelper = ioHelper; + } + public string Alias => DataEditorAlias; public EditorType Type => EditorType.PropertyValue; @@ -32,13 +54,16 @@ public sealed class TemplatedLabelDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new TemplatedLabelConfigurationEditor(); + public IConfigurationEditor GetConfigurationEditor() => new TemplatedLabelConfigurationEditor(_ioHelper); public IDataValueEditor GetValueEditor() { - return new DataValueEditor + return new DataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } @@ -51,11 +76,14 @@ public IDataValueEditor GetValueEditor(object configuration) hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } - return new DataValueEditor + return new DataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) { Configuration = configuration, HideLabel = hideLabel, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs index 9bf0bbe6..057ba72c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs @@ -5,11 +5,12 @@ using System; using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using UmbConstants = Umbraco.Core.Constants; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs index f1e8b93a..deaee297 100644 --- a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs +++ b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs @@ -6,10 +6,10 @@ using System; using System.Linq.Expressions; using System.Reflection; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Embedded; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Infrastructure.ModelsBuilder; -namespace Umbraco.Web +namespace Umbraco.Extensions { public static class PublishedElementExtensions { @@ -21,13 +21,13 @@ public static bool HasValueFor(this TModel model, Expression(TModel model, Expression> property) { try { var assembly = typeof(ApiVersion).Assembly; - var type = assembly.GetType("Umbraco.Web.PublishedElementExtensions"); + var type = assembly.GetType("Umbraco.Extensions.PublishedElementExtensions"); var method = type.GetMethod(nameof(GetAlias), BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod); var generic = method.MakeGenericMethod(typeof(TModel), typeof(TValue)); return generic.Invoke(null, new object[] { model, property }) as string; From 5c2a922496c0bb78af578718fc3b457fc2585854 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 19 Sep 2021 20:26:28 +0100 Subject: [PATCH 77/81] [WIP] Multi-targeting .NET 4.7.2 and 5.0+ --- .github/README.md | 4 +- .github/ROADMAP.md | 10 +- VERSION | 2 +- build/_nuget-post-install.targets | 35 ++++ build/build-assets.ps1 | 21 +- build/build-pkgs.ps1 | 11 +- build/manifest-nuget-core.nuspec | 10 +- build/manifest-nuget-web.nuspec | 1 + docs/README.md | 3 + docs/telemetry.md | 23 +++ docs/tree-dashboard.md | 53 +++++ src/.editorconfig | 8 +- src/Umbraco.Community.Contentment.sln | 2 + .../Composing/CompositionExtensions.cs | 2 + .../Composing/ContentmentComponent.cs | 46 ++++- .../Composing/ContentmentComposer.cs | 43 +++++ .../ContentmentListItemCollectionBuilder.cs | 10 +- .../Composing/UmbracoBuilderExtensions.cs | 36 ++++ .../Configuration/ContentmentSettings.cs | 16 ++ .../Configuration/ContentmentVersion.cs | 6 +- .../Constants.cs | 8 +- .../Core/DictionaryExtensions.cs | 9 + .../Trees/TreeCollectionBuilderExtensions.cs | 2 + .../Core/Trees/TreeCollectionExtensions.cs | 63 ++++++ .../Core/Xml/UmbracoXPathPathSyntaxParser.cs | 2 + .../Buttons/ButtonsDataListEditor.cs | 27 ++- .../Bytes/BytesConfigurationEditor.cs | 12 +- .../DataEditors/Bytes/BytesDataEditor.cs | 24 ++- .../DataEditors/Bytes/BytesValueConverter.cs | 6 + .../CheckboxListDataListEditor.cs | 4 + .../CodeEditorConfigurationEditor.cs | 26 ++- .../CodeEditor/CodeEditorDataEditor.cs | 68 ++++++- .../CodeEditor/CodeEditorValueConverter.cs | 6 + .../ConfigurationEditorDataEditor.cs | 8 +- .../ConfigurationEditorModel.cs | 4 + .../ConfigurationEditorUtility.cs | 26 ++- .../ContentBlocks/ContentBlockPreviewModel.cs | 5 + .../ContentBlocks/ContentBlockPreviewView.cs | 52 +++++ .../ContentBlocksApiController.cs | 182 +++++++++++++++++- .../ContentBlocksConfigurationEditor.cs | 28 ++- .../ContentBlocks/ContentBlocksDataEditor.cs | 84 +++++++- .../ContentBlocksDataValueEditor.cs | 40 +++- .../ContentBlocksTypesConfigurationField.cs | 21 +- .../ContentBlocksValueConverter.cs | 10 +- .../ContentBlocks/ContentBlocksViewHelper.cs | 2 + .../ContentBlocks/ContentTypeCacheHelper.cs | 7 + .../DisplayModes/BlocksDisplayMode.cs | 16 +- .../DisplayModes/ListDisplayMode.cs | 15 +- .../DisplayModes/StackDisplayMode.cs | 4 + .../DataList/DataListApiController.cs | 22 ++- .../DataList/DataListConfigurationEditor.cs | 24 ++- .../DataList/DataListDataEditor.cs | 65 ++++++- .../DataList/DataListValueConverter.cs | 6 + .../DataSources/CountriesDataListSource.cs | 5 + .../DataSources/CurrenciesDataListSource.cs | 5 + .../DataSources/EnumDataListSource.cs | 81 +++++++- .../DataSources/ExamineDataListSource.cs | 63 ++++-- .../DataSources/JsonDataListSource.cs | 66 ++++++- .../DataSources/LanguagesDataListSource.cs | 5 + .../DataSources/NumberRangeDataListSource.cs | 22 ++- .../PhysicalFileSystemDataSource.cs | 48 ++++- .../DataList/DataSources/SqlDataListSource.cs | 76 ++++++-- .../TextDelimitedDataListSource.cs | 54 +++++- .../DataSources/TimeZoneDataListSource.cs | 4 + .../UmbracoContentDataListSource.cs | 56 +++++- .../UmbracoContentPropertiesDataListSource.cs | 22 ++- .../UmbracoContentTypesDataListSource.cs | 24 ++- .../UmbracoContentXPathDataListSource.cs | 52 ++++- .../UmbracoDictionaryDataListSource.cs | 16 +- .../UmbracoEntityDataListSource.cs | 43 ++++- .../UmbracoImageCropDataListSource.cs | 21 +- .../UmbracoLanguagesDataListSource.cs | 6 + .../UmbracoMemberGroupDataListSource.cs | 7 + .../UmbracoMembersDataListSource.cs | 45 ++++- .../DataSources/UmbracoTagsDataListSource.cs | 12 +- .../UmbracoUserGroupDataListSource.cs | 7 + .../DataSources/UmbracoUsersDataListSource.cs | 17 +- .../DataSources/UserDefinedDataListSource.cs | 33 +++- .../DataList/DataSources/XmlDataListSource.cs | 67 ++++++- ...XmlSitemapChangeFrequencyDataListSource.cs | 5 + .../XmlSitemapPriorityDataListSource.cs | 4 + .../uCssClassNameDataListSource.cs | 24 ++- .../DropdownListDataListEditor.cs | 17 +- .../IconPickerConfigurationEditor.cs | 10 +- .../IconPicker/IconPickerDataEditor.cs | 25 ++- .../IconPicker/IconPickerValueConverter.cs | 6 + .../ItemPicker/ItemPickerDataListEditor.cs | 27 ++- .../Notes/NotesConfigurationEditor.cs | 10 +- .../Notes/NotesConfigurationField.cs | 11 +- .../DataEditors/Notes/NotesDataEditor.cs | 52 ++++- .../DataEditors/Notes/NotesValueConverter.cs | 6 + .../NumberInputConfigurationEditor.cs | 10 +- .../NumberInput/NumberInputDataEditor.cs | 29 ++- .../NumberInput/NumberInputValueConverter.cs | 6 + .../RadioButtonListDataListEditor.cs | 4 + .../ReadOnly/ReadOnlyDataValueEditor.cs | 16 ++ .../RenderMacroConfigurationEditor.cs | 10 +- .../RenderMacro/RenderMacroDataEditor.cs | 56 +++++- .../RenderMacro/RenderMacroValueConverter.cs | 6 + .../DataEditors/Tags/TagsDataListEditor.cs | 4 + .../TemplatedLabelConfigurationEditor.cs | 15 +- .../TemplatedLabelDataEditor.cs | 52 ++++- .../TemplatedLabelValueConverter.cs | 8 + .../TemplatedListDataListEditor.cs | 21 +- .../TextInput/TextInputConfigurationEditor.cs | 19 +- .../TextInput/TextInputDataEditor.cs | 38 +++- .../TextInput/TextInputValueConverter.cs | 6 + .../AllowClearConfigurationField.cs | 4 + .../DisableSortingConfigurationField.cs | 4 + .../EnableDevModeConfigurationField.cs | 4 + .../EnableFilterConfigurationField.cs | 4 + .../HideLabelConfigurationField.cs | 4 + .../HtmlAttributesConfigurationField.cs | 10 +- .../MaxItemsConfigurationField.cs | 10 +- .../ShowDescriptionsConfigurationField.cs | 4 + .../ShowIconsConfigurationField.cs | 4 + .../DataEditors/_/IContentmentEditorItem.cs | 4 + .../DataEditors/_/IContentmentListItem.cs | 4 + .../Migrations/ContentmentPlan.cs | 4 + .../Install/RegisterUmbracoPackageEntry.cs | 23 ++- ...tmentServerVariablesParsingNotification.cs | 39 ++++ .../ContentmentTelemetryNotification.cs | 130 +++++++++++++ ...tUmbracoApplicationStartingNotification.cs | 41 ++++ .../Polyfill/IHostingEnvironment.cs | 30 +++ .../Polyfill/IIOHelper.cs | 29 +++ .../PublishedSnapshotAccessorExtensions.cs | 20 ++ .../Polyfill/README.md | 5 + .../Polyfill/StringExtensions.cs | 22 +++ .../Polyfill/UdiParser.cs | 20 ++ .../UmbracoContextAccessorExtensions.cs | 20 ++ .../Telemetry/CompositionExtensions.cs | 2 + .../ContentmentTelemetryComponent.cs | 3 +- .../Trees/CompositionExtensions.cs | 2 + .../Trees/ContentmentTreeController.cs | 49 +++++ .../Umbraco.Community.Contentment.csproj | 21 +- .../EnumDataListSourceApiController.cs | 39 +++- .../Web/Extensions/HtmlHelperExtensions.cs | 2 + .../PublishedContentTypeExtensions.cs | 7 + .../Extensions/PublishedElementExtensions.cs | 9 + .../DetachedPublishedElement.cs | 4 + .../DetachedPublishedProperty.cs | 5 + .../PublishedContentContractResolver.cs | 81 ++++++++ .../backoffice/contentment/index.html | 24 +-- .../Web/UI/backoffice.js | 25 --- src/_vars.ps1 | 2 +- 145 files changed, 2961 insertions(+), 327 deletions(-) create mode 100644 build/_nuget-post-install.targets create mode 100644 docs/tree-dashboard.md create mode 100644 src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs create mode 100644 src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs create mode 100644 src/Umbraco.Community.Contentment/Core/Trees/TreeCollectionExtensions.cs create mode 100644 src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsingNotification.cs create mode 100644 src/Umbraco.Community.Contentment/Notifications/ContentmentTelemetryNotification.cs create mode 100644 src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs create mode 100644 src/Umbraco.Community.Contentment/Polyfill/IHostingEnvironment.cs create mode 100644 src/Umbraco.Community.Contentment/Polyfill/IIOHelper.cs create mode 100644 src/Umbraco.Community.Contentment/Polyfill/PublishedSnapshotAccessorExtensions.cs create mode 100644 src/Umbraco.Community.Contentment/Polyfill/README.md create mode 100644 src/Umbraco.Community.Contentment/Polyfill/StringExtensions.cs create mode 100644 src/Umbraco.Community.Contentment/Polyfill/UdiParser.cs create mode 100644 src/Umbraco.Community.Contentment/Polyfill/UmbracoContextAccessorExtensions.cs diff --git a/.github/README.md b/.github/README.md index 6c4bc748..e488d6e4 100644 --- a/.github/README.md +++ b/.github/README.md @@ -46,8 +46,8 @@ Downloads are available on the [releases page](https://github.com/leekelleher/um _**Please note...**_ -- v3.x has been developed against **Umbraco v9.0-RC001** and will support that version and above. -- v2.x has been developed against **Umbraco v8.14.0** and will support that and future Umbraco v8.x releases. +- v3.x has been developed against **Umbraco v8.17.0** and **Umbraco v9.0.0** and will support those versions and above. +- v2.x has been developed against **Umbraco v8.14.0** and it will still work on current Umbraco v8.x releases. - v1.x has been developed against **Umbraco v8.6.1**, it will still work on current Umbraco v8.x releases. With Contentment v3.x (Umbraco v9.0 / .NET 5), you can only install a package from the [NuGet package repository](https://www.nuget.org/packages/Our.Umbraco.Community.Contentment). For previous Contentment versions, the package can be installed from either [Our Umbraco](https://our.umbraco.com/packages/backoffice-extensions/contentment/) or NuGet package repositories, or build manually from the source-code: diff --git a/.github/ROADMAP.md b/.github/ROADMAP.md index e7d46df2..ba2d658a 100644 --- a/.github/ROADMAP.md +++ b/.github/ROADMAP.md @@ -60,16 +60,16 @@ Property Editors are: - [Templated Label](https://github.com/leekelleher/umbraco-contentment/discussions/100) -### v2.3 - -- [Data List: Groups](https://github.com/leekelleher/umbraco-contentment/discussions/90) - ## v3 ### v3.0 -- A breaking-change release of latest v2.x features that compiles against Umbraco CMS v9.0 (.NET 5). +- _Under active development!_ [See my developer's journal for the latest updates.](https://github.com/leekelleher/umbraco-contentment/discussions/105) A breaking-change release _(following SemVer guidelines),_ of v2.x features that will compile against both Umbraco v8.17 and v9.0. + +### v3.1 + +- [Data List: Groups](https://github.com/leekelleher/umbraco-contentment/discussions/90) ## Future feature releases diff --git a/VERSION b/VERSION index e3a4f193..44401a75 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.0 \ No newline at end of file +3.0.0-beta001 \ No newline at end of file diff --git a/build/_nuget-post-install.targets b/build/_nuget-post-install.targets new file mode 100644 index 00000000..01d262e3 --- /dev/null +++ b/build/_nuget-post-install.targets @@ -0,0 +1,35 @@ + + + + + $(MSBuildThisFileDirectory)..\content\App_Plugins\Contentment\**\*.* + + + + + + + + + + + + + + + + + + + diff --git a/build/build-assets.ps1 b/build/build-assets.ps1 index 914bb28c..4d7e5bb3 100644 --- a/build/build-assets.ps1 +++ b/build/build-assets.ps1 @@ -4,26 +4,27 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. param( - [string]$SolutionDir, + [string]$TargetFramework, [string]$TargetDir, [string]$ProjectName, [string]$ProjectDir, [string]$ConfigurationName ); -. "${SolutionDir}_vars.ps1"; +$rootDir = "${ProjectDir}..\.."; +. "${rootDir}\src\_vars.ps1"; Write-Host $ConfigurationName; if ($ConfigurationName -eq 'Debug') { - Write-Host $SolutionDir; + Write-Host $TargetFramework; Write-Host $TargetDir; Write-Host $ProjectName; Write-Host $ProjectDir; Write-Host $TargetDevWebsite; } -$targetFolder = "${SolutionDir}..\build\assets"; +$targetFolder = "${rootDir}\build\assets"; # If it already exists, delete it if (Test-Path -Path $targetFolder) { @@ -32,9 +33,11 @@ if (Test-Path -Path $targetFolder) { # Copy DLL / PDB $binFolder = "${targetFolder}\bin"; - if (!(Test-Path -Path $binFolder)) {New-Item -Path $binFolder -Type Directory;} -Copy-Item -Path "${TargetDir}${ProjectName}.*" -Destination $binFolder; +Copy-Item -Path "${ProjectDir}\bin\${ConfigurationName}\net472\${ProjectName}.*" -Destination $binFolder; +$net50Folder = "${targetFolder}\net50"; +if (!(Test-Path -Path $net50Folder)) {New-Item -Path $net50Folder -Type Directory;} +Copy-Item -Path "${ProjectDir}\bin\${ConfigurationName}\net50\${ProjectName}.*" -Destination $net50Folder; # Copy package front-end files assets $pluginFolder = "${targetFolder}\App_Plugins\Contentment\"; @@ -61,14 +64,14 @@ foreach($razorFile in $razorFiles){ # CSS - Bundle & Minify $targetCssPath = "${pluginFolder}contentment.css"; Get-Content -Raw -Path "${ProjectDir}**\**\*.css" | Set-Content -Encoding UTF8 -Path $targetCssPath; -& "${SolutionDir}..\tools\AjaxMinifier.exe" $targetCssPath -o $targetCssPath +& "${rootDir}\tools\AjaxMinifier.exe" $targetCssPath -o $targetCssPath # JS - Bundle & Minify $targetJsPath = "${pluginFolder}contentment.js"; Get-Content -Raw -Path "${ProjectDir}**\**\*.js" | Set-Content -Encoding UTF8 -Path $targetJsPath; -& "${SolutionDir}..\tools\AjaxMinifier.exe" $targetJsPath -o $targetJsPath +& "${rootDir}\tools\AjaxMinifier.exe" $targetJsPath -o $targetJsPath # In debug mode, copy the assets over to the local dev website if ($ConfigurationName -eq 'Debug' -AND -NOT($TargetDevWebsite -eq '')) { - Copy-Item -Path "${targetFolder}\*" -Force -Recurse -Destination $TargetDevWebsite; + Copy-Item -Path "${targetFolder}\*" -Force -Recurse -Destination $TargetDevWebsite | Where { $_.FullName -NotLike "*\net50\*" }; } \ No newline at end of file diff --git a/build/build-pkgs.ps1 b/build/build-pkgs.ps1 index 5d26d696..3c239bf2 100644 --- a/build/build-pkgs.ps1 +++ b/build/build-pkgs.ps1 @@ -10,14 +10,14 @@ $nugetPackageId = 'Our.Umbraco.Community.Contentment'; $projectNamespace = 'Umbraco.Community.Contentment'; $packageName = 'Contentment'; $nugetTitle = "${packageName} for Umbraco"; -$packageDescription = "${packageName}, a collection of components for Umbraco 8."; +$packageDescription = "${packageName}, a collection of components for Umbraco."; $packageUrl = 'https://github.com/leekelleher/umbraco-contentment'; $iconUrl = 'https://raw.githubusercontent.com/leekelleher/umbraco-contentment/master/docs/assets/img/logo.png'; $licenseName = 'Mozilla Public License Version 2.0'; $licenseUrl = 'https://mozilla.org/MPL/2.0/'; $authorName = 'Lee Kelleher'; $authorUrl = 'https://leekelleher.com/'; -$minUmbracoVersion = 8,14,0; +$minUmbracoVersion = 8,17,0; $copyright = "Copyright " + [char]0x00A9 + " " + (Get-Date).year + " $authorName"; $tags = "umbraco"; @@ -81,7 +81,7 @@ $umbracoPackageXml.umbPackage.info.readme."#cdata-section" = $packageDescription $filesXml = $umbracoPackageXml.CreateElement("files"); -$assetFiles = Get-ChildItem -Path $assetsFolder -File -Recurse; +$assetFiles = Get-ChildItem -Path $assetsFolder -File -Recurse | Where { $_.FullName -NotLike "*\net50\*" }; foreach($assetFile in $assetFiles){ $hash = Get-FileHash -Path $assetFile.FullName -Algorithm MD5; @@ -106,8 +106,9 @@ Compress-Archive -Path "${umbFolder}\*" -DestinationPath "${artifactsFolder}\Con # Populate the NuGet package manifest Copy-Item -Path "${rootFolder}\docs\assets\img\logo.png" -Destination "${assetsFolder}\icon.png"; -& $nuget_exe pack "${buildFolder}\manifest-nuget-core.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$($minUmbracoVersion[0]).$($minUmbracoVersion[1]).$($minUmbracoVersion[2]);repositoryUrl=$packageUrl;" -& $nuget_exe pack "${buildFolder}\manifest-nuget-web.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;minUmbracoVersion=$($minUmbracoVersion[0]).$($minUmbracoVersion[1]).$($minUmbracoVersion[2]);repositoryUrl=$packageUrl;" +Copy-Item -Path "${buildFolder}\_nuget-post-install.targets" -Destination "${assetsFolder}\${nugetPackageId}.targets"; +& $nuget_exe pack "${buildFolder}\manifest-nuget-core.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;repositoryUrl=$packageUrl;" +& $nuget_exe pack "${buildFolder}\manifest-nuget-web.nuspec" -BasePath $assetsFolder -OutputDirectory $artifactsFolder -Version "$version" -Properties "id=$nugetPackageId;version=$version;title=$nugetTitle;authors=$authorName;owners=$authorName;projectUrl=$packageUrl;requireLicenseAcceptance=false;description=$packageDescription;copyright=$copyright;license=MPL-2.0;language=en;tags=$tags;repositoryUrl=$packageUrl;" # Tidy up folders diff --git a/build/manifest-nuget-core.nuspec b/build/manifest-nuget-core.nuspec index ecfd2a75..cdfd481c 100644 --- a/build/manifest-nuget-core.nuspec +++ b/build/manifest-nuget-core.nuspec @@ -17,13 +17,17 @@ - - + + + + + - + + \ No newline at end of file diff --git a/build/manifest-nuget-web.nuspec b/build/manifest-nuget-web.nuspec index 1ad4282c..80edfb9c 100644 --- a/build/manifest-nuget-web.nuspec +++ b/build/manifest-nuget-web.nuspec @@ -21,6 +21,7 @@ + \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index f7a757cf..d0fda2fe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,3 +38,6 @@ If you are unfamiliar with how to do this, then please refer to documentation, g Information about Contentment's [telemetry feature](../docs/telemetry.md). +#### Tree Dashboard + +Information about Contentment's [tree dashboard](../docs/tree-dashboard.md). diff --git a/docs/telemetry.md b/docs/telemetry.md index bbea8855..5f98a60a 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -35,6 +35,12 @@ For information about the data and analysis, please go to: { opts.DisableTelemetry = true; })` extension method in your `Startup.cs` file `ConfigureServices()` method. diff --git a/docs/tree-dashboard.md b/docs/tree-dashboard.md new file mode 100644 index 00000000..b01785c7 --- /dev/null +++ b/docs/tree-dashboard.md @@ -0,0 +1,53 @@ +Contentment for Umbraco logo + +## Contentment for Umbraco + +### Tree Dashboard + +By default, the package will display a Contentment tree item in the Settings section. Currently, this is used for promotional purposes, to give information about the package itself, newsletter sign-up, etc. + + +#### Disable tree dashboard + +If you would prefer to disable the tree dashboard completely, you can use this code snippet to disable it. + +##### For Umbraco v8 + +Code snippet to disable Contentment tree dashboard. + +Copy the C# class below. You can either save this to your `~/App_Code/` folder, or add it to your own code library. + +```csharp +using Umbraco.Core.Composing; + +namespace Our.Umbraco.Web +{ + public class DisableContentmentTreeComposer : IUserComposer + { + public void Compose(Composition composition) + { + composition.DisableContentmentTree(); + } + } +} +``` + +If you already have your own composer class, you can add the `composition.DisableContentmentTree();` line to it. + +##### For Umbraco v9+ + +Configuration to disable Contentment tree dashboard. + +In your `appsettings.json` file, add this option inside the `"Umbraco"` section, add the following. + +```json +{ + "Umbraco": { + "Contentment": { + "DisableTree": true + } + } +} +``` + +If you prefer to use a strongly-typed configuration in C# code, you can do this with the `.AddContentment(opts => { opts.DisableTree = true; })` extension method in your `Startup.cs` file `ConfigureServices()` method. diff --git a/src/.editorconfig b/src/.editorconfig index 8380e946..c548ed93 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -1,5 +1,5 @@ # This .editorconfig has been taken from Umbraco CMS, licensed under MIT. -# https://github.com/umbraco/Umbraco-CMS/blob/release-8.1.0/.editorconfig +# https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v9/dev/.editorconfig # top-most EditorConfig file root = true @@ -26,6 +26,7 @@ dotnet_naming_rule.private_members_with_underscore.severity = suggestion dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private +# dotnet_naming_symbols.private_fields.required_modifiers = abstract,async,readonly,static # all except const dotnet_naming_style.prefix_underscore.capitalization = camel_case dotnet_naming_style.prefix_underscore.required_prefix = _ @@ -37,5 +38,8 @@ csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion csharp_prefer_braces = false : none -[*.{js,less}] +[*.js] +trim_trailing_whitespace = true + +[*.less] trim_trailing_whitespace = false diff --git a/src/Umbraco.Community.Contentment.sln b/src/Umbraco.Community.Contentment.sln index 1e4d5b16..fa3ff480 100644 --- a/src/Umbraco.Community.Contentment.sln +++ b/src/Umbraco.Community.Contentment.sln @@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{5297EC84-5180-4489-B642-E3453B51330F}" ProjectSection(SolutionItems) = preProject + ..\build\_nuget-post-install.targets = ..\build\_nuget-post-install.targets ..\build\build-assets.ps1 = ..\build\build-assets.ps1 ..\build\build-pkgs.ps1 = ..\build\build-pkgs.ps1 ..\build\manifest-nuget-core.nuspec = ..\build\manifest-nuget-core.nuspec @@ -37,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentat ProjectSection(SolutionItems) = preProject ..\docs\README.md = ..\docs\README.md ..\docs\telemetry.md = ..\docs\telemetry.md + ..\docs\tree-dashboard.md = ..\docs\tree-dashboard.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Editors", "Editors", "{A5294B30-2ED5-4BBA-A2DE-A07103DAE78F}" diff --git a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs index e82dc4d1..618974a9 100644 --- a/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Composing/CompositionExtensions.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Community.Contentment.Composing; // NOTE: This extension method class is deliberately using the Umbraco namespace, @@ -23,3 +24,4 @@ public static Composition UnlockContentment(this Composition composition) } } } +#endif diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs index 9601f668..02533227 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System.Collections.Generic; using Umbraco.Community.Contentment.Migrations; using Umbraco.Core; @@ -13,9 +14,19 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Web.JavaScript; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade; +using Umbraco.Community.Contentment.Migrations; +#endif namespace Umbraco.Community.Contentment.Composing { +#if NET472 internal sealed class ContentmentComponent : IComponent { private readonly IScopeProvider _scopeProvider; @@ -55,10 +66,43 @@ private void ServerVariablesParser_Parsing(object sender, Dictionary RuntimeLevel.Install) + { + var upgrader = new Upgrader(new ContentmentPlan()); + upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); + } + } + + public void Terminate() + { } + } +#endif } diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs index 3518c021..7fbb83df 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComposer.cs @@ -3,14 +3,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Community.Contentment.DataEditors; using Umbraco.Community.Contentment.Telemetry; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Web.Runtime; +#else +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Community.Contentment.DataEditors; +using Umbraco.Community.Contentment.Notifications; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.Composing { +#if NET472 [ComposeAfter(typeof(WebInitialComposer))] [RuntimeLevel(MinLevel = RuntimeLevel.Boot)] internal sealed class ContentmentComposer : IUserComposer @@ -22,6 +33,8 @@ public void Compose(Composition composition) .Add(() => composition.TypeLoader.GetTypes()) ; + composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); if (composition.RuntimeState.Level > RuntimeLevel.Install) @@ -41,4 +54,34 @@ public void Compose(Composition composition) } } } +#else + internal sealed class ContentmentComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder + .Services + .Configure(builder.Config.GetSection(Constants.Internals.ConfigurationSection)) + ; + + builder + .WithCollectionBuilder() + .Add(() => builder.TypeLoader.GetTypes()) + ; + + builder.Services.AddUnique(); + + builder + .Components() + .Append() + ; + + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + ; + } + } +#endif } diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs index 1687ce14..1e34290b 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentListItemCollectionBuilder.cs @@ -7,8 +7,13 @@ using System.Collections.Generic; using System.ComponentModel; using Umbraco.Community.Contentment.DataEditors; +#if NET472 using Umbraco.Core; using Umbraco.Core.Composing; +#else +using Umbraco.Cms.Core.Composing; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.Composing { @@ -23,8 +28,11 @@ public sealed class ContentmentListItemCollectionBuilder public sealed class ContentmentListItemCollection : BuilderCollectionBase { private readonly Dictionary _lookup; - +#if NET472 public ContentmentListItemCollection(IEnumerable items) +#else + public ContentmentListItemCollection(Func> items) +#endif : base(items) { _lookup = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs new file mode 100644 index 00000000..fc864726 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Composing/UmbracoBuilderExtensions.cs @@ -0,0 +1,36 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 == false +using System; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Community.Contentment; +using Umbraco.Community.Contentment.Composing; + +namespace Umbraco.Extensions +{ + public static partial class UmbracoBuilderExtensions + { + public static IUmbracoBuilder AddContentment( + this IUmbracoBuilder builder, + Action settings = default, + Action listItems = default) + { + if (settings is not null) + { + _ = builder.Services.PostConfigure(settings); + } + + if (listItems is not null) + { + listItems(builder.WithCollectionBuilder()); + } + + return builder; + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs b/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs new file mode 100644 index 00000000..a82588e4 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Configuration/ContentmentSettings.cs @@ -0,0 +1,16 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 == false +namespace Umbraco.Community.Contentment +{ + public class ContentmentSettings + { + public bool DisableTree { get; set; } = false; + + public bool DisableTelemetry { get; set; } = false; + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs b/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs index 25688e59..4e41099b 100644 --- a/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs +++ b/src/Umbraco.Community.Contentment/Configuration/ContentmentVersion.cs @@ -10,10 +10,14 @@ using System; using System.Reflection; +#if NET472 using Semver; using Umbraco.Core; +#else +using Umbraco.Cms.Core.Semver; +#endif -namespace Umbraco.Community.Contentment.Configuration +namespace Umbraco.Community.Contentment { public static class ContentmentVersion { diff --git a/src/Umbraco.Community.Contentment/Constants.cs b/src/Umbraco.Community.Contentment/Constants.cs index 82fa86be..5d13c838 100644 --- a/src/Umbraco.Community.Contentment/Constants.cs +++ b/src/Umbraco.Community.Contentment/Constants.cs @@ -28,6 +28,8 @@ internal static partial class Internals internal const string BackOfficePathRoot = PackagePathRoot + "backoffice/" + TreeAlias + "/"; internal const string TreeAlias = ProjectAlias; + + internal const string ConfigurationSection = "Umbraco:Contentment"; } internal static partial class Conventions @@ -109,7 +111,11 @@ internal static partial class Package public const string LicenseUrl = "https://mozilla.org/MPL/2.0/"; - public static readonly System.Version MinimumSupportedUmbracoVersion = new System.Version(8, 6, 1); +#if NET472 + public static readonly System.Version MinimumSupportedUmbracoVersion = new System.Version(8, 17, 0); +#else + public static readonly System.Version MinimumSupportedUmbracoVersion = new System.Version(9, 0, 0); +#endif public const string RepositoryUrl = "https://github.com/leekelleher/umbraco-contentment"; } diff --git a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs index e9ffae3f..30bc37c1 100644 --- a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs +++ b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs @@ -5,7 +5,11 @@ using System.Collections.Generic; +#if NET472 namespace Umbraco.Core +#else +namespace Umbraco.Extensions +#endif { internal static class DictionaryExtensions { @@ -13,6 +17,11 @@ public static TValueOut GetValueAs(this IDictionary(this TreeCollection collection) + where TController : TreeControllerBase + { + var controllerType = typeof(TController); + var type = typeof(BuilderCollectionBase); + + // https://github.com/umbraco/Umbraco-CMS/blob/release-9.0.0/src/Umbraco.Core/Composing/BuilderCollectionBase.cs#L14 + var field = type.GetField("_items", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) + { + return collection; + } + + var trees = (Tree[])field.GetValue(collection); + if (trees == null) + { + return collection; + } + + if (typeof(TreeControllerBase).IsAssignableFrom(controllerType) == false) + { + throw new ArgumentException($"Type {controllerType} does not inherit from {nameof(TreeControllerBase)}."); + } + + var idx = Array.FindIndex(trees, x => x.TreeControllerType == controllerType); + if (idx > -1) + { + var tmp = new Tree[trees.Length - 1]; + + if (idx > 0) + { + Array.Copy(trees, 0, tmp, 0, idx); + } + else + { + Array.Copy(trees, idx + 1, tmp, idx, trees.Length - idx - 1); + } + + field.SetValue(collection, tmp); + } + + return collection; + } + } +} + +#endif diff --git a/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs index da52d87c..8d623157 100644 --- a/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System; using System.Collections.Generic; using System.Reflection; @@ -32,3 +33,4 @@ public static string ParseXPathQuery( } } } +#endif diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs index 4ac78b4a..76a50cf4 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs @@ -3,10 +3,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using UmbConstants = Umbraco.Core.Constants; +#else +using System.Collections.Generic; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +24,14 @@ public sealed class ButtonsDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "buttons.html"; - public string Name => "Buttons"; + private readonly IIOHelper _ioHelper; + + public ButtonsDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + + public string Name => "Buttons"; public string Description => "Select multiple values from a group of buttons."; @@ -29,14 +46,14 @@ public sealed class ButtonsDataListEditor : IDataListEditor Key = "defaultIcon", Name = "Default icon", Description = "Select an icon to be displayed as the default icon,
(for when no icon is available).", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), + View = _ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), }, new ConfigurationField { Key = "size", Name = "Size", Description = "Select the button size. By default this is set to 'medium'.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -54,7 +71,7 @@ public sealed class ButtonsDataListEditor : IDataListEditor Key = "labelStyle", Name = "Label style", Description = "Select the style of the button's label.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -80,7 +97,7 @@ public sealed class ButtonsDataListEditor : IDataListEditor public Dictionary DefaultValues => new Dictionary { - { "defaultIcon", Core.Constants.Icons.DefaultIcon }, + { "defaultIcon", UmbConstants.Icons.DefaultIcon }, { "labelStyle", "both" }, }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs index 303284da..83e6813f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs @@ -4,8 +4,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -16,14 +22,14 @@ internal sealed class BytesConfigurationEditor : ConfigurationEditor internal const string Format = "format"; internal const string Kilo = "kilo"; - public BytesConfigurationEditor() + public BytesConfigurationEditor(IIOHelper ioHelper) { Fields.Add(new ConfigurationField { Key = Kilo, Name = "Kilobytes?", Description = "How many bytes do you prefer in your kilobyte?", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -42,7 +48,7 @@ public BytesConfigurationEditor() Key = Decimals, Name = "Decimal places", Description = "How many decimal places would you like?", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/slider/slider.html"), + View = ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/slider/slider.html"), Config = new Dictionary { { "initVal1", 2 }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs index d9a0e921..ae186e87 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesDataEditor.cs @@ -3,8 +3,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -23,10 +29,22 @@ public sealed class BytesDataEditor : DataEditor internal const string DataEditorViewPath = "readonlyvalue"; internal const string DataEditorIcon = "icon-binarycode"; - public BytesDataEditor(ILogger logger) + private readonly IIOHelper _ioHelper; + +#if NET472 + public BytesDataEditor(IIOHelper ioHelper, ILogger logger) : base(logger) - { } + { + _ioHelper = ioHelper; + } +#else + public BytesDataEditor(IIOHelper ioHelper, IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) + { + _ioHelper = ioHelper; + } +#endif - protected override IConfigurationEditor CreateConfigurationEditor() => new BytesConfigurationEditor(); + protected override IConfigurationEditor CreateConfigurationEditor() => new BytesConfigurationEditor(_ioHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs index 9808efc7..34b41f50 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesValueConverter.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs index c44a2933..105aa372 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CheckboxList/CheckboxListDataListEditor.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs index e3df5f7f..53555f02 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs @@ -5,9 +5,17 @@ using System.Collections.Generic; using System.IO; +#if NET472 using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -20,11 +28,13 @@ internal sealed class CodeEditorConfigurationEditor : ConfigurationEditor internal const string MinLines = "minLines"; internal const string MaxLines = "maxLines"; - public CodeEditorConfigurationEditor() + public CodeEditorConfigurationEditor( + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) : base() { var targetPath = "~/umbraco/lib/ace-builds/src-min-noconflict/"; - var aceEditorPath = IOHelper.MapPath(targetPath); + var aceEditorPath = hostingEnvironment.MapPathWebRoot(targetPath); if (Directory.Exists(aceEditorPath) == true) { @@ -58,7 +68,7 @@ public CodeEditorConfigurationEditor() Key = Mode, Name = "Language mode", Description = "Select the programming language mode. The default mode is 'Razor'.", - View = IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -75,7 +85,7 @@ public CodeEditorConfigurationEditor() Key = Theme, Name = nameof(Theme), Description = "Set the theme for the code editor. The default theme is 'Chrome'.", - View = IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -86,7 +96,7 @@ public CodeEditorConfigurationEditor() if (modes.Count > 0 || themes.Count > 0) { - Fields.Add(new NotesConfigurationField($@"
+ Fields.Add(new NotesConfigurationField(ioHelper, $@"
Would you like to add more language modes and themes?

This property editor makes use of AWS Cloud 9's Ace editor library that is distributed with Umbraco. By default, Umbraco ships a streamlined set of programming language modes and themes.

If you would like to add more modes and themes, you can do this by downloading the latest pre-packaged version of the Ace editor and copy any of the mode-* or theme-* files from the src-min-noconflict folder over to the {targetPath} folder in this Umbraco installation.

@@ -102,7 +112,7 @@ public CodeEditorConfigurationEditor() Key = FontSize, Name = "Font size", Description = @"Set the font size. The value must be a valid CSS font-size value. The default size is 'small'.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] { @@ -149,7 +159,7 @@ public CodeEditorConfigurationEditor() Key = MinLines, Name = "Minimum lines", Description = "Set the minimum number of lines that the editor will be. The default is 12 lines.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath) + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath) }); DefaultConfiguration.Add(MaxLines, 30); @@ -158,7 +168,7 @@ public CodeEditorConfigurationEditor() Key = MaxLines, Name = "Maximum lines", Description = "Set the maximum number of lines that the editor can be. If left empty, the editor will not auto-scale.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath) + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath) }); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs index bc0331de..c50cc19b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs @@ -3,9 +3,22 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; using Umbraco.Web.PropertyEditors; +#else +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -24,12 +37,61 @@ internal sealed class CodeEditorDataEditor : DataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "code-editor.html"; internal const string DataEditorIcon = "icon-fa fa-code"; - public CodeEditorDataEditor(ILogger logger) + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + +#if NET472 + public CodeEditorDataEditor( + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper, + ILogger logger) : base(logger) - { } + { + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; + } +#else + private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + + public CodeEditorDataEditor( + IDataValueEditorFactory dataValueEditorFactory, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) + : base(dataValueEditorFactory) + { + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; + _dataTypeService = dataTypeService; + _localizationService = localizationService; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + } +#endif - protected override IConfigurationEditor CreateConfigurationEditor() => new CodeEditorConfigurationEditor(); + protected override IConfigurationEditor CreateConfigurationEditor() => new CodeEditorConfigurationEditor( + _hostingEnvironment, + _ioHelper); +#if NET472 protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor(Attribute); +#else + protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor( + Attribute, + _localizedTextService, + _shortStringHelper, + _jsonSerializer, + _ioHelper); +#endif } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs index 2c331f35..7771fdf8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs index e5f9dbb1..5dc2c356 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorDataEditor.cs @@ -3,6 +3,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 +using UmbConstants = Umbraco.Core.Constants; +#else +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif + namespace Umbraco.Community.Contentment.DataEditors { internal sealed class ConfigurationEditorDataEditor @@ -11,6 +17,6 @@ internal sealed class ConfigurationEditorDataEditor internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Configuration Editor"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "configuration-editor.html"; internal const string DataEditorOverlayViewPath = Constants.Internals.EditorsPathRoot + "configuration-editor.overlay.html"; - internal const string DataEditorIcon = Core.Constants.Icons.Macro; + internal const string DataEditorIcon = UmbConstants.Icons.Macro; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs index 78505d1a..0005fcb1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs @@ -7,7 +7,11 @@ using System.ComponentModel; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs index 5e248f66..d34c2785 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorUtility.cs @@ -8,8 +8,18 @@ using System.ComponentModel; using System.Linq; using Umbraco.Community.Contentment.Composing; +#if NET472 using Umbraco.Core; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -40,13 +50,13 @@ public T GetConfigurationEditor(string key) return default; } - public ConfigurationEditorModel GetConfigurationEditorModel(bool ignoreFields = false) + public ConfigurationEditorModel GetConfigurationEditorModel(IShortStringHelper shortStringHelper, bool ignoreFields = false) where T : IContentmentEditorItem { - return GetConfigurationEditorModel(GetConfigurationEditor(typeof(T).GetFullNameWithAssembly()), ignoreFields); + return GetConfigurationEditorModel(GetConfigurationEditor(typeof(T).GetFullNameWithAssembly()), shortStringHelper, ignoreFields); } - public ConfigurationEditorModel GetConfigurationEditorModel(T item, bool ignoreFields = false) + public ConfigurationEditorModel GetConfigurationEditorModel(T item, IShortStringHelper shortStringHelper, bool ignoreFields = false) where T : IContentmentEditorItem { var type = item.GetType(); @@ -58,9 +68,9 @@ public ConfigurationEditorModel GetConfigurationEditorModel(T item, bool igno return new ConfigurationEditorModel { Key = type.GetFullNameWithAssembly(), - Name = item.Name ?? type.Name.SplitPascalCasing(), + Name = item.Name ?? type.Name.SplitPascalCasing(shortStringHelper), Description = item.Description, - Icon = item.Icon ?? Core.Constants.Icons.DefaultIcon, + Icon = item.Icon ?? UmbConstants.Icons.DefaultIcon, Group = item.Group, Fields = fields, DefaultValues = item.DefaultValues, @@ -68,8 +78,8 @@ public ConfigurationEditorModel GetConfigurationEditorModel(T item, bool igno }; } - public IEnumerable GetConfigurationEditorModels(bool ignoreFields = false) - where T : IContentmentEditorItem + public IEnumerable GetConfigurationEditorModels(IShortStringHelper shortStringHelper, bool ignoreFields = false) + where T : IContentmentEditorItem { var models = new List(); @@ -77,7 +87,7 @@ public IEnumerable GetConfigurationEditorModels(boo { if (item is T editorItem) { - models.Add(GetConfigurationEditorModel(editorItem, ignoreFields)); + models.Add(GetConfigurationEditorModel(editorItem, shortStringHelper, ignoreFields)); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs index 4bf97bf9..463132c8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs @@ -3,8 +3,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Models; +#else +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index f8851469..2c7faede 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -4,12 +4,23 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using System.Web.Mvc; using Umbraco.Community.Contentment.DataEditors; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; +#else +using Microsoft.AspNetCore.Mvc.Rendering; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Community.Contentment.DataEditors; +using Umbraco.Extensions; +#endif +#if NET472 namespace Umbraco.Web.Mvc +#else +namespace Umbraco.Cms.Web.Common.Views +#endif { public abstract class ContentBlockPreviewView : ContentBlockPreviewView @@ -20,6 +31,7 @@ public abstract class ContentBlockPreviewView(string key, Action action) @@ -52,5 +64,45 @@ void setProperty(string key, Action action) base.SetViewData(viewData); } +#else + public override ViewContext ViewContext + { + get => base.ViewContext; + set => base.ViewContext = SetViewData(value); + } + + protected ViewContext SetViewData(ViewContext viewCtx) + { + void setProperty(string key, Action action) + { + if (viewCtx.ViewData.TryGetValueAs(key, out T value) == true) + { + action(value); + } + } + + var model = new ContentBlockPreviewModel(); + + setProperty("content", (x) => model.Content = x); + setProperty("element", (x) => model.Element = x); + setProperty("elementIndex", (x) => model.ElementIndex = x); + setProperty("contentIcon", (x) => model.ContentTypeIcon = x); + setProperty("elementIcon", (x) => model.ElementTypeIcon = x); + + if (model.Element == null && viewCtx.ViewData.Model is TPublishedElement element) + { + model.Element = element; + } + + if (model.Content == null && UmbracoContext?.PublishedRequest?.PublishedContent is TPublishedContent content) + { + model.Content = content; + } + + viewCtx.ViewData.Model = model; + + return viewCtx; + } +#endif } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs index 42503c2b..7cc451bd 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System; using System.Collections.Generic; using System.Net; @@ -15,9 +16,32 @@ using Umbraco.Web.Editors; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; +#else +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Community.Contentment.Web.PublishedCache; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { +#if NET472 [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController { @@ -83,7 +107,7 @@ public HttpResponseMessage GetPreviewMarkup([FromBody] JObject item, int element viewData.Add(nameof(elementIcon), elementIcon); } - var markup = default(string); + string markup; try { @@ -107,4 +131,160 @@ public HttpResponseMessage GetPreviewMarkup([FromBody] JObject item, int element return Request.CreateResponse(HttpStatusCode.OK, new { elementKey, markup }); } } +#else + [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] + public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController + { + private readonly ILogger _logger; + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly IContentTypeService _contentTypeService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly IRazorViewEngine _viewEngine; + private readonly ITempDataProvider _tempDataProvider; + + public ContentBlocksApiController( + ILogger logger, + IPublishedModelFactory publishedModelFactory, + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IModelMetadataProvider modelMetadataProvider, + IRazorViewEngine viewEngine, + ITempDataProvider tempDataProvider) + { + _logger = logger; + _publishedModelFactory = publishedModelFactory; + _contentTypeService = contentTypeService; + _umbracoContextAccessor = umbracoContextAccessor; + _modelMetadataProvider = modelMetadataProvider; + _viewEngine = viewEngine; + _tempDataProvider = tempDataProvider; + } + + [HttpPost] + public ActionResult GetPreviewMarkup([FromBody] JObject item, int elementIndex, Guid elementKey, int contentId) + { + var preview = true; + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + + var content = umbracoContext.Content.GetById(true, contentId); + if (content == null) + { + _logger.LogDebug($"Unable to retrieve content for ID '{contentId}', it is most likely a new unsaved page."); + } + + var element = default(IPublishedElement); + var block = item.ToObject(); + if (block != null && block.ElementType.Equals(Guid.Empty) == false) + { + if (ContentTypeCacheHelper.TryGetAlias(block.ElementType, out var alias, _contentTypeService) == true) + { + var contentType = umbracoContext.PublishedSnapshot.Content.GetContentType(alias); + if (contentType != null && contentType.IsElement == true) + { + var properties = new List(); + + foreach (var thing in block.Value) + { + var propType = contentType.GetPropertyType(thing.Key); + if (propType != null) + { + properties.Add(new DetachedPublishedProperty(propType, null, thing.Value, preview)); + } + } + + element = _publishedModelFactory.CreateModel(new DetachedPublishedElement(block.Key, contentType, properties)); + } + } + } + + var viewData = new ViewDataDictionary(_modelMetadataProvider, new ModelStateDictionary()) + { + Model = element, + [nameof(content)] = content, + [nameof(element)] = element, + [nameof(elementIndex)] = elementIndex, + + }; + + if (ContentTypeCacheHelper.TryGetIcon(content.ContentType.Alias, out var contentIcon, _contentTypeService) == true) + { + viewData.Add(nameof(contentIcon), contentIcon); + } + + if (ContentTypeCacheHelper.TryGetIcon(element.ContentType.Alias, out var elementIcon, _contentTypeService) == true) + { + viewData.Add(nameof(elementIcon), elementIcon); + } + + string markup; + + try + { + markup = RenderPartialViewToString(element.ContentType.Alias, viewData); + } + catch (InvalidCastException icex) + { + // NOTE: This type of exception happens on a new (unsaved) page, when the context becomes the parent page, + // and the preview view is strongly typed to the current page's model type. + markup = "

Unable to render the preview until the page has been saved.

"; + + _logger.LogError(icex, "Error rendering preview view."); + } + catch (Exception ex) + { + markup = $"
{ex}
"; + + _logger.LogError(ex, "Error rendering preview view."); + } + + return new ObjectResult(new { elementKey, markup }); + } + + // HACK: [v9] [LK:2021-05-13] Got it working. Future rewrite, make nicer. + // The following code has been hacked and butchered from: + // https://github.com/aspnet/Entropy/blob/master/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs + // https://gist.github.com/ahmad-moussawi/1643d703c11699a6a4046e57247b4d09 + private string RenderPartialViewToString(string viewName, ViewDataDictionary viewData) + { + IView view = default; + + // TODO: [v9] [LK:2021-05-13] Implement the custom partial-view paths. + // e.g. "~/Views/Partials/Blocks/{0}.cshtml", "~/Views/Partials/Blocks/Default.cshtml", "~/App_Plugins/Contentment/render/ContentBlockPreview.cshtml" + + var getViewResult = _viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true); + if (getViewResult.Success) + { + view = getViewResult.View; + } + + var actionContext = new ActionContext(HttpContext, new RouteData(), new ActionDescriptor()); + var findViewResult = _viewEngine.FindView(actionContext, viewName, isMainPage: true); + if (findViewResult.Success) + { + view = findViewResult.View; + } + + if (view == default) + { + var messages = new List { $"Unable to find view '{viewName}'. The following locations were searched:" }; + messages.AddRange(getViewResult.SearchedLocations); + messages.AddRange(findViewResult.SearchedLocations); + + var errorMessage = string.Join(Environment.NewLine, messages); + + throw new InvalidOperationException(errorMessage); + } + + using var output = new StringWriter(); + + var tempDataDictionary = new TempDataDictionary(actionContext.HttpContext, _tempDataProvider); + var viewContext = new ViewContext(actionContext, view, viewData, tempDataDictionary, output, new HtmlHelperOptions()); + + view.RenderAsync(viewContext).GetAwaiter().GetResult(); + + return output.ToString(); + } + } +#endif } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs index 5f129175..ecb08c1f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs @@ -7,11 +7,21 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Core.Strings; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -20,6 +30,7 @@ internal sealed class ContentBlocksConfigurationEditor : ConfigurationEditor // TODO: expire the local cache `_elementTypes` when a new element type is added. [LK:2021-08-16] private readonly Dictionary _elementTypes; private readonly Lazy> _elementBlueprints; + private readonly IIOHelper _ioHelper; private readonly ConfigurationEditorUtility _utility; internal const string DisplayMode = "displayMode"; @@ -27,16 +38,19 @@ internal sealed class ContentBlocksConfigurationEditor : ConfigurationEditor public ContentBlocksConfigurationEditor( IContentService contentService, IContentTypeService contentTypeService, - ConfigurationEditorUtility utility) + ConfigurationEditorUtility utility, + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) : base() { + _ioHelper = ioHelper; _utility = utility; // NOTE: Gets all the elementTypes and blueprints upfront, rather than several hits inside the loop. _elementTypes = contentTypeService.GetAllElementTypes().ToDictionary(x => x.Key); _elementBlueprints = new Lazy>(() => contentService.GetBlueprintsForContentTypes(_elementTypes.Values.Select(x => x.Id).ToArray()).ToLookup(x => x.ContentTypeId)); - var displayModes = utility.GetConfigurationEditorModels(); + var displayModes = utility.GetConfigurationEditorModels(shortStringHelper); // NOTE: Sets the default display mode to be the Blocks. var defaultDisplayMode = displayModes.FirstOrDefault(x => x.Key.InvariantEquals(typeof(BlocksDisplayMode).GetFullNameWithAssembly())); @@ -50,21 +64,21 @@ public ContentBlocksConfigurationEditor( Key = DisplayMode, Name = "Display mode", Description = "Select and configure how to display the blocks in the editor.", - View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath), Config = new Dictionary() { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureDisplayMode" }, { Constants.Conventions.ConfigurationFieldAliases.Items, displayModes }, { MaxItemsConfigurationField.MaxItems, 1 }, { DisableSortingConfigurationField.DisableSorting, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, } }); - Fields.Add(new ContentBlocksTypesConfigurationField(_elementTypes.Values)); + Fields.Add(new ContentBlocksTypesConfigurationField(_elementTypes.Values, ioHelper)); Fields.Add(new EnableFilterConfigurationField()); - Fields.Add(new MaxItemsConfigurationField()); + Fields.Add(new MaxItemsConfigurationField(ioHelper)); Fields.Add(new DisableSortingConfigurationField()); Fields.Add(new EnableDevModeConfigurationField()); } @@ -166,7 +180,7 @@ public override IDictionary ToValueEditor(object configuration) if (config.ContainsKey(Constants.Conventions.ConfigurationFieldAliases.OverlayView) == false) { - config.Add(Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ContentBlocksDataEditor.DataEditorOverlayViewPath)); + config.Add(Constants.Conventions.ConfigurationFieldAliases.OverlayView, _ioHelper.ResolveRelativeOrVirtualUrl(ContentBlocksDataEditor.DataEditorOverlayViewPath)); } return config; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index 24e9fa7a..a26422be 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -6,9 +6,23 @@ using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -24,21 +38,54 @@ public sealed class ContentBlocksDataEditor : IDataEditor private readonly IContentTypeService _contentTypeService; private readonly IDataTypeService _dataTypeService; private readonly Lazy _propertyEditors; + private readonly IShortStringHelper _shortStringHelper; private readonly ConfigurationEditorUtility _utility; + private readonly IIOHelper _ioHelper; +#if NET472 public ContentBlocksDataEditor( IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, Lazy propertyEditors, - ConfigurationEditorUtility utility) + IShortStringHelper shortStringHelper, + ConfigurationEditorUtility utility, + IIOHelper ioHelper) { _contentService = contentService; _contentTypeService = contentTypeService; _dataTypeService = dataTypeService; _propertyEditors = propertyEditors; + _shortStringHelper = shortStringHelper; _utility = utility; + _ioHelper = ioHelper; } +#else + private readonly ILocalizedTextService _localizedTextService; + private readonly IJsonSerializer _jsonSerializer; + + public ContentBlocksDataEditor( + IContentService contentService, + IContentTypeService contentTypeService, + Lazy propertyEditors, + IDataTypeService dataTypeService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + ConfigurationEditorUtility utility, + IIOHelper ioHelper) + { + _contentService = contentService; + _contentTypeService = contentTypeService; + _dataTypeService = dataTypeService; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _propertyEditors = propertyEditors; + _utility = utility; + _ioHelper = ioHelper; + } +#endif public string Alias => DataEditorAlias; @@ -48,7 +95,7 @@ public ContentBlocksDataEditor( public string Icon => DataEditorIcon; - public string Group => Core.Constants.PropertyEditors.Groups.RichContent; + public string Group => UmbConstants.PropertyEditors.Groups.RichContent; public bool IsDeprecated => false; @@ -56,20 +103,33 @@ public ContentBlocksDataEditor( public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new ContentBlocksConfigurationEditor(_contentService, _contentTypeService, _utility); + public IConfigurationEditor GetConfigurationEditor() => new ContentBlocksConfigurationEditor(_contentService, _contentTypeService, _utility, _shortStringHelper, _ioHelper); public IDataValueEditor GetValueEditor() { - return new ContentBlocksDataValueEditor(_contentTypeService, _dataTypeService, _propertyEditors.Value) +#if NET472 + return new ContentBlocksDataValueEditor( + _contentTypeService, + _dataTypeService, + _propertyEditors.Value) +#else + return new ContentBlocksDataValueEditor( + _contentTypeService, + _propertyEditors.Value, + _dataTypeService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { ValueType = ValueTypes.Json, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } public IDataValueEditor GetValueEditor(object configuration) { - var view = DataEditorViewPath; + var view = default(string); if (configuration is Dictionary config) { @@ -93,11 +153,21 @@ public IDataValueEditor GetValueEditor(object configuration) } } +#if NET472 return new ContentBlocksDataValueEditor(_contentTypeService, _dataTypeService, _propertyEditors.Value) +#else + return new ContentBlocksDataValueEditor( + _contentTypeService, + _propertyEditors.Value, + _dataTypeService, + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { Configuration = configuration, ValueType = ValueTypes.Json, - View = view, + View = _ioHelper.ResolveRelativeOrVirtualUrl(view ?? DataEditorViewPath), }; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs index 9b1f3e44..ca06b3c6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs @@ -15,11 +15,21 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +#else +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -29,29 +39,53 @@ internal sealed class ContentBlocksDataValueEditor : DataValueEditor private readonly Lazy> _elementTypes; private readonly PropertyEditorCollection _propertyEditors; - public ContentBlocksDataValueEditor( +#if NET472 +public ContentBlocksDataValueEditor( IContentTypeService contentTypeService, IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) : base() +#else + public ContentBlocksDataValueEditor( + IContentTypeService contentTypeService, + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) + : base(localizedTextService, shortStringHelper, jsonSerializer) +#endif { _dataTypeService = dataTypeService; _elementTypes = new Lazy>(() => contentTypeService.GetAllElementTypes().ToDictionary(x => x.Key)); _propertyEditors = propertyEditors; } + +#if NET472 public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) +#else + public override object ToEditor(IProperty property, string culture = null, string segment = null) +#endif { var value = property.GetValue(culture, segment)?.ToString(); if (string.IsNullOrWhiteSpace(value) == true) { +#if NET472 return base.ToEditor(property, dataTypeService, culture, segment); +#else + return base.ToEditor(property, culture, segment); +#endif } var blocks = JsonConvert.DeserializeObject>(value); if (blocks == null) { +#if NET472 return base.ToEditor(property, dataTypeService, culture, segment); +#else + return base.ToEditor(property, culture, segment); +#endif } foreach (var block in blocks) @@ -86,7 +120,11 @@ public override object ToEditor(Property property, IDataTypeService dataTypeServ continue; } +#if NET472 var convertedValue = propertyEditor.GetValueEditor()?.ToEditor(fakeProperty, dataTypeService); +#else + var convertedValue = propertyEditor.GetValueEditor()?.ToEditor(fakeProperty); +#endif block.Value[key] = convertedValue != null ? JToken.FromObject(convertedValue) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index 0fdf4c3a..3c2ef2e0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -5,10 +5,17 @@ using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -16,8 +23,12 @@ internal sealed class ContentBlocksTypesConfigurationField : ConfigurationField { internal const string ContentBlockTypes = "contentBlockTypes"; - public ContentBlocksTypesConfigurationField(IEnumerable elementTypes) + private readonly IIOHelper _ioHelper; + + public ContentBlocksTypesConfigurationField(IEnumerable elementTypes, IIOHelper ioHelper) { + _ioHelper = ioHelper; + var items = elementTypes .OrderBy(x => x.Name) .Select(x => new ConfigurationEditorModel @@ -37,13 +48,13 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp Key = ContentBlockTypes; Name = "Block types"; Description = "Configure the block types to use."; - View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath); + View = ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath); Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureElementType" }, { "allowDuplicates", Constants.Values.False }, { EnableFilterConfigurationField.EnableFilter, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { Constants.Conventions.ConfigurationFieldAliases.Items, items }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, }; @@ -57,7 +68,7 @@ private IEnumerable GetConfigurationFields(IContentType cont { Key = "elementType", Name = "Element type", - View = IOHelper.ResolveUrl(Constants.Internals.EditorsPathRoot + "readonly-node-preview.html"), + View = _ioHelper.ResolveRelativeOrVirtualUrl(Constants.Internals.EditorsPathRoot + "readonly-node-preview.html"), Config = new Dictionary { { "name", contentType.Name }, @@ -78,7 +89,7 @@ private IEnumerable GetConfigurationFields(IContentType cont Key = "overlaySize", Name = "Editor overlay size", Description = "Select the size of the overlay editing panel. By default this is set to 'small'. However if the editor fields require a wider panel, please select 'medium' or 'large'.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs index bb093bb3..6f206e9d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs @@ -7,11 +7,19 @@ using System.Collections.Generic; using Newtonsoft.Json; using Umbraco.Community.Contentment.Web.PublishedCache; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.PublishedCache; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -62,7 +70,7 @@ public override object ConvertIntermediateToObject(IPublishedElement owner, IPub if (ContentTypeCacheHelper.TryGetAlias(item.ElementType, out var alias, _contentTypeService) == false) continue; - var contentType = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetContentType(alias); + var contentType = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Content.GetContentType(alias); if (contentType == null || contentType.IsElement == false) continue; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs index d330e2ac..e3aa6da6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System.IO; using System.Web; using System.Web.Mvc; @@ -49,3 +50,4 @@ internal static string RenderPartial(string partialName, ViewDataDictionary view } } } +#endif diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs index 9189a352..e999872c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs @@ -8,10 +8,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System; using System.Collections.Concurrent; using Umbraco.Core.Models; using Umbraco.Core.Services; +#else +using System; +using System.Collections.Concurrent; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs index 00f680ed..21ca4759 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/BlocksDisplayMode.cs @@ -4,13 +4,27 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; using UmbIcons = Umbraco.Core.Constants.Icons; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using UmbIcons = Umbraco.Cms.Core.Constants.Icons; +#endif namespace Umbraco.Community.Contentment.DataEditors { internal class BlocksDisplayMode : IContentBlocksDisplayMode { + private readonly IIOHelper _ioHelper; + + public BlocksDisplayMode(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Blocks"; public string Description => "Blocks will be displayed in a list similar to the Block List editor."; @@ -36,7 +50,7 @@ internal class BlocksDisplayMode : IContentBlocksDisplayMode public IEnumerable Fields => new[] { - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
A note about block type previews.

Currently, the preview feature for block types has not been implemented for the {Name} display mode and has been temporarily disabled.

", true), diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs index 4601ff37..dcf34d8a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs @@ -4,12 +4,25 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { internal class ListDisplayMode : IContentBlocksDisplayMode { + private readonly IIOHelper _ioHelper; + + public ListDisplayMode(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "List"; public string Description => "Blocks will be displayed in a list similar to a content picker."; @@ -29,7 +42,7 @@ internal class ListDisplayMode : IContentBlocksDisplayMode public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
A note about block type previews.

Unfortunately, the preview feature for block types is unsupported in the {Name} display mode and will be disabled.

", true), diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs index e1b6f495..17e386eb 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/StackDisplayMode.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs index 36d4b406..c90afc04 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs @@ -4,15 +4,23 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +using Newtonsoft.Json.Linq; +#if NET472 using System.Net; using System.Net.Http; using System.Web.Http; -using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Editors; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; +#else +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -27,7 +35,11 @@ public DataListApiController(PropertyEditorCollection propertyEditors) } [HttpPost] +#if NET472 public HttpResponseMessage GetPreview([FromBody] JObject data) +#else + public ActionResult GetPreview([FromBody] JObject data) +#endif { var config = data.ToObject>(); @@ -38,10 +50,18 @@ public HttpResponseMessage GetPreview([FromBody] JObject data) var valueEditorConfig = configurationEditor.ToValueEditor(config); var valueEditor = propertyEditor.GetValueEditor(config); +#if NET472 return Request.CreateResponse(HttpStatusCode.OK, new { config = valueEditorConfig, view = valueEditor.View, alias }); +#else + return new ObjectResult(new { config = valueEditorConfig, view = valueEditor.View, alias }); +#endif } +#if NET472 return Request.CreateResponse(HttpStatusCode.NotFound); +#else + return new NotFoundResult(); +#endif } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs index 3cc8582b..a881feee 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs @@ -6,9 +6,17 @@ using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -22,22 +30,26 @@ internal sealed class DataListConfigurationEditor : ConfigurationEditor private readonly ConfigurationEditorUtility _utility; - public DataListConfigurationEditor(ConfigurationEditorUtility utility) + public DataListConfigurationEditor( + ConfigurationEditorUtility utility, + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) : base() { _utility = utility; - var configEditorViewPath = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath); + var configEditorViewPath = ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath); var defaultConfigEditorConfig = new Dictionary { { MaxItemsConfigurationField.MaxItems, 1 }, { DisableSortingConfigurationField.DisableSorting, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, }; - var dataSources = new List(utility.GetConfigurationEditorModels()); - var listEditors = new List(utility.GetConfigurationEditorModels()); + var dataSources = new List(utility.GetConfigurationEditorModels(shortStringHelper)); + var listEditors = new List(utility.GetConfigurationEditorModels(shortStringHelper)); + Fields.Add(new ConfigurationField { @@ -76,7 +88,7 @@ public DataListConfigurationEditor(ConfigurationEditorUtility utility) { Key = "preview", Name = "Preview", - View = IOHelper.ResolveUrl(DataListDataEditor.DataEditorPreviewViewPath) + View = ioHelper.ResolveRelativeOrVirtualUrl(DataListDataEditor.DataEditorPreviewViewPath) }); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs index 710155a4..5d866844 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs @@ -5,8 +5,23 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -20,9 +35,37 @@ public sealed class DataListDataEditor : IDataEditor internal const string DataEditorIcon = "icon-fa fa-list-ul"; private readonly ConfigurationEditorUtility _utility; - - public DataListDataEditor(ConfigurationEditorUtility utility) => _utility = utility; - + private readonly IShortStringHelper _shortStringHelper; + private readonly IIOHelper _ioHelper; + +#if NET472 + public DataListDataEditor( + ConfigurationEditorUtility utility, + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) + { + _utility = utility; + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; + } +#else + private readonly ILocalizedTextService _localizedTextService; + private readonly IJsonSerializer _jsonSerializer; + + public DataListDataEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + ConfigurationEditorUtility utility, + IIOHelper ioHelper) + { + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _utility = utility; + _ioHelper = ioHelper; + } +#endif public string Alias => DataEditorAlias; public EditorType Type => EditorType.PropertyValue; @@ -31,7 +74,7 @@ public sealed class DataListDataEditor : IDataEditor public string Icon => DataEditorIcon; - public string Group => Core.Constants.PropertyEditors.Groups.Lists; + public string Group => UmbConstants.PropertyEditors.Groups.Lists; public bool IsDeprecated => false; @@ -39,14 +82,18 @@ public sealed class DataListDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new DataListConfigurationEditor(_utility); + public IConfigurationEditor GetConfigurationEditor() => new DataListConfigurationEditor(_utility, _shortStringHelper, _ioHelper); public IDataValueEditor GetValueEditor() { +#if NET472 return new DataValueEditor +#else + return new DataValueEditor(_localizedTextService, _shortStringHelper, _jsonSerializer) +#endif { ValueType = ValueTypes.Json, - View = DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorViewPath), }; } @@ -73,11 +120,15 @@ public IDataValueEditor GetValueEditor(object configuration) } } +#if NET472 return new DataValueEditor +#else + return new DataValueEditor(_localizedTextService, _shortStringHelper, _jsonSerializer) +#endif { Configuration = configuration, ValueType = ValueTypes.Json, - View = view ?? DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(view ?? DataEditorViewPath), }; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs index fd85bf25..741983ce 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs @@ -8,9 +8,15 @@ using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs index d86f8137..fabe1dad 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CountriesDataListSource.cs @@ -6,8 +6,13 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs index ae773753..e6d6ec74 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/CurrenciesDataListSource.cs @@ -6,8 +6,13 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs index 71fd361a..432f436e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs @@ -11,21 +11,49 @@ using System.Runtime.Serialization; using Newtonsoft.Json.Linq; using Umbraco.Community.Contentment.Web.Controllers; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +#else +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class EnumDataListSource : IDataListSource, IDataListSourceValueConverter { - private readonly ILogger _logger; + private readonly IIOHelper _ioHelper; + private readonly IShortStringHelper _shortStringHelper; - public EnumDataListSource(ILogger logger) +#if NET472 + private readonly ILogger _logger; +#else + private readonly ILogger _logger; +#endif + + public EnumDataListSource( +#if NET472 + ILogger logger, +#else + ILogger logger, +#endif + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) { _logger = logger; + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; } + + public string Name => ".NET Enumeration"; public string Description => "Select an enumeration from a .NET assembly as the data source."; @@ -45,7 +73,7 @@ public EnumDataListSource(ILogger logger) Key = "enumType", Name = "Enumeration type", Description = "Select the enumeration from an assembly type.", - View = CascadingDropdownListDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(CascadingDropdownListDataEditor.DataEditorViewPath), Config = new Dictionary { { CascadingDropdownListDataEditor.APIs, new[] @@ -93,7 +121,7 @@ public IEnumerable GetItems(Dictionary config) Description = attr?.Description ?? attr2?.Description, Disabled = attr?.Disabled ?? false, Icon = attr?.Icon, - Name = attr?.Name ?? field.Name.SplitPascalCasing(), + Name = attr?.Name ?? field.Name.SplitPascalCasing(_shortStringHelper), Value = attr3?.Value ?? attr?.Value ?? field.Name }); } @@ -114,11 +142,35 @@ public Type GetValueType(Dictionary config) if (enumType?.Length > 1) { var assembly = default(Assembly); - try { assembly = Assembly.Load(enumType[0]); } catch (Exception ex) { _logger.Error(ex); } + try + { + assembly = Assembly.Load(enumType[0]); + } + catch (Exception ex) + { +#if NET472 + _logger.Error(ex); +#else + _logger.LogError(ex, "Unable to load target type."); +#endif + } + if (assembly != null) { var type = default(Type); - try { type = assembly.GetType(enumType[1]); } catch (Exception ex) { _logger.Error(ex); } + try + { + type = assembly.GetType(enumType[1]); + } + catch (Exception ex) + { +#if NET472 + _logger.Error(ex); +#else + _logger.LogError(ex, "Unable to retrieve target type."); +#endif + } + if (type != null && type.IsEnum == true) { return type; @@ -135,7 +187,18 @@ public object ConvertValue(Type type, string value) if (string.IsNullOrWhiteSpace(value) == false && type?.IsEnum == true) { // NOTE: Can't use `Enum.TryParse` here, as it's only available with generic types in .NET 4.8. - try { return Enum.Parse(type, value, true); } catch (Exception ex) { _logger.Error(ex); } + try + { + return Enum.Parse(type, value, true); + } + catch (Exception ex) + { +#if NET472 + _logger.Error(ex); +#else + _logger.LogError(ex, "Unable to parse Enum."); +#endif + } // If the value doesn't match the Enum field, then it is most likely set with `DataListItemAttribute.Value`. var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); @@ -149,7 +212,11 @@ public object ConvertValue(Type type, string value) } } +#if NET472 _logger.Debug($"Unable to find value '{value}' in enum '{type.FullName}'."); +#else + _logger.LogDebug($"Unable to find value '{value}' in enum '{type.FullName}'."); +#endif } return type.GetDefaultValue(); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs index f9b577f2..fac6e40c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/ExamineDataListSource.cs @@ -6,23 +6,34 @@ using System.Collections.Generic; using System.Linq; using Examine; -using Examine.LuceneEngine.Providers; using Examine.Search; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; -using Umbraco.Examine; +using Umbraco.Core.Strings; using UmbConstants = Umbraco.Core.Constants; +using UmbracoExamineFieldNames = Umbraco.Examine.UmbracoExamineIndex; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class ExamineDataListSource : IDataListSource { private readonly IExamineManager _examineManager; + private readonly IShortStringHelper _shortStringHelper; + private readonly IIOHelper _ioHelper; private const string _defaultNameField = "nodeName"; - private const string _defaultValueField = UmbracoExamineIndex.NodeKeyFieldName; - private const string _defaultIconField = UmbracoExamineIndex.IconFieldName; + private const string _defaultValueField = UmbracoExamineFieldNames.NodeKeyFieldName; + private const string _defaultIconField = UmbracoExamineFieldNames.IconFieldName; private readonly Dictionary _examineFieldConfig = new Dictionary { @@ -30,14 +41,14 @@ public sealed class ExamineDataListSource : IDataListSource Constants.Conventions.ConfigurationFieldAliases.Items, new[] { - LuceneIndex.CategoryFieldName, - LuceneIndex.ItemIdFieldName, - LuceneIndex.ItemTypeFieldName, - UmbracoExamineIndex.IconFieldName, - UmbracoExamineIndex.IndexPathFieldName, - UmbracoExamineIndex.NodeKeyFieldName, - UmbracoExamineIndex.PublishedFieldName, - UmbracoExamineIndex.UmbracoFileFieldName, + UmbracoExamineFieldNames.CategoryFieldName, + UmbracoExamineFieldNames.ItemIdFieldName, + UmbracoExamineFieldNames.ItemTypeFieldName, + UmbracoExamineFieldNames.IconFieldName, + UmbracoExamineFieldNames.IndexPathFieldName, + UmbracoExamineFieldNames.NodeKeyFieldName, + UmbracoExamineFieldNames.PublishedFieldName, + UmbracoExamineFieldNames.UmbracoFileFieldName, "createDate", "creatorID", "creatorName", @@ -57,9 +68,11 @@ public sealed class ExamineDataListSource : IDataListSource }, }; - public ExamineDataListSource(IExamineManager examineManager) + public ExamineDataListSource(IExamineManager examineManager, IShortStringHelper shortStringHelper, IIOHelper ioHelper) { _examineManager = examineManager; + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; } public string Name => "Examine Query"; @@ -79,14 +92,18 @@ public ExamineDataListSource(IExamineManager examineManager) Key = "examineIndex", Name = "Examine Index", Description = "Select the Examine index.", - View = DropdownListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, - { Constants.Conventions.ConfigurationFieldAliases.Items, _examineManager.Indexes.OrderBy(x => x.Name).Select(x => new DataListItem { Name = x.Name.SplitPascalCasing(), Value = x.Name }) }, + { Constants.Conventions.ConfigurationFieldAliases.Items, _examineManager.Indexes.OrderBy(x => x.Name).Select(x => new DataListItem + { + Name = x.Name.SplitPascalCasing(_shortStringHelper), + Value = x.Name + }) }, } }, - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with Lucene query?

If you need assistance with Lucene query syntax, please refer to this resource on our.umbraco.com.

", true), @@ -95,7 +112,7 @@ public ExamineDataListSource(IExamineManager examineManager) Key = "luceneQuery", Name = "Lucene query", Description = "Enter your raw Lucene expression to query Examine with.", - View = CodeEditorDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, "text" }, @@ -108,7 +125,7 @@ public ExamineDataListSource(IExamineManager examineManager) Key = "nameField", Name = "Name Field", Description = "Enter the field name to select the name from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, new ConfigurationField @@ -116,7 +133,7 @@ public ExamineDataListSource(IExamineManager examineManager) Key = "valueField", Name = "Value Field", Description = "Enter the field name to select the value (key) from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, new ConfigurationField @@ -124,7 +141,7 @@ public ExamineDataListSource(IExamineManager examineManager) Key = "iconField", Name = "Icon Field", Description = "(optional) Enter the field name to select the icon from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, new ConfigurationField @@ -132,7 +149,7 @@ public ExamineDataListSource(IExamineManager examineManager) Key = "descriptionField", Name = "Description Field", Description = "(optional) Enter the field name to select the description from the Examine record.", - View = IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(TextInputDataEditor.DataEditorViewPath), Config = _examineFieldConfig }, }; @@ -161,7 +178,11 @@ public IEnumerable GetItems(Dictionary config) var descriptionField = config.GetValueAs("descriptionField", string.Empty); var results = index +#if NET472 .GetSearcher() +#else + .Searcher +#endif .CreateQuery() .NativeQuery(luceneQuery) // NOTE: For any `OrderBy` complaints, refer to: https://github.com/Shazwazza/Examine/issues/126 diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs index 7ad04880..7cae63ef 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs @@ -10,20 +10,46 @@ using System.Net; using System.Text; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +#else +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class JsonDataListSource : IDataListSource { + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + +#if NET472 private readonly ILogger _logger; - public JsonDataListSource(ILogger logger) + public JsonDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) +#else + private readonly ILogger _logger; + + public JsonDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) +#endif { _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; } public string Name => "JSON Data"; @@ -38,12 +64,12 @@ public JsonDataListSource(ILogger logger) public IEnumerable Fields => new[] { - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
Do you need help with JSONPath expressions?

This data-source uses Newtonsoft's Json.NET library, with this we are limited to extracting only the 'value' from any key/value-pairs.

If you need assistance with JSONPath syntax, please refer to this resource: goessner.net/articles/JsonPath.


-

If you are a developer and have ideas on how to extract the `key` (name) from the items, please do let me know on GitHub issue: #40.

+

If you are a developer and have ideas on how to extract the key (name) from the items, please do let me know on GitHub issue: #40.

", true), new ConfigurationField { @@ -128,7 +154,11 @@ public IEnumerable GetItems(Dictionary config) if (tokens.Any() == false) { +#if NET472 _logger.Warn($"The JSONPath '{itemsJsonPath}' did not match any items in the JSON."); +#else + _logger.LogWarning($"The JSONPath '{itemsJsonPath}' did not match any items in the JSON."); +#endif return Enumerable.Empty(); } @@ -159,13 +189,21 @@ public IEnumerable GetItems(Dictionary config) // How should we log if either name or value is empty? Note that empty or missing values are totally legal according to json if (name == null) { +#if NET472 _logger.Warn($"The JSONPath '{nameJsonPath}' did not match a 'name' in the item JSON."); +#else + _logger.LogWarning($"The JSONPath '{nameJsonPath}' did not match a 'name' in the item JSON."); +#endif } // If value is missing we'll skip this specific item and log as a warning if (value == null) { +#if NET472 _logger.Warn($"The JSONPath '{valueJsonPath}' did not match a 'value' in the item XML. The item was skipped."); +#else + _logger.LogWarning($"The JSONPath '{valueJsonPath}' did not match a 'value' in the item XML. The item was skipped."); +#endif continue; } @@ -182,7 +220,11 @@ public IEnumerable GetItems(Dictionary config) } catch (Exception ex) { +#if NET472 _logger.Error(ex, "Error finding items in the JSON. Please check the syntax of your JSONPath expressions."); +#else + _logger.LogError(ex, "Error finding items in the JSON. Please check the syntax of your JSONPath expressions."); +#endif } return Enumerable.Empty(); @@ -203,27 +245,39 @@ private JToken GetJson(string url) } catch (WebException ex) { +#if NET472 _logger.Error(ex, $"Unable to fetch remote data from URL: {url}"); +#else + _logger.LogError(ex, $"Unable to fetch remote data from URL: {url}"); +#endif } } else { // assume local file - var path = IOHelper.MapPath(url); + var path = _hostingEnvironment.MapPathWebRoot(url); if (File.Exists(path) == true) { content = File.ReadAllText(path); } else { +#if NET472 _logger.Error(new FileNotFoundException(), $"Unable to find the local file path: {url}"); +#else + _logger.LogError(new FileNotFoundException(), $"Unable to find the local file path: {url}"); +#endif return null; } } if (string.IsNullOrWhiteSpace(content) == true) { +#if NET472 _logger.Warn($"The contents of '{url}' was empty. Unable to process JSON data."); +#else + _logger.LogWarning($"The contents of '{url}' was empty. Unable to process JSON data."); +#endif return default; } @@ -237,7 +291,11 @@ private JToken GetJson(string url) catch (Exception ex) { var trimmed = content.Substring(0, Math.Min(400, content.Length)); +#if NET472 _logger.Error(ex, $"Error parsing string to JSON: {trimmed}"); +#else + _logger.LogError(ex, $"Error parsing string to JSON: {trimmed}"); +#endif } return default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/LanguagesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/LanguagesDataListSource.cs index 0fe90032..e55b93ff 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/LanguagesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/LanguagesDataListSource.cs @@ -6,8 +6,13 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs index a3ae72b6..a1b8fbfb 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/NumberRangeDataListSource.cs @@ -6,15 +6,29 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class NumberRangeDataListSource : IDataListSource, IDataListSourceValueConverter { + private readonly IIOHelper _ioHelper; + + public NumberRangeDataListSource(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Number Range"; public string Description => "Generates a sequence of numbers within a specified range."; @@ -30,7 +44,7 @@ public sealed class NumberRangeDataListSource : IDataListSource, IDataListSource Key = "start", Name = "Start", Description = "The value of the first number in the sequence.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), Config = new Dictionary { { "step", 0.1D }, @@ -42,7 +56,7 @@ public sealed class NumberRangeDataListSource : IDataListSource, IDataListSource Key = "end", Name = "End", Description = "The value of the last number in the sequence.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), Config = new Dictionary { { "step", 0.1D }, @@ -54,7 +68,7 @@ public sealed class NumberRangeDataListSource : IDataListSource, IDataListSource Key = "step", Name = "Step", Description = "The number of steps between each number.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), Config = new Dictionary { { "step", 0.1D }, @@ -66,7 +80,7 @@ public sealed class NumberRangeDataListSource : IDataListSource, IDataListSource Key = "decimals", Name = "Decimal places", Description = "How many decimal places would you like?", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/slider/slider.html"), + View = _ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/slider/slider.html"), Config = new Dictionary { { "initVal1", 0 }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs index 25526329..f5b1b454 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs @@ -5,14 +5,52 @@ using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +using UmbConstants = Umbraco.Core.Constants; +#else +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class PhysicalFileSystemDataSource : IDataListSource { + private readonly IShortStringHelper _shortStringHelper; + +#if NET472 + public PhysicalFileSystemDataSource( + IShortStringHelper shortStringHelper) + { + _shortStringHelper = shortStringHelper; + } +#else + private readonly IIOHelper _ioHelper; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILogger _logger; + + public PhysicalFileSystemDataSource( + IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, + ILogger logger, + IShortStringHelper shortStringHelper) + { + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; + _hostingEnvironment = hostingEnvironment; + _logger = logger; + } +#endif + public string Name => "File System"; public string Description => "Select paths from the physical file system as the data source."; @@ -29,7 +67,7 @@ public sealed class PhysicalFileSystemDataSource : IDataListSource { Key = "path", Name = "Folder path", - Description = "Enter the relative path of the folder. e.g. ~/css", + Description = "Enter the relative path of the folder. e.g. ~/css
Please note, this is relative to the web root folder, e.g. wwwroot.", View = "textstring", }, new ConfigurationField @@ -68,15 +106,19 @@ public IEnumerable GetItems(Dictionary config) ? filter : "*.*"; +#if NET472 var fs = new PhysicalFileSystem(virtualRoot); +#else + var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, _hostingEnvironment.MapPathWebRoot(virtualRoot), _hostingEnvironment.ToAbsolute(virtualRoot)); +#endif var files = fs.GetFiles(".", fileFilter); return files.Select(x => new DataListItem { - Name = friendlyName == true ? x.SplitPascalCasing().ToFriendlyName() : x, + Name = friendlyName == true ? x.SplitPascalCasing(_shortStringHelper).ToFriendlyName() : x, Value = virtualRoot + x, Description = virtualRoot + x, - Icon = Core.Constants.Icons.DefaultIcon, + Icon = UmbConstants.Icons.DefaultIcon, }); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index 3dfdd03f..4fb1a315 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -4,16 +4,28 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; -using System.Configuration; using System.Data.Common; using System.Data.SqlClient; -using System.Data.SqlServerCe; using System.IO; using System.Linq; +#if NET472 +using System.Configuration; +using System.Data.SqlServerCe; using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; using UmbConstants = Umbraco.Core.Constants; +#else +using Microsoft.Extensions.Configuration; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -21,14 +33,24 @@ public sealed class SqlDataListSource : IDataListSource { private readonly string _codeEditorMode; private readonly IEnumerable _connectionStrings; - - public SqlDataListSource() + private readonly IIOHelper _ioHelper; +#if NET472 == false + private readonly IConfiguration _configuration; +#endif + + public SqlDataListSource( + IHostingEnvironment hostingEnvironment, +#if NET472 == false + IConfiguration configuration, +#endif + IIOHelper ioHelper) { // NOTE: Umbraco doesn't ship with SqlServer mode, so we check if its been added manually, otherwise defautls to Razor. - _codeEditorMode = File.Exists(IOHelper.MapPath("~/umbraco/lib/ace-builds/src-min-noconflict/mode-sqlserver.js")) + _codeEditorMode = File.Exists(hostingEnvironment.MapPathWebRoot("~/umbraco/lib/ace-builds/src-min-noconflict/mode-sqlserver.js")) ? "sqlserver" : "razor"; +#if NET472 _connectionStrings = ConfigurationManager.ConnectionStrings .Cast() .Select(x => new DataListItem @@ -36,6 +58,19 @@ public SqlDataListSource() Name = x.Name, Value = x.Name }); +#else + _connectionStrings = configuration + .GetSection("ConnectionStrings") + .GetChildren() + .Select(x => new DataListItem + { + Name = x.Key, + Value = x.Key + }); + + _configuration = configuration; +#endif + _ioHelper = ioHelper; } public string Name => "SQL Data"; @@ -50,7 +85,9 @@ public SqlDataListSource() public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField(@"
+ // TODO: [LK:2021-09-20] Add a note on v9 edition to inform the user about the lack of SQLCE support + a plea for help. + + new NotesConfigurationField(_ioHelper, @"
Important: A note about your SQL query.

Your SQL query should be designed to return a minimum of 2 columns, (and a maximum of 5 columns). These columns will be used to populate the List Editor items.

The columns will be mapped in the following order:

@@ -68,7 +105,7 @@ public SqlDataListSource() Key = "query", Name = "SQL query", Description = "Enter your SQL query.", - View = CodeEditorDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, _codeEditorMode }, @@ -81,7 +118,7 @@ public SqlDataListSource() Key = "connectionString", Name = "Connection string", Description = "Select the connection string.", - View = DropdownListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -92,7 +129,7 @@ public SqlDataListSource() public Dictionary DefaultValues => new Dictionary { - { "query", $"-- This is an example query that will select all the content nodes that are at level 1.\r\nSELECT\r\n\t[text],\r\n\t[uniqueId]\r\nFROM\r\n\t[umbracoNode]\r\nWHERE\r\n\t[nodeObjectType] = '{Core.Constants.ObjectTypes.Strings.Document}'\r\n\tAND\r\n\t[level] = 1\r\nORDER BY\r\n\t[sortOrder] ASC\r\n;" }, + { "query", $"-- This is an example query that will select all the content nodes that are at level 1.\r\nSELECT\r\n\t[text],\r\n\t[uniqueId]\r\nFROM\r\n\t[umbracoNode]\r\nWHERE\r\n\t[nodeObjectType] = '{UmbConstants.ObjectTypes.Strings.Document}'\r\n\tAND\r\n\t[level] = 1\r\nORDER BY\r\n\t[sortOrder] ASC\r\n;" }, { "connectionString", UmbConstants.System.UmbracoConnectionName } }; @@ -101,19 +138,25 @@ public IEnumerable GetItems(Dictionary config) var items = new List(); var query = config.GetValueAs("query", string.Empty); - var connectionString = config.GetValueAs("connectionString", string.Empty); + var connectionStringName = config.GetValueAs("connectionString", string.Empty); - if (string.IsNullOrWhiteSpace(query) == true || string.IsNullOrWhiteSpace(connectionString) == true) + if (string.IsNullOrWhiteSpace(query) == true || string.IsNullOrWhiteSpace(connectionStringName) == true) { return items; } - var settings = ConfigurationManager.ConnectionStrings[connectionString]; +#if NET472 + var settings = ConfigurationManager.ConnectionStrings[connectionStringName]; if (settings == null) +#else + var connectionString = _configuration.GetConnectionString(connectionStringName); + if (string.IsNullOrWhiteSpace(connectionString) == true) +#endif { return items; } +#if NET472 // NOTE: SQLCE uses a different connection/command. I'm trying to keep this as generic as possible, without resorting to using NPoco. [LK] if (settings.ProviderName.InvariantEquals(UmbConstants.DatabaseProviders.SqlCe) == true) { @@ -123,6 +166,15 @@ public IEnumerable GetItems(Dictionary config) { items.AddRange(GetSqlItems(query, settings.ConnectionString)); } +#else + // TODO: [v9] [LK:2021-05-07] Review SQLCE + // NOTE: SQLCE uses a different connection/command. I'm trying to keep this as generic as possible, without resorting to using NPoco. [LK] + // I've tried digging around Umbraco's `IUmbracoDatabase` layer, but I couldn't get my head around it. + // At the end of the day, if the user has SQLCE configured, it'd be nice for them to query it. + // But I don't want to add an assembly dependency (for SQLCE) to Contentment itself. I'd like to leverage Umbraco's code. + + items.AddRange(GetSqlItems(query, connectionString)); +#endif return items; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs index 083e6591..bc0debc4 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs @@ -7,21 +7,53 @@ using System.Collections.Generic; using System.IO; using System.Net; +#if NET472 using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +#else +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class TextDelimitedDataListSource : IDataListSource { + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + +#if NET472 private readonly ILogger _logger; - public TextDelimitedDataListSource(ILogger logger) + public TextDelimitedDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) + { + _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; + } +#else + private readonly ILogger _logger; + + public TextDelimitedDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) { _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; } +#endif public string Name => "Text Delimited Data"; @@ -35,7 +67,7 @@ public TextDelimitedDataListSource(ILogger logger) public IEnumerable Fields => new[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
A note about using this data source.

The text contents will be retrieved and split into lines. Each line will be split into fields by the delimiting character.

The fields are then assigned by index position.

@@ -66,28 +98,28 @@ public TextDelimitedDataListSource(ILogger logger) Key = "nameIndex", Name = "Name Index", Description = "Enter the index position of the name field from the delimited line.
The default index position is 0.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }, new ConfigurationField { Key = "valueIndex", Name = "Value Index", Description = "Enter the index position of the value (key) field from the delimited line.
The default index position is 1.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }, new ConfigurationField { Key = "iconIndex", Name = "Icon Index", Description = "(optional) Enter the index position of the icon field from the delimited line. To ignore this option, set the value to -1.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }, new ConfigurationField { Key = "descriptionIndex", Name = "Description Index", Description = "(optional) Enter the index position of the description field from the delimited line. To ignore this option, set the value to -1.", - View = NumberInputDataEditor.DataEditorViewPath + View = _ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), } }; @@ -186,20 +218,28 @@ private string[] GetTextLines(string url) } catch (WebException ex) { +#if NET472 _logger.Error(ex, "Unable to fetch remote data."); +#else + _logger.LogError(ex, "Unable to fetch remote data."); +#endif } } else { // assume local file - var path = IOHelper.MapPath(url); + var path = _hostingEnvironment.MapPathWebRoot(url); if (File.Exists(path) == true) { return File.ReadAllLines(path); } else { +#if NET472 _logger.Warn("Unable to find the local file path."); +#else + _logger.LogWarning("Unable to find the local file path."); +#endif } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs index 47bbd009..74a4ab61 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TimeZoneDataListSource.cs @@ -6,7 +6,11 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 73fc8d21..407002b9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -6,13 +6,26 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Core.Xml; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -20,12 +33,33 @@ public sealed class UmbracoContentDataListSource : IDataListSource, IDataListSou { private readonly IContentTypeService _contentTypeService; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoContentDataListSource(IContentTypeService contentTypeService, IUmbracoContextAccessor umbracoContextAccessor) +#if NET472 + public UmbracoContentDataListSource( + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; } +#else + private readonly IRequestAccessor _requestAccessor; + + public UmbracoContentDataListSource( + IContentTypeService contentTypeService, + IRequestAccessor requestAccessor, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) + { + _contentTypeService = contentTypeService; + _requestAccessor = requestAccessor; + _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; + } +#endif public string Name => "Umbraco Content"; @@ -44,7 +78,7 @@ public UmbracoContentDataListSource(IContentTypeService contentTypeService, IUmb Key = "parentNode", Name = "Parent node", Description = "Set a parent node to use its child nodes as the data source items.", - View = ContentPickerDataEditor.DataEditorSourceViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(ContentPickerDataEditor.DataEditorSourceViewPath), } }; @@ -55,18 +89,26 @@ public IEnumerable GetItems(Dictionary config) var preview = true; var parentNode = config.GetValueAs("parentNode", string.Empty); var startNode = default(IPublishedContent); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); if (parentNode.InvariantStartsWith("umb://document/") == false) { var nodeContextId = default(int?); - var umbracoContext = _umbracoContextAccessor.UmbracoContext; // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). +#if NET472 if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("id"), out var currentId) == true) +#else + if (int.TryParse(_requestAccessor.GetQueryStringValue("id"), out var currentId) == true) +#endif { nodeContextId = currentId; } +#if NET472 else if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("parentId"), out var parentId) == true) +#else + else if (int.TryParse(_requestAccessor.GetQueryStringValue("parentId"), out var parentId) == true) +#endif { nodeContextId = parentId; } @@ -86,9 +128,9 @@ public IEnumerable GetItems(Dictionary config) startNode = umbracoContext.Content.GetSingleByXPath(preview, parsed); } } - else if (GuidUdi.TryParse(parentNode, out var udi) == true && udi.Guid != Guid.Empty) + else if (UdiParser.TryParse(parentNode, out GuidUdi udi) == true && udi.Guid != Guid.Empty) { - startNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(preview, udi.Guid); + startNode = umbracoContext.Content.GetById(preview, udi.Guid); } if (startNode != null) @@ -111,8 +153,8 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - return Udi.TryParse(value, out var udi) == true - ? _umbracoContextAccessor.UmbracoContext.Content.GetById(udi) + return UdiParser.TryParse(value, out GuidUdi udi) == true + ? _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetById(udi) : default; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs index e0c5e907..d531a70a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentPropertiesDataListSource.cs @@ -7,11 +7,20 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -19,12 +28,17 @@ public sealed class UmbracoContentPropertiesDataListSource : IDataListSource { private readonly IContentTypeService _contentTypeService; private readonly Lazy _dataEditors; + private readonly IIOHelper _ioHelper; private Dictionary _icons; - public UmbracoContentPropertiesDataListSource(IContentTypeService contentTypeService, Lazy dataEditors) + public UmbracoContentPropertiesDataListSource( + IContentTypeService contentTypeService, + Lazy dataEditors, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; _dataEditors = dataEditors; + _ioHelper = ioHelper; } public string Name => "Umbraco Content Properties"; @@ -57,13 +71,13 @@ public IEnumerable Fields Key = "contentType", Name = "Content Type", Description = "Select a Content Type to list the properties from.", - View = ItemPickerDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorViewPath), Config = new Dictionary { { "enableFilter", items.Count > 5 ? Constants.Values.True : Constants.Values.False }, { "items", items }, { "listType", "list" }, - { "overlayView", IOHelper.ResolveUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, + { "overlayView", _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, { "maxItems", 1 }, } } @@ -81,7 +95,7 @@ public IEnumerable GetItems(Dictionary config) array.Count > 0 && array[0].Value() is string str && string.IsNullOrWhiteSpace(str) == false && - GuidUdi.TryParse(str, out var udi) == true) + UdiParser.TryParse(str, out GuidUdi udi) == true) { var contentType = _contentTypeService.Get(udi.Guid); if (contentType != null) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs index a70615dd..50182cfd 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentTypesDataListSource.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models.PublishedContent; @@ -15,6 +16,16 @@ using Umbraco.Core.Xml; using Umbraco.Web; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -22,11 +33,16 @@ public sealed class UmbracoContentTypesDataListSource : IDataListSource, IDataLi { private readonly IContentTypeService _contentTypeService; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoContentTypesDataListSource(IContentTypeService contentTypeService, IUmbracoContextAccessor umbracoContextAccessor) + public UmbracoContentTypesDataListSource( + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; } public string Name => "Umbraco Content Types"; @@ -45,7 +61,7 @@ public UmbracoContentTypesDataListSource(IContentTypeService contentTypeService, { Key = "contentTypes", Name = "Content types", - View = IOHelper.ResolveUrl(CheckboxListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(CheckboxListDataListEditor.DataEditorViewPath), Description = "Select the types to use.", Config = new Dictionary { @@ -128,9 +144,9 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - if (GuidUdi.TryParse(value, out var udi) == true && ContentTypeCacheHelper.TryGetAlias(udi.Guid, out var alias, _contentTypeService) == true) + if (UdiParser.TryParse(value, out GuidUdi udi) == true && ContentTypeCacheHelper.TryGetAlias(udi.Guid, out var alias, _contentTypeService) == true) { - return _umbracoContextAccessor.UmbracoContext.Content.GetContentType(alias); + return _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetContentType(alias); } return default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs index e6cfa604..3573c0c2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentXPathDataListSource.cs @@ -6,13 +6,26 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Core.Xml; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -20,12 +33,33 @@ public sealed class UmbracoContentXPathDataListSource : IDataListSource, IDataLi { private readonly IContentTypeService _contentTypeService; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoContentXPathDataListSource(IContentTypeService contentTypeService, IUmbracoContextAccessor umbracoContextAccessor) +#if NET472 + public UmbracoContentXPathDataListSource( + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) { _contentTypeService = contentTypeService; _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; } +#else + private readonly IRequestAccessor _requestAccessor; + + public UmbracoContentXPathDataListSource( + IContentTypeService contentTypeService, + IRequestAccessor requestAccessor, + IUmbracoContextAccessor umbracoContextAccessor, + IIOHelper ioHelper) + { + _contentTypeService = contentTypeService; + _requestAccessor = requestAccessor; + _umbracoContextAccessor = umbracoContextAccessor; + _ioHelper = ioHelper; + } +#endif public string Name => "Umbraco Content by XPath"; @@ -46,7 +80,7 @@ public UmbracoContentXPathDataListSource(IContentTypeService contentTypeService, Description = "Enter the XPath expression to select the content.", View = "textstring", }, - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
Do you need help with XPath expressions?

If you need assistance with XPath syntax in general, please refer to this resource: w3schools.com/xml.


@@ -79,14 +113,22 @@ public IEnumerable GetItems(Dictionary config) { var nodeContextId = default(int?); var preview = true; - var umbracoContext = _umbracoContextAccessor.UmbracoContext; + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). +#if NET472 if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("id"), out var currentId) == true) +#else + if (int.TryParse(_requestAccessor.GetQueryStringValue("id"), out var currentId) == true) +#endif { nodeContextId = currentId; } +#if NET472 else if (int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("parentId"), out var parentId) == true) +#else + else if (int.TryParse(_requestAccessor.GetQueryStringValue("parentId"), out var parentId) == true) +#endif { nodeContextId = parentId; } @@ -117,8 +159,8 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - return Udi.TryParse(value, out var udi) == true - ? _umbracoContextAccessor.UmbracoContext.Content.GetById(udi) + return UdiParser.TryParse(value, out var udi) == true + ? _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetById(udi) : default; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs index c4d66ff7..20a78db3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoDictionaryDataListSource.cs @@ -7,19 +7,31 @@ using System.Globalization; using System.Linq; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class UmbracoDictionaryDataListSource : IDataListSource { private readonly ILocalizationService _localizationService; + private readonly IIOHelper _ioHelper; - public UmbracoDictionaryDataListSource(ILocalizationService localizationService) + public UmbracoDictionaryDataListSource( + ILocalizationService localizationService, + IIOHelper ioHelper) { _localizationService = localizationService; + _ioHelper = ioHelper; } public string Name => "Umbraco Dictionary Items"; @@ -37,7 +49,7 @@ public UmbracoDictionaryDataListSource(ILocalizationService localizationService) Key = "item", Name = "Dictionary item", Description = "Select a parent dictionary item to display the child items.", - View = DictionaryPickerDataEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DictionaryPickerDataEditor.DataEditorViewPath), Config = new Dictionary { { MaxItemsConfigurationField.MaxItems, 1 } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs index 665a67dd..8d169861 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoEntityDataListSource.cs @@ -6,12 +6,26 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Core.Strings; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -41,11 +55,18 @@ public sealed class UmbracoEntityDataListSource : IDataListSource, IDataListSour { nameof(UmbracoObjectTypes.MemberType), UmbConstants.Icons.MemberType }, }; - private readonly IEntityService _entityService; + private readonly IIOHelper _ioHelper; + private readonly Lazy _entityService; + private readonly IShortStringHelper _shortStringHelper; - public UmbracoEntityDataListSource(IEntityService entityService) + public UmbracoEntityDataListSource( + Lazy entityService, + IShortStringHelper shortStringHelper, + IIOHelper ioHelper) { _entityService = entityService; + _shortStringHelper = shortStringHelper; + _ioHelper = ioHelper; } public string Name => "Umbraco Entities"; @@ -58,7 +79,7 @@ public UmbracoEntityDataListSource(IEntityService entityService) public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
A note about supported Umbraco entity types.

Umbraco's EntityService API (currently) has limited support for querying entity types by GUID or UDI.

Supported entity types are available in the list below.

@@ -68,11 +89,16 @@ public UmbracoEntityDataListSource(IEntityService entityService) Key = "entityType", Name = "Entity type", Description = "Select the Umbraco entity type to use.", - View = DropdownListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary() { { "allowEmpty", Constants.Values.False }, - { "items", SupportedEntityTypes.Keys.Select(x => new DataListItem { Name = x.SplitPascalCasing(), Value = x }) }, + { "items", SupportedEntityTypes.Keys.Select(x => new DataListItem + { + Name = x.SplitPascalCasing(_shortStringHelper), + Value = x + }) + }, } } }; @@ -88,13 +114,14 @@ public IEnumerable GetItems(Dictionary config) var icon = EntityTypeIcons.GetValueAs(entityType, UmbConstants.Icons.DefaultIcon); return _entityService + .Value .GetAll(objectType) .OrderBy(x => x.Name) .Select(x => new DataListItem { Icon = icon, Name = x.Name, - Value = Udi.Create(UmbConstants.UdiEntityType.FromUmbracoObjectType(objectType), x.Key).ToString(), + Value = Udi.Create(objectType.GetUdiType(), x.Key).ToString(), }); } @@ -105,8 +132,8 @@ public IEnumerable GetItems(Dictionary config) public object ConvertValue(Type type, string value) { - return GuidUdi.TryParse(value, out var udi) == true && udi.Guid.Equals(Guid.Empty) == false - ? _entityService.Get(udi.Guid) + return UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false + ? _entityService.Value.Get(udi.Guid) : default; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs index 06d91155..1d2f69da 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoImageCropDataListSource.cs @@ -5,21 +5,36 @@ using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class UmbracoImageCropDataListSource : IDataListSource { private readonly IDataTypeService _dataTypeService; + private readonly IIOHelper _ioHelper; - public UmbracoImageCropDataListSource(IDataTypeService dataTypeService) + public UmbracoImageCropDataListSource( + IDataTypeService dataTypeService, + IIOHelper ioHelper) { _dataTypeService = dataTypeService; + _ioHelper = ioHelper; } public string Name => "Umbraco Image Crops"; @@ -50,7 +65,7 @@ public IEnumerable Fields Key = "imageCropper", Name = "Image Cropper", Description = "Select a Data Type that uses the Image Cropper.", - View = RadioButtonListDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, items }, @@ -71,7 +86,7 @@ public IEnumerable GetItems(Dictionary config) if (config.TryGetValue("imageCropper", out var obj) == true && obj is string str && string.IsNullOrWhiteSpace(str) == false && - GuidUdi.TryParse(str, out var udi) == true) + UdiParser.TryParse(str, out GuidUdi udi) == true) { return _dataTypeService .GetDataType(udi.Guid)? diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoLanguagesDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoLanguagesDataListSource.cs index e5c7d5fe..21afe54d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoLanguagesDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoLanguagesDataListSource.cs @@ -5,9 +5,15 @@ using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs index 5be1f843..84cf4d53 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMemberGroupDataListSource.cs @@ -6,10 +6,17 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs index 120dd237..125d207a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoMembersDataListSource.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -15,6 +16,17 @@ using Umbraco.Core.Services; using Umbraco.Web.PublishedCache; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -23,12 +35,18 @@ public sealed class UmbracoMembersDataListSource : IDataListSource, IDataListSou private readonly IMemberTypeService _memberTypeService; private readonly IMemberService _memberService; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IIOHelper _ioHelper; - public UmbracoMembersDataListSource(IMemberTypeService memberTypeService, IMemberService memberService, IPublishedSnapshotAccessor publishedSnapshotAccessor) + public UmbracoMembersDataListSource( + IMemberTypeService memberTypeService, + IMemberService memberService, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IIOHelper ioHelper) { _memberTypeService = memberTypeService; _memberService = memberService; _publishedSnapshotAccessor = publishedSnapshotAccessor; + _ioHelper = ioHelper; } public string Name => "Umbraco Members"; @@ -56,7 +74,7 @@ public IEnumerable Fields return new[] { - new NotesConfigurationField($@"
+ new NotesConfigurationField(_ioHelper, $@"
Important note about Umbraco Members.

This data source is ideal for smaller number of members, e.g. under 50. Upwards of that, you will notice an unpleasant editor experience and rapidly diminished performance.

Remember...

@@ -70,14 +88,14 @@ public IEnumerable Fields Key = "memberType", Name = "Member Type", Description = "Select a member type to filter the members by. If left empty, all members will be used.", - View = ItemPickerDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorViewPath), Config = new Dictionary { { "addButtonLabelKey", "defaultdialogs_selectMemberType" }, { "enableFilter", items.Count > 5 ? Constants.Values.True : Constants.Values.False }, { "items", items }, { "listType", "list" }, - { "overlayView", IOHelper.ResolveUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, + { "overlayView", _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, { "maxItems", 1 }, } } @@ -107,7 +125,7 @@ DataListItem mapMember(IMember member) array.Count > 0 && array[0].Value() is string str && string.IsNullOrWhiteSpace(str) == false && - GuidUdi.TryParse(str, out var udi) == true) + UdiParser.TryParse(str, out GuidUdi udi) == true) { var memberType = _memberTypeService.Get(udi.Guid); if (memberType != null) @@ -123,9 +141,20 @@ DataListItem mapMember(IMember member) public object ConvertValue(Type type, string value) { - return GuidUdi.TryParse(value, out var udi) == true && udi.Guid.Equals(Guid.Empty) == false - ? _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(udi.Guid) - : default; + if (UdiParser.TryParse(value, out GuidUdi udi) == true && udi.Guid.Equals(Guid.Empty) == false) + { +#if NET472 + return _publishedSnapshotAccessor.GetRequiredPublishedSnapshot()?.Members.GetByProviderKey(udi.Guid); +#else + var member = _memberService.GetByKey(udi.Guid); + if (member != null) + { + return _publishedSnapshotAccessor.GetRequiredPublishedSnapshot()?.Members.Get(_memberService.GetByKey(udi.Guid)); + } +#endif + } + + return default(IPublishedContent); } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs index 2f048af5..a2d22337 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoTagsDataListSource.cs @@ -6,9 +6,15 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.PropertyEditors; using Umbraco.Web; +#else +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -51,7 +57,11 @@ public IEnumerable GetItems(Dictionary config) { var tagGroup = config.GetValueAs("tagGroup", defaultValue: string.Empty); - return _tagQuery.Value + // TODO: [LK:2021-09-20] Error with Tags data source on v9. + // FIXME: Cannot resolve scoped service 'Umbraco.Cms.Core.PublishedCache.ITagQuery' from root provider. + + return _tagQuery + .Value .GetAllTags(tagGroup) .OrderBy(x => x.Text) .Select(x => new DataListItem diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs index 221c6a43..9ca7c857 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUserGroupDataListSource.cs @@ -6,10 +6,17 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core.Models.Membership; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs index f2200e56..c9fe153f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoUsersDataListSource.cs @@ -7,21 +7,32 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models.Membership; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class UmbracoUsersDataListSource : IDataListSource, IDataListSourceValueConverter { + private readonly IIOHelper _ioHelper; private readonly IUserService _userService; - public UmbracoUsersDataListSource(IUserService userService) + public UmbracoUsersDataListSource(IIOHelper ioHelper, IUserService userService) { + _ioHelper = ioHelper; _userService = userService; } @@ -55,13 +66,13 @@ public IEnumerable Fields Key = "userGroup", Name = "User Group", Description = "Select a user group to filter the users by. If left empty, all users will be used.", - View = ItemPickerDataListEditor.DataEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorViewPath), Config = new Dictionary { { "enableFilter", items.Count > 5 ? Constants.Values.True : Constants.Values.False }, { "items", items }, { "listType", "list" }, - { "overlayView", IOHelper.ResolveUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, + { "overlayView", _ioHelper.ResolveRelativeOrVirtualUrl(ItemPickerDataListEditor.DataEditorOverlayViewPath) }, { "maxItems", 1 }, } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs index 5ffaee35..c03390e3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UserDefinedDataListSource.cs @@ -6,18 +6,34 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class UserDefinedDataListSource : IDataListSource { + private readonly IIOHelper _ioHelper; + + public UserDefinedDataListSource(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "User-defined List"; public string Description => "Manually configure the items for the data source."; - public string Icon => Core.Constants.Icons.DataType; + public string Icon => UmbConstants.Icons.DataType; public string Group => Constants.Conventions.DataSourceGroups.Data; @@ -28,18 +44,17 @@ public sealed class UserDefinedDataListSource : IDataListSource Key = "items", Name = "Options", Description = "Configure the option items for the data list.

Please try to avoid using duplicate values, as this may cause adverse issues with list editors.", - View = DataListDataEditor.DataEditorListEditorViewPath, + View = _ioHelper.ResolveRelativeOrVirtualUrl(DataListDataEditor.DataEditorListEditorViewPath), Config = new Dictionary() { { "confirmRemoval", Constants.Values.True }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, { MaxItemsConfigurationField.MaxItems, 0 }, - { NotesConfigurationField.Notes, @" -
- Advanced: Paste in the raw JSON? -

If you have copied the raw JSON from the Data List preview panel, .

-

The JSON format must be an array of the Data List item structure.
For example...

- [ + { NotesConfigurationField.Notes, @"
+Advanced: Paste in the raw JSON? +

If you have copied the raw JSON from the Data List preview panel, .

+

The JSON format must be an array of the Data List item structure.
For example...

+[ { ""name"": ""Ready"", ""value"": ""value1"", @@ -57,7 +72,7 @@ public sealed class UserDefinedDataListSource : IDataListSource ""description"": ""Three to get ready. Now go, cat, go."" } ] -
" }, +
" }, }, } }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index c5b3eecb..2e78d4cb 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -7,23 +7,56 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Xml; using System.Xml.XPath; +#if NET472 using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +#else +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { public sealed class XmlDataListSource : IDataListSource { + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + +#if NET472 private readonly ILogger _logger; - public XmlDataListSource(ILogger logger) + public XmlDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) + { + _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; + } +#else + private readonly ILogger _logger; + + public XmlDataListSource( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) { _logger = logger; + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; } +#endif public string Name => "XML Data"; @@ -44,7 +77,7 @@ public XmlDataListSource(ILogger logger) Description = "Enter the URL of the XML data source.
This can be either a remote URL, or local relative file path.", View = "textstring" }, - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with XPath expressions?

If you need assistance with XPath syntax, please refer to this resource: w3schools.com/xml.

@@ -109,7 +142,7 @@ public IEnumerable GetItems(Dictionary config) } var path = url.InvariantStartsWith("http") == false - ? IOHelper.MapPath(url) + ? _hostingEnvironment.MapPathWebRoot(url) : url; var doc = default(XPathDocument); @@ -118,13 +151,29 @@ public IEnumerable GetItems(Dictionary config) { doc = new XPathDocument(path); } + catch (HttpRequestException ex) + { +#if NET472 + _logger.Error(ex, $"Unable to retrieve data from '{path}'."); +#else + _logger.LogError(ex, $"Unable to retrieve data from '{path}'."); +#endif + } catch (WebException ex) { +#if NET472 _logger.Error(ex, $"Unable to retrieve data from '{path}'."); +#else + _logger.LogError(ex, $"Unable to retrieve data from '{path}'."); +#endif } catch (XmlException ex) { +#if NET472 _logger.Error(ex, "Unable to load XML data."); +#else + _logger.LogError(ex, $"Unable to load XML data from '{path}'."); +#endif } if (doc == null) @@ -158,7 +207,11 @@ public IEnumerable GetItems(Dictionary config) if (nodes.Count == 0) { +#if NET472 _logger.Warn($"The XPath '{itemsXPath}' did not match any items in the XML: {nav.OuterXml.Substring(0, Math.Min(300, nav.OuterXml.Length))}"); +#else + _logger.LogWarning($"The XPath '{itemsXPath}' did not match any items in the XML: {nav.OuterXml.Substring(0, Math.Min(300, nav.OuterXml.Length))}"); +#endif return Enumerable.Empty(); } @@ -198,12 +251,20 @@ public IEnumerable GetItems(Dictionary config) if (name == null) { +#if NET472 _logger.Warn($"The XPath '{nameXPath}' did not match a 'name' in the item XML: {outerXml}"); +#else + _logger.LogWarning($"The XPath '{nameXPath}' did not match a 'name' in the item XML: {outerXml}"); +#endif } if (value == null) { +#if NET472 _logger.Warn($"The XPath '{valueXPath}' did not match a 'value' in the item XML: {outerXml}"); +#else + _logger.LogWarning($"The XPath '{valueXPath}' did not match a 'value' in the item XML: {outerXml}"); +#endif } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapChangeFrequencyDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapChangeFrequencyDataListSource.cs index 00633050..6c5cc68f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapChangeFrequencyDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapChangeFrequencyDataListSource.cs @@ -5,8 +5,13 @@ using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapPriorityDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapPriorityDataListSource.cs index 246b5fd4..1e32d5ba 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapPriorityDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlSitemapPriorityDataListSource.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs index 609e93d9..45ed6bd3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/uCssClassNameDataListSource.cs @@ -7,14 +7,34 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +#if NET472 using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { public class uCssClassNameDataListSource : IDataListSource { + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + + public uCssClassNameDataListSource( + IHostingEnvironment hostingEnvironment, + IIOHelper ioHelper) + { + _hostingEnvironment = hostingEnvironment; + _ioHelper = ioHelper; + } + public string Name => "uCssClassName"; public string Description => "A homage to @marcemarc's bingo-famous uCssClassNameDropdown package!"; @@ -25,7 +45,7 @@ public class uCssClassNameDataListSource : IDataListSource public IEnumerable Fields => new[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
uCssClassName? What sort of a name is that?

Welcome to a piece of Umbraco package history.

First released back in 2013 by Marc Goodson, uCssClassNameDropdown became one of the most popular packages for Umbraco v4.11.3.

@@ -86,7 +106,7 @@ public IEnumerable GetItems(Dictionary config) var items = new HashSet(); - var path = IOHelper.MapPath(cssPath); + var path = _hostingEnvironment.MapPathWebRoot(cssPath); if (File.Exists(path) == true) { diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs index 8bc0740e..e264996e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs @@ -4,7 +4,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -13,7 +19,14 @@ public sealed class DropdownListDataListEditor : IDataListEditor internal const string AllowEmpty = "allowEmpty"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "dropdown-list.html"; - public string Name => "Dropdown List"; + private readonly IIOHelper _ioHelper; + + public DropdownListDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + + public string Name => "Dropdown List"; public string Description => "Select a single value from a dropdown select list."; @@ -34,7 +47,7 @@ public sealed class DropdownListDataListEditor : IDataListEditor { "default", Constants.Values.True } } }, - new HtmlAttributesConfigurationField(), + new HtmlAttributesConfigurationField(_ioHelper), }; public Dictionary DefaultValues => new Dictionary diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs index 10a234d1..db04e879 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerConfigurationEditor.cs @@ -4,14 +4,20 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { internal sealed class IconPickerConfigurationEditor : ConfigurationEditor { - public IconPickerConfigurationEditor() + public IconPickerConfigurationEditor(IIOHelper ioHelper) : base() { Fields.Add(new ConfigurationField @@ -19,7 +25,7 @@ public IconPickerConfigurationEditor() Key = "defaultIcon", Name = "Default icon", Description = "Select an icon to be displayed as the default icon, (for when no icon has been selected).", - View = IOHelper.ResolveUrl(IconPickerDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(IconPickerDataEditor.DataEditorViewPath), Config = new Dictionary { { "defaultIcon", string.Empty }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs index 383252a7..fd8072fa 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerDataEditor.cs @@ -3,8 +3,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +22,7 @@ namespace Umbraco.Community.Contentment.DataEditors DataEditorName, DataEditorViewPath, ValueType = ValueTypes.String, - Group = Core.Constants.PropertyEditors.Groups.Pickers, + Group = UmbConstants.PropertyEditors.Groups.Pickers, Icon = DataEditorIcon)] public sealed class IconPickerDataEditor : DataEditor { @@ -23,10 +31,19 @@ public sealed class IconPickerDataEditor : DataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "icon-picker.html"; internal const string DataEditorIcon = "icon-fa fa-circle-o"; - public IconPickerDataEditor(ILogger logger) + private readonly IIOHelper _ioHelper; + +#if NET472 + public IconPickerDataEditor(IIOHelper ioHelper, ILogger logger) : base(logger) - { } +#else + public IconPickerDataEditor(IIOHelper ioHelper, IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) +#endif + { + _ioHelper = ioHelper; + } - protected override IConfigurationEditor CreateConfigurationEditor() => new IconPickerConfigurationEditor(); + protected override IConfigurationEditor CreateConfigurationEditor() => new IconPickerConfigurationEditor(_ioHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs index e118455b..7e51f7bb 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/IconPicker/IconPickerValueConverter.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs index bdbc9819..186b165d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs @@ -4,9 +4,17 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -15,6 +23,13 @@ public sealed class ItemPickerDataListEditor : IDataListEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "item-picker.html"; internal const string DataEditorOverlayViewPath = Constants.Internals.EditorsPathRoot + "item-picker.overlay.html"; + private readonly IIOHelper _ioHelper; + + public ItemPickerDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + public string Name => "Item Picker"; public string Description => "Select items from an Umbraco style overlay."; @@ -30,7 +45,7 @@ public sealed class ItemPickerDataListEditor : IDataListEditor Key = "overlaySize", Name = "Editor overlay size", Description = "Select the size of the overlay editing panel. By default this is set to 'small'. However if the editor fields require a wider panel, please select 'medium' or 'large'.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -48,14 +63,14 @@ public sealed class ItemPickerDataListEditor : IDataListEditor Key = "defaultIcon", Name = "Default icon", Description = "Select an icon to be displayed as the default icon,
(for when no icon is available).", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), + View = _ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), }, new ConfigurationField { Key = "listType", Name = "List type", Description = "Select the style of list to be displayed in the overlay.", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] @@ -75,7 +90,7 @@ public sealed class ItemPickerDataListEditor : IDataListEditor { "default", Constants.Values.True } }, }, - new MaxItemsConfigurationField(), + new MaxItemsConfigurationField(_ioHelper), new AllowClearConfigurationField(), new ConfigurationField { @@ -104,14 +119,14 @@ public sealed class ItemPickerDataListEditor : IDataListEditor public Dictionary DefaultValues => new Dictionary { { "listType", "list" }, - { "defaultIcon", Core.Constants.Icons.DefaultIcon }, + { "defaultIcon", UmbConstants.Icons.DefaultIcon }, { EnableFilterConfigurationField.EnableFilter, Constants.Values.True }, { MaxItemsConfigurationField.MaxItems, "0" }, }; public Dictionary DefaultConfig => new Dictionary { - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, _ioHelper.ResolveRelativeOrVirtualUrl(DataEditorOverlayViewPath) }, { "overlayOrderBy", string.Empty }, }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs index 641d067e..2e0cb522 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationEditor.cs @@ -4,8 +4,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -13,7 +19,7 @@ internal sealed class NotesConfigurationEditor : ConfigurationEditor { internal const string Notes = NotesConfigurationField.Notes; - public NotesConfigurationEditor() + public NotesConfigurationEditor(IIOHelper ioHelper) : base() { Fields.Add(new ConfigurationField @@ -21,7 +27,7 @@ public NotesConfigurationEditor() Key = Notes, Name = nameof(Notes), Description = "Enter the notes to be displayed for the content editor.", - View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/rte/rte.html"), + View = ioHelper.ResolveRelativeOrVirtualUrl("~/umbraco/views/propertyeditors/rte/rte.html"), Config = new Dictionary { { "editor", Constants.Conventions.DefaultConfiguration.RichTextEditor } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs index 9517446e..e7daca6b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesConfigurationField.cs @@ -4,7 +4,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -12,12 +19,12 @@ internal class NotesConfigurationField : ConfigurationField { internal const string Notes = "notes"; - public NotesConfigurationField(string notes, bool hideLabel = true) + public NotesConfigurationField(IIOHelper ioHelper, string notes, bool hideLabel = true) : base() { Key = Notes; Name = nameof(Notes); - View = NotesDataEditor.DataEditorViewPath; + View = ioHelper.ResolveRelativeOrVirtualUrl(NotesDataEditor.DataEditorViewPath); Config = new Dictionary { { Notes, notes } }; HideLabel = hideLabel; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs index 463d4a0e..c99bd425 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs @@ -4,8 +4,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -16,6 +27,31 @@ public sealed class NotesDataEditor : IDataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "notes.html"; internal const string DataEditorIcon = "icon-fa fa-sticky-note-o"; + private readonly IIOHelper _ioHelper; + +#if NET472 + public NotesDataEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } +#else + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + + public NotesDataEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + { + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _ioHelper = ioHelper; + } +#endif + public string Alias => DataEditorAlias; public EditorType Type => EditorType.PropertyValue; @@ -32,11 +68,18 @@ public sealed class NotesDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new NotesConfigurationEditor(); + public IConfigurationEditor GetConfigurationEditor() => new NotesConfigurationEditor(_ioHelper); public IDataValueEditor GetValueEditor() { +#if NET472 return new ReadOnlyDataValueEditor +#else + return new ReadOnlyDataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { ValueType = ValueTypes.Integer, View = DataEditorViewPath, @@ -52,7 +95,14 @@ public IDataValueEditor GetValueEditor(object configuration) hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } +#if NET472 return new ReadOnlyDataValueEditor +#else + return new ReadOnlyDataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { Configuration = configuration, HideLabel = hideLabel, diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs index 2363f91e..07a48164 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesValueConverter.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs index 262a8396..64dc1a24 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputConfigurationEditor.cs @@ -4,14 +4,20 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { internal sealed class NumberInputConfigurationEditor : ConfigurationEditor { - public NumberInputConfigurationEditor() + public NumberInputConfigurationEditor(IIOHelper ioHelper) : base() { DefaultConfiguration.Add("size", "s"); @@ -21,7 +27,7 @@ public NumberInputConfigurationEditor() Key = "size", Name = "Numeric size", Description = "How big will the number get?", - View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs index e3db273f..4fc39cb2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputDataEditor.cs @@ -3,8 +3,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +22,7 @@ namespace Umbraco.Community.Contentment.DataEditors DataEditorName, DataEditorViewPath, ValueType = ValueTypes.Integer, - Group = Core.Constants.PropertyEditors.Groups.Common, + Group = UmbConstants.PropertyEditors.Groups.Common, Icon = DataEditorIcon)] internal sealed class NumberInputDataEditor : DataEditor { @@ -23,10 +31,23 @@ internal sealed class NumberInputDataEditor : DataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "number-input.html"; internal const string DataEditorIcon = "icon-coin"; - public NumberInputDataEditor(ILogger logger) + private readonly IIOHelper _ioHelper; + +#if NET472 + public NumberInputDataEditor(IIOHelper ioHelper, ILogger logger) : base(logger) - { } + { + _ioHelper = ioHelper; + } +#else + public NumberInputDataEditor(IIOHelper ioHelper, IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) + { + _ioHelper = ioHelper; + } +#endif + - protected override IConfigurationEditor CreateConfigurationEditor() => new NumberInputConfigurationEditor(); + protected override IConfigurationEditor CreateConfigurationEditor() => new NumberInputConfigurationEditor(_ioHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs index 66425714..527129f2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs index 2ac3e3e5..066f04c6 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RadioButtonList/RadioButtonListDataListEditor.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs index e86a4d60..4500aab5 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/ReadOnlyDataValueEditor.cs @@ -3,12 +3,28 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +#endif namespace Umbraco.Community.Contentment.DataEditors { internal sealed class ReadOnlyDataValueEditor : DataValueEditor { +#if NET472 == false + public ReadOnlyDataValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) + : base(localizedTextService, shortStringHelper, jsonSerializer) + { } +#endif + public override bool IsReadOnly => true; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs index b656abea..911e2168 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroConfigurationEditor.cs @@ -4,8 +4,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -13,7 +19,7 @@ internal sealed class RenderMacroConfigurationEditor : ConfigurationEditor { internal const string Macro = "macro"; - public RenderMacroConfigurationEditor() + public RenderMacroConfigurationEditor(IIOHelper ioHelper) : base() { Fields.Add(new ConfigurationField @@ -21,7 +27,7 @@ public RenderMacroConfigurationEditor() Key = Macro, Name = nameof(Macro), Description = "Select and configure the macro to be displayed.", - View = IOHelper.ResolveUrl(MacroPickerDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(MacroPickerDataEditor.DataEditorViewPath), Config = new Dictionary { { MaxItemsConfigurationField.MaxItems, 1 } diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs index 5858f4a1..c3fcc4ed 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs @@ -4,8 +4,21 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +27,32 @@ public sealed class RenderMacroDataEditor : IDataEditor internal const string DataEditorAlias = Constants.Internals.DataEditorAliasPrefix + "RenderMacro"; internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Render Macro"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "render-macro.html"; - internal const string DataEditorIcon = Core.Constants.Icons.Macro; + internal const string DataEditorIcon = UmbConstants.Icons.Macro; + + private readonly IIOHelper _ioHelper; + +#if NET472 + public RenderMacroDataEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } +#else + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + + public RenderMacroDataEditor( + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) + { + _ioHelper = ioHelper; + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + } +#endif public string Alias => DataEditorAlias; @@ -32,11 +70,18 @@ public sealed class RenderMacroDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new RenderMacroConfigurationEditor(); + public IConfigurationEditor GetConfigurationEditor() => new RenderMacroConfigurationEditor(_ioHelper); public IDataValueEditor GetValueEditor() { +#if NET472 return new ReadOnlyDataValueEditor +#else + return new ReadOnlyDataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { ValueType = ValueTypes.Integer, View = DataEditorViewPath, @@ -52,7 +97,14 @@ public IDataValueEditor GetValueEditor(object configuration) hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } +#if NET472 return new ReadOnlyDataValueEditor +#else + return new ReadOnlyDataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { Configuration = configuration, HideLabel = hideLabel, diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs index 49af4063..cb899b78 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroValueConverter.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs index 48d2faff..448f7272 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs index 38bab5cf..113297d0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelConfigurationEditor.cs @@ -4,15 +4,22 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { internal sealed class TemplatedLabelConfigurationEditor : ConfigurationEditor { - public TemplatedLabelConfigurationEditor() + public TemplatedLabelConfigurationEditor(IIOHelper ioHelper) : base() { var valueTypes = new[] @@ -36,7 +43,7 @@ public TemplatedLabelConfigurationEditor() Key = UmbConstants.PropertyEditors.ConfigurationKeys.DataValueType, Name = "Value type", Description = "Select the value's type. This defines how the underlying value is stored in the database.", - View = IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(DropdownListDataListEditor.DataEditorViewPath), Config = new Dictionary { { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, @@ -44,7 +51,7 @@ public TemplatedLabelConfigurationEditor() } }); - Fields.Add(new NotesConfigurationField(@"
+ Fields.Add(new NotesConfigurationField(ioHelper, @"
Do you need help with your custom template?

Your custom template will be used to display the label on the property from the underlying value.

If you are familiar with AngularJS template syntax, you can display the value using an expression: e.g. {{ model.value }}.

@@ -62,7 +69,7 @@ public TemplatedLabelConfigurationEditor() Key = "notes", Name = "Template", Description = "Enter the AngularJS template to be displayed for the label.", - View = IOHelper.ResolveUrl(CodeEditorDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, "razor" }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs index 3c03bcc8..bd3d4d0b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelDataEditor.cs @@ -4,8 +4,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -16,6 +27,31 @@ public sealed class TemplatedLabelDataEditor : IDataEditor internal const string DataEditorViewPath = NotesDataEditor.DataEditorViewPath; internal const string DataEditorIcon = "icon-fa fa-codepen"; + private readonly IIOHelper _ioHelper; + +#if NET472 + public TemplatedLabelDataEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } +#else + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + + public TemplatedLabelDataEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + { + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _ioHelper = ioHelper; + } +#endif + public string Alias => DataEditorAlias; public EditorType Type => EditorType.PropertyValue; @@ -32,11 +68,18 @@ public sealed class TemplatedLabelDataEditor : IDataEditor public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - public IConfigurationEditor GetConfigurationEditor() => new TemplatedLabelConfigurationEditor(); + public IConfigurationEditor GetConfigurationEditor() => new TemplatedLabelConfigurationEditor(_ioHelper); public IDataValueEditor GetValueEditor() { +#if NET472 return new DataValueEditor +#else + return new DataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { View = DataEditorViewPath, }; @@ -51,7 +94,14 @@ public IDataValueEditor GetValueEditor(object configuration) hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } +#if NET472 return new DataValueEditor +#else + return new DataValueEditor( + _localizedTextService, + _shortStringHelper, + _jsonSerializer) +#endif { Configuration = configuration, HideLabel = hideLabel, diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs index 9bf0bbe6..6571e066 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedLabel/TemplatedLabelValueConverter.cs @@ -5,11 +5,19 @@ using System; using System.Collections.Generic; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using UmbConstants = Umbraco.Cms.Core.Constants; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs index c7fa2582..ad23fee3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TemplatedList/TemplatedListDataListEditor.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +20,14 @@ public sealed class TemplatedListDataListEditor : IDataListEditor { internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "templated-list.html"; - public string Name => "Templated List"; + private readonly IIOHelper _ioHelper; + + public TemplatedListDataListEditor(IIOHelper ioHelper) + { + _ioHelper = ioHelper; + } + + public string Name => "Templated List"; public string Description => "Select items from a list rendered with custom markup."; @@ -24,7 +37,7 @@ public sealed class TemplatedListDataListEditor : IDataListEditor public IEnumerable Fields => new ConfigurationField[] { - new NotesConfigurationField(@"
+ new NotesConfigurationField(_ioHelper, @"
Do you need help with your custom template?

Your custom template will be used to display an individual item from your configured data source.

The data for the item will be in the following format:

@@ -47,7 +60,7 @@ public sealed class TemplatedListDataListEditor : IDataListEditor { Key = "template", Name = "Template", - View = IOHelper.ResolveUrl(CodeEditorDataEditor.DataEditorViewPath), + View = _ioHelper.ResolveRelativeOrVirtualUrl(CodeEditorDataEditor.DataEditorViewPath), Config = new Dictionary { { CodeEditorConfigurationEditor.Mode, "razor" }, @@ -63,7 +76,7 @@ public sealed class TemplatedListDataListEditor : IDataListEditor Description = "Select to enable picking multiple items.", View = "boolean", }, - new HtmlAttributesConfigurationField(), + new HtmlAttributesConfigurationField(_ioHelper), }; public Dictionary DefaultConfig => default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs index ab576c66..31c5dd03 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs @@ -6,9 +6,17 @@ using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; +#if NET472 using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -16,12 +24,13 @@ internal sealed class TextInputConfigurationEditor : ConfigurationEditor { private readonly ConfigurationEditorUtility _utility; - public TextInputConfigurationEditor(ConfigurationEditorUtility utility) + public TextInputConfigurationEditor(ConfigurationEditorUtility utility, IIOHelper ioHelper, IShortStringHelper shortStringHelper) + : base() { _utility = utility; - var dataSources = new List(_utility.GetConfigurationEditorModels()); + var dataSources = new List(_utility.GetConfigurationEditorModels(shortStringHelper)); Fields.Add(new ConfigurationField { @@ -36,13 +45,13 @@ public TextInputConfigurationEditor(ConfigurationEditorUtility utility) Key = Constants.Conventions.ConfigurationFieldAliases.Items, Name = "Data list", Description = "(optional) Select and configure a data source to provide a HTML5 <datalist> for this text input.", - View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorViewPath), Config = new Dictionary() { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureDataSource" }, { MaxItemsConfigurationField.MaxItems, 1 }, { DisableSortingConfigurationField.DisableSorting, Constants.Values.True }, - { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, ioHelper.ResolveRelativeOrVirtualUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, { EnableFilterConfigurationField.EnableFilter, dataSources.Count > 10 ? Constants.Values.True : Constants.Values.False }, { Constants.Conventions.ConfigurationFieldAliases.Items, dataSources }, @@ -54,7 +63,7 @@ public TextInputConfigurationEditor(ConfigurationEditorUtility utility) Name = "Maximum allowed characters", Key = "maxChars", Description = "Enter the maximum number of characters allowed for the text input.
The default limit is 500 characters.", - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath), + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath), }); Fields.Add(new ConfigurationField diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs index 2a2447a0..9668c18e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputDataEditor.cs @@ -3,8 +3,18 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Strings; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -14,7 +24,7 @@ namespace Umbraco.Community.Contentment.DataEditors DataEditorName, DataEditorViewPath, ValueType = ValueTypes.String, - Group = Core.Constants.PropertyEditors.Groups.Common, + Group = UmbConstants.PropertyEditors.Groups.Common, Icon = DataEditorIcon)] public sealed class TextInputDataEditor : DataEditor { @@ -23,14 +33,36 @@ public sealed class TextInputDataEditor : DataEditor internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "text-input.html"; internal const string DataEditorIcon = "icon-autofill"; + private readonly IIOHelper _ioHelper; + private readonly IShortStringHelper _shortStringHelper; private readonly ConfigurationEditorUtility _utility; - public TextInputDataEditor(ILogger logger, ConfigurationEditorUtility utility) +#if NET472 + public TextInputDataEditor( + ConfigurationEditorUtility utility, + IIOHelper ioHelper, + IShortStringHelper shortStringHelper, + ILogger logger) : base(logger) { _utility = utility; + _ioHelper = ioHelper; + _shortStringHelper = shortStringHelper; } +#else + public TextInputDataEditor( + ConfigurationEditorUtility utility, + IIOHelper ioHelper, + IShortStringHelper shortStringHelper, + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) + { + _utility = utility; + _ioHelper = ioHelper; + _shortStringHelper = shortStringHelper; + } +#endif - protected override IConfigurationEditor CreateConfigurationEditor() => new TextInputConfigurationEditor(_utility); + protected override IConfigurationEditor CreateConfigurationEditor() => new TextInputConfigurationEditor(_utility, _ioHelper, _shortStringHelper); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs index 2f040060..0e659fbb 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputValueConverter.cs @@ -4,9 +4,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs index 5ae52781..94151f88 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/AllowClearConfigurationField.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs index 865b6786..abd507cc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/DisableSortingConfigurationField.cs @@ -3,7 +3,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs index 95a30b63..933038eb 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableDevModeConfigurationField.cs @@ -3,7 +3,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs index aca4fc7e..890fab0a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/EnableFilterConfigurationField.cs @@ -3,7 +3,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs index 332f7342..c8ae62c2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HideLabelConfigurationField.cs @@ -3,7 +3,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs index a927a02f..58545408 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/HtmlAttributesConfigurationField.cs @@ -4,8 +4,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -13,7 +19,7 @@ internal sealed class HtmlAttributesConfigurationField : ConfigurationField { internal const string HtmlAttributes = "htmlAttributes"; - public HtmlAttributesConfigurationField() + public HtmlAttributesConfigurationField(IIOHelper ioHelper) : base() { var listFields = new[] @@ -35,7 +41,7 @@ public HtmlAttributesConfigurationField() Key = HtmlAttributes; Name = "HTML attributes"; Description = "(optional) Use this field to add any HTML attributes to the list editor."; - View = IOHelper.ResolveUrl(DataTableDataEditor.DataEditorViewPath); + View = ioHelper.ResolveRelativeOrVirtualUrl(DataTableDataEditor.DataEditorViewPath); Config = new Dictionary() { { DataTableConfigurationEditor.FieldItems, listFields }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs index cfa9abf3..3a59d037 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/MaxItemsConfigurationField.cs @@ -3,8 +3,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.DataEditors { @@ -12,13 +18,13 @@ internal sealed class MaxItemsConfigurationField : ConfigurationField { internal const string MaxItems = "maxItems"; - public MaxItemsConfigurationField() + public MaxItemsConfigurationField(IIOHelper ioHelper) : base() { Key = MaxItems; Name = "Maximum items"; Description = "Enter the number for the maximum items allowed.
Use '0' for an unlimited amount."; - View = IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath); + View = ioHelper.ResolveRelativeOrVirtualUrl(NumberInputDataEditor.DataEditorViewPath); } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs index 048da91a..48a2ceba 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowDescriptionsConfigurationField.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs index 4890779d..f59c3f02 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/ConfigurationFields/ShowIconsConfigurationField.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs index 8cb9ebbc..c4e5e36c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentEditorItem.cs @@ -5,7 +5,11 @@ using System.Collections.Generic; using System.ComponentModel; +#if NET472 using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs index 100626ff..8c4704de 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/_/IContentmentListItem.cs @@ -4,7 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.ComponentModel; +#if NET472 using Umbraco.Core.Composing; +#else +using Umbraco.Cms.Core.Composing; +#endif namespace Umbraco.Community.Contentment.DataEditors { diff --git a/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs b/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs index 15341e28..5d44898c 100644 --- a/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs +++ b/src/Umbraco.Community.Contentment/Migrations/ContentmentPlan.cs @@ -3,7 +3,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Core.Migrations; +#else +using Umbraco.Cms.Infrastructure.Migrations; +#endif namespace Umbraco.Community.Contentment.Migrations { diff --git a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs index 3930120e..2e52f5be 100644 --- a/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs +++ b/src/Umbraco.Community.Contentment/Migrations/Install/RegisterUmbracoPackageEntry.cs @@ -3,13 +3,18 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System.Linq; using Umbraco.Core.Migrations; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Services; +#else +using Umbraco.Cms.Infrastructure.Migrations; +#endif namespace Umbraco.Community.Contentment.Migrations { +#if NET472 internal sealed class RegisterUmbracoPackageEntry : MigrationBase { private readonly IPackagingService _packagingService; @@ -39,10 +44,26 @@ public override void Migrate() License = Constants.Package.License, LicenseUrl = Constants.Package.LicenseUrl, UmbracoVersion = Constants.Package.MinimumSupportedUmbracoVersion, - Version = Configuration.ContentmentVersion.Version.ToString(), + Version = ContentmentVersion.Version.ToString(), Readme = "", }); } } } +#else + internal sealed class RegisterUmbracoPackageEntry : MigrationBase + { + public const string State = "{contentment-reg-umb-pkg-entry}"; + + public RegisterUmbracoPackageEntry(IMigrationContext context) + : base(context) + { } + + protected override void Migrate() + { + // NOTE: This migration does nothing. It is a left over from code targeting Umbraco 8. + // It needs to remain, as Umbraco instances that have been upgraded from v8 to v9 will have reached this migrated state. + } + } +#endif } diff --git a/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsingNotification.cs b/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsingNotification.cs new file mode 100644 index 00000000..c919d226 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Notifications/ContentmentServerVariablesParsingNotification.cs @@ -0,0 +1,39 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 == false +using System.Collections.Generic; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Community.Contentment.Notifications +{ + internal sealed class ContentmentServerVariablesParsingNotification : INotificationHandler + { + private readonly ContentmentSettings _contentmentSettings; + + public ContentmentServerVariablesParsingNotification(IOptions contentmentSettings) + { + _contentmentSettings = contentmentSettings.Value; + } + + public void Handle(ServerVariablesParsingNotification notification) + { + if (notification.ServerVariables.TryGetValueAs("umbracoPlugins", out Dictionary umbracoPlugins) == true && + umbracoPlugins.ContainsKey(Constants.Internals.ProjectAlias) == false) + { + umbracoPlugins.Add(Constants.Internals.ProjectAlias, new + { + name = Constants.Internals.ProjectName, + version = ContentmentVersion.SemanticVersion.ToSemanticString(), + telemetry = _contentmentSettings.DisableTelemetry == false, + }); + } + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Notifications/ContentmentTelemetryNotification.cs b/src/Umbraco.Community.Contentment/Notifications/ContentmentTelemetryNotification.cs new file mode 100644 index 00000000..a5f43b20 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Notifications/ContentmentTelemetryNotification.cs @@ -0,0 +1,130 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 == false +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Community.Contentment.DataEditors; +using Umbraco.Extensions; + +namespace Umbraco.Community.Contentment.Notifications +{ + internal sealed class ContentmentTelemetryNotification : INotificationHandler + { + private readonly ContentmentSettings _contentmentSettings; + private readonly GlobalSettings _globalSettings; + private readonly IUmbracoVersion _umbracoVersion; + + public ContentmentTelemetryNotification( + IOptions contentmentSettings, + IOptions globalSettings, + IUmbracoVersion umbracoVersion) + { + _contentmentSettings = contentmentSettings.Value; + _globalSettings = globalSettings.Value; + _umbracoVersion = umbracoVersion; + } + + public void Handle(DataTypeSavedNotification notification) + { + if (_contentmentSettings.DisableTelemetry == true) + { + return; + } + + foreach (var entity in notification.SavedEntities) + { + if (entity.EditorAlias.InvariantStartsWith(Constants.Internals.DataEditorAliasPrefix) == true) + { + try + { + var dataTypeConfig = new Dictionary(); + + if (entity.Configuration is Dictionary config) + { + void AddConfigurationEditorKey(string alias) + { + if (config.ContainsKey(alias) == true && + config.TryGetValueAs(alias, out JArray array) == true && + array.Count > 0 && + array[0] is JObject item && + item.ContainsKey("key") == true) + { + var key = item.Value("key"); + + if (key.InvariantStartsWith(Constants.Internals.ProjectNamespace) == true && key.Length > 73) + { + // Strips off the namespace and assembly. + // e.g. "Umbraco.Community.Contentment.DataEditors.[DataSourceName], Umbraco.Community.Contentment" + key = key.Substring(42, key.Length - 73); + } + + dataTypeConfig.Add(alias, key); + } + }; + + switch (entity.EditorAlias) + { + case DataListDataEditor.DataEditorAlias: + AddConfigurationEditorKey(DataListConfigurationEditor.DataSource); + AddConfigurationEditorKey(DataListConfigurationEditor.ListEditor); + break; + + case ContentBlocksDataEditor.DataEditorAlias: + AddConfigurationEditorKey(ContentBlocksConfigurationEditor.DisplayMode); + break; + + case TextInputDataEditor.DataEditorAlias: + AddConfigurationEditorKey(Constants.Conventions.ConfigurationFieldAliases.Items); + break; + + default: + break; + } + } + + var umbracoId = Guid.TryParse(_globalSettings.Id, out var telemetrySiteIdentifier) == true + ? telemetrySiteIdentifier + : Guid.Empty; + + // No identifiable details, just a quick call home. + var data = new + { + dataType = entity.Key, + editorAlias = entity.EditorAlias.Substring(Constants.Internals.DataEditorAliasPrefix.Length), + umbracoId = umbracoId, + umbracoVersion = _umbracoVersion.SemanticVersion.ToString(), + contentmentVersion = ContentmentVersion.SemanticVersion.ToString(), + dataTypeConfig = dataTypeConfig, + }; + + using (var client = new WebClient()) + { + var address = new Uri("https://leekelleher.com/umbraco/contentment/telemetry/"); + var json = JsonConvert.SerializeObject(data, Formatting.Indented); + var payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(json)); + + client.Headers.Add("Content-Type", MediaTypeNames.Text.Plain); + Task.Run(() => client.UploadStringAsync(address, payload)); + } + } + catch { /* ¯\_(ツ)_/¯ */ } + } + } + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs b/src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs new file mode 100644 index 00000000..23d22575 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Notifications/ContentmentUmbracoApplicationStartingNotification.cs @@ -0,0 +1,41 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 == false +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Trees; +using Umbraco.Community.Contentment.Trees; +using Umbraco.Extensions; + +namespace Umbraco.Community.Contentment.Notifications +{ + internal class ContentmentUmbracoApplicationStartingNotification : INotificationHandler + { + private readonly ContentmentSettings _contentmentSettings; + private readonly TreeCollection _treeCollection; + + public ContentmentUmbracoApplicationStartingNotification( + IOptions contentmentSettings, + TreeCollection treeCollection) + { + _contentmentSettings = contentmentSettings.Value; + _treeCollection = treeCollection; + } + + public void Handle(UmbracoApplicationStartingNotification notification) + { + if (notification.RuntimeLevel == RuntimeLevel.Run && + _contentmentSettings.DisableTree == true && + _treeCollection != null) + { + _treeCollection.RemoveTreeController(); + } + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Polyfill/IHostingEnvironment.cs b/src/Umbraco.Community.Contentment/Polyfill/IHostingEnvironment.cs new file mode 100644 index 00000000..749f49e8 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Polyfill/IHostingEnvironment.cs @@ -0,0 +1,30 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 +using System.ComponentModel; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Hosting +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IHostingEnvironment + { + string MapPathWebRoot(string path); + } +} + +namespace Umbraco.Community.Contentment.Polyfill +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal sealed class ContenmentHostingEnvironment : Core.Hosting.IHostingEnvironment + { + public string MapPathWebRoot(string path) + { + return IOHelper.MapPath(path); + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Polyfill/IIOHelper.cs b/src/Umbraco.Community.Contentment/Polyfill/IIOHelper.cs new file mode 100644 index 00000000..49663691 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Polyfill/IIOHelper.cs @@ -0,0 +1,29 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 +using System.ComponentModel; + +namespace Umbraco.Core.IO +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IIOHelper + { + string ResolveRelativeOrVirtualUrl(string path); + } +} + +namespace Umbraco.Community.Contentment.Polyfill +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal sealed class ContenmentIOHelper : Core.IO.IIOHelper + { + public string ResolveRelativeOrVirtualUrl(string path) + { + return Core.IO.IOHelper.ResolveUrl(path); + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Polyfill/PublishedSnapshotAccessorExtensions.cs b/src/Umbraco.Community.Contentment/Polyfill/PublishedSnapshotAccessorExtensions.cs new file mode 100644 index 00000000..b824b445 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Polyfill/PublishedSnapshotAccessorExtensions.cs @@ -0,0 +1,20 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 +using System.ComponentModel; + +namespace Umbraco.Web.PublishedCache +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class PublishedSnapshotAccessorExtensions + { + public static IPublishedSnapshot GetRequiredPublishedSnapshot(this IPublishedSnapshotAccessor accessor) + { + return accessor.PublishedSnapshot; + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Polyfill/README.md b/src/Umbraco.Community.Contentment/Polyfill/README.md new file mode 100644 index 00000000..b94e0dd1 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Polyfill/README.md @@ -0,0 +1,5 @@ +## Polyfills + +These polyfill classes are to support Contentment multi-targeting Umbraco v8 and v9. +Having this class injected for v8, will reduce the number of #if/#else directives needed. + diff --git a/src/Umbraco.Community.Contentment/Polyfill/StringExtensions.cs b/src/Umbraco.Community.Contentment/Polyfill/StringExtensions.cs new file mode 100644 index 00000000..722ccca0 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Polyfill/StringExtensions.cs @@ -0,0 +1,22 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 +using System.ComponentModel; +using Umbraco.Core.Strings; + +namespace Umbraco.Core +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class StringExtensions + { + public static string SplitPascalCasing(this string phrase, IShortStringHelper shortStringHelper) + { + // https://github.com/umbraco/Umbraco-CMS/blob/release-8.17.0/src/Umbraco.Core/StringExtensions.cs#L1191 + return shortStringHelper.SplitPascalCasing(phrase, ' '); + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Polyfill/UdiParser.cs b/src/Umbraco.Community.Contentment/Polyfill/UdiParser.cs new file mode 100644 index 00000000..aa415fd6 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Polyfill/UdiParser.cs @@ -0,0 +1,20 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 +using System.ComponentModel; + +namespace Umbraco.Core +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal sealed class UdiParser + { + public static bool TryParse(string s, out GuidUdi udi) + { + return GuidUdi.TryParse(s, out udi); + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Polyfill/UmbracoContextAccessorExtensions.cs b/src/Umbraco.Community.Contentment/Polyfill/UmbracoContextAccessorExtensions.cs new file mode 100644 index 00000000..c34108ad --- /dev/null +++ b/src/Umbraco.Community.Contentment/Polyfill/UmbracoContextAccessorExtensions.cs @@ -0,0 +1,20 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#if NET472 +using System.ComponentModel; + +namespace Umbraco.Web +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class UmbracoContextAccessorExtensions + { + public static UmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor accessor) + { + return accessor.UmbracoContext; + } + } +} +#endif diff --git a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs index 84b57a17..b396668a 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/CompositionExtensions.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Community.Contentment.Telemetry; // NOTE: This extension method class is deliberately using the Umbraco namespace, @@ -36,3 +37,4 @@ public static Composition DisableContentmentTelemetry(this Composition compositi } } } +#endif diff --git a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs index e202d5de..0dcb44dc 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System; using System.Collections.Generic; using System.Net; @@ -11,7 +12,6 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Community.Contentment.Configuration; using Umbraco.Community.Contentment.DataEditors; using Umbraco.Core; using Umbraco.Core.Composing; @@ -134,3 +134,4 @@ array[0] is JObject item && } } } +#endif diff --git a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs b/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs index b6d04d71..9e98f1af 100644 --- a/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs +++ b/src/Umbraco.Community.Contentment/Trees/CompositionExtensions.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Community.Contentment.Trees; using Umbraco.Web; @@ -23,3 +24,4 @@ public static Composition DisableContentmentTree(this Composition composition) } } } +#endif diff --git a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs index 19fa13c3..851869ff 100644 --- a/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs +++ b/src/Umbraco.Community.Contentment/Trees/ContentmentTreeController.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System.Net.Http.Formatting; using System.Web.Http.ModelBinding; using Umbraco.Web.Models.Trees; @@ -10,9 +11,22 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi.Filters; using UmbConstants = Umbraco.Core.Constants; +#else +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Web.BackOffice.Trees; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.ModelBinders; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Community.Contentment.Trees { +#if NET472 [Tree( UmbConstants.Applications.Settings, Constants.Internals.TreeAlias, @@ -39,4 +53,39 @@ protected override TreeNode CreateRootNode(FormDataCollection queryStrings) protected override TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) => null; } +#else + [Tree( + UmbConstants.Applications.Settings, + Constants.Internals.TreeAlias, + IsSingleNodeTree = true, + TreeGroup = UmbConstants.Trees.Groups.ThirdParty, + TreeTitle = Constants.Internals.ProjectName, + TreeUse = TreeUse.Main)] + [PluginController(Constants.Internals.PluginControllerName)] + public sealed class ContentmentTreeController : TreeController + { + public ContentmentTreeController( + ILocalizedTextService localizedTextService, + UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, + IEventAggregator eventAggregator) + : base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator) + { } + + protected override ActionResult CreateRootNode(FormCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + root.Value.Icon = "icon-fa fa-cube"; + root.Value.HasChildren = false; + root.Value.RoutePath = $"{SectionAlias}/{TreeAlias}/index"; + root.Value.MenuUrl = null; + + return root.Value; + } + + protected override ActionResult GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) => null; + + protected override ActionResult GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) => null; + } +#endif } diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 39bee987..cdde0bd4 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -1,13 +1,13 @@  - net472 + net472;net50 Umbraco.Community.Contentment Our.Umbraco.Community.Contentment Contentment for Umbraco - Contentment, a collection of components for Umbraco 8. + Contentment, a collection of components for Umbraco. umbraco - 2.2.0 + 3.0.0-beta001 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher @@ -16,10 +16,19 @@ https://github.com/leekelleher/umbraco-contentment git - - + + + + + + + + + + + - + diff --git a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs index 81229659..17bdbb8c 100644 --- a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs +++ b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs @@ -6,12 +6,23 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Reflection; using Umbraco.Community.Contentment.DataEditors; +#if NET472 +using System.Web.Http; using Umbraco.Core; +using Umbraco.Core.Strings; using Umbraco.Web.Editors; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; +#else +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.Web.Controllers { @@ -22,6 +33,14 @@ public sealed class EnumDataSourceApiController : UmbracoAuthorizedJsonControlle internal const string GetAssembliesUrl = "backoffice/Contentment/EnumDataSourceApi/GetAssemblies"; internal const string GetEnumsUrl = "backoffice/Contentment/EnumDataSourceApi/GetEnums?assembly={0}"; + private readonly IShortStringHelper _shortStringHelper; + + public EnumDataSourceApiController(IShortStringHelper shortStringHelper) + { + _shortStringHelper = shortStringHelper; + } + + [HttpGet] public IEnumerable GetAssemblies() { const string App_Code = "App_Code"; @@ -39,17 +58,23 @@ public IEnumerable GetAssemblies() } var hasEnums = false; - if (assembly.ExportedTypes != null) + try { - foreach (var exportedType in assembly.ExportedTypes) + var exportedTypes = assembly.GetExportedTypes(); + if (exportedTypes != null) { - if (exportedType.IsEnum == true) + foreach (var exportedType in exportedTypes) { - hasEnums = true; - break; + if (exportedType.IsEnum == true) + { + hasEnums = true; + break; + } } } } + catch (FileLoadException) { /* (╯°□°)╯︵ ┻━┻ */ } + catch (TypeLoadException) { /* ¯\_(ツ)_/¯ */ } if (hasEnums == false) { @@ -71,11 +96,13 @@ public IEnumerable GetAssemblies() return options.Values; } + [HttpGet] public IEnumerable GetEnums(string assembly) { var options = new SortedDictionary(); var types = Assembly.Load(assembly).GetTypes(); + foreach (var type in types) { if (type.IsEnum == false) @@ -83,7 +110,7 @@ public IEnumerable GetEnums(string assembly) continue; } - options.Add(type.FullName, new DataListItem { Name = type.Name.SplitPascalCasing(), Value = type.FullName }); + options.Add(type.FullName, new DataListItem { Name = type.Name.SplitPascalCasing(_shortStringHelper), Value = type.FullName }); } return options.Values; diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs index 6bb2eb11..920bee4a 100644 --- a/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs +++ b/src/Umbraco.Community.Contentment/Web/Extensions/HtmlHelperExtensions.cs @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using System; using System.Collections.Generic; using System.Web.Mvc; @@ -83,3 +84,4 @@ public static HelperResult RenderElements( } } } +#endif diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs index 8ea74232..05930a4e 100644 --- a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs +++ b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedContentTypeExtensions.cs @@ -3,10 +3,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#if NET472 using Umbraco.Community.Contentment.DataEditors; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using UmbConstants = Umbraco.Core.Constants; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Community.Contentment.DataEditors; +using UmbConstants = Umbraco.Cms.Core.Constants; +#endif namespace Umbraco.Web { diff --git a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs index f1e8b93a..01af95d2 100644 --- a/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs +++ b/src/Umbraco.Community.Contentment/Web/Extensions/PublishedElementExtensions.cs @@ -6,10 +6,19 @@ using System; using System.Linq.Expressions; using System.Reflection; +#if NET472 using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Embedded; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +#endif +#if NET472 namespace Umbraco.Web +#else +namespace Umbraco.Extensions +#endif { public static class PublishedElementExtensions { diff --git a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs index cd74a644..d260fb3f 100644 --- a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs +++ b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs @@ -11,7 +11,11 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET472 using Umbraco.Core.Models.PublishedContent; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +#endif namespace Umbraco.Community.Contentment.Web.PublishedCache { diff --git a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs index 76584a0f..19406269 100644 --- a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs +++ b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs @@ -9,8 +9,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +#if NET472 using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +#endif namespace Umbraco.Community.Contentment.Web.PublishedCache { diff --git a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs index f17d84f8..4f6073bf 100644 --- a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs +++ b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs @@ -17,7 +17,12 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; +#if NET472 using Umbraco.Core.Models.PublishedContent; +#else +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; +#endif namespace Umbraco.Community.Contentment.Web.Serialization { @@ -31,6 +36,9 @@ public sealed class PublishedContentContractResolver : DefaultContractResolver private readonly HashSet _ignoreFromContent; private readonly HashSet _ignoreFromProperty; private readonly HashSet _systemProperties; +#if NET472 == false + private readonly Dictionary> _systemMethods; +#endif public PublishedContentContractResolver() : base() @@ -73,9 +81,13 @@ public PublishedContentContractResolver() _systemProperties = new HashSet(StringComparer.OrdinalIgnoreCase) { nameof(IPublishedContent.CreateDate), +#if NET472 #pragma warning disable CS0618 // Type or member is obsolete nameof(IPublishedContent.CreatorName), #pragma warning restore CS0618 // Type or member is obsolete +#else + nameof(FriendlyPublishedContentExtensions.CreatorName), +#endif nameof(IPublishedContent.Id), nameof(IPublishedContent.ItemType), nameof(IPublishedElement.Key), @@ -84,14 +96,31 @@ public PublishedContentContractResolver() nameof(IPublishedContent.Path), nameof(IPublishedContent.SortOrder), nameof(IPublishedContent.UpdateDate), +#if NET472 #pragma warning disable CS0618 // Type or member is obsolete nameof(IPublishedContent.Url), #pragma warning restore CS0618 // Type or member is obsolete +#else + nameof(FriendlyPublishedContentExtensions.Url), +#endif nameof(IPublishedContent.UrlSegment), +#if NET472 #pragma warning disable CS0618 // Type or member is obsolete nameof(IPublishedContent.WriterName), #pragma warning restore CS0618 // Type or member is obsolete +#else + nameof(FriendlyPublishedContentExtensions.WriterName), +#endif }; + +#if NET472 == false + _systemMethods = new Dictionary> + { + { nameof(FriendlyPublishedContentExtensions.CreatorName), x => x.CreatorName() }, + { nameof(FriendlyPublishedContentExtensions.Url), x => x.Url() }, + { nameof(FriendlyPublishedContentExtensions.WriterName), x => x.WriterName() }, + }; +#endif } public string[] PropertiesToIgnore @@ -117,7 +146,32 @@ public Dictionary PropertyTypeConverters protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { +#if NET472 return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList(); +#else + var properties = base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList(); + + if (typeof(IPublishedContent).IsAssignableFrom(type) == true) + { + var noAttributeProvider = new NoAttributeProvider(); + + properties.AddRange(_systemMethods.Select(x => new JsonProperty + { + DeclaringType = type, + PropertyName = ResolvePropertyName(x.Key), + UnderlyingName = x.Key, + PropertyType = typeof(string), + ValueProvider = new PublishedContentValueProvider(x.Value), + AttributeProvider = noAttributeProvider, + Readable = true, + Writable = false, + ItemIsReference = false, + TypeNameHandling = TypeNameHandling.None, + })); + } + + return properties; +#endif } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) @@ -153,5 +207,32 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ return property; } + +#if NET472 == false + protected override string ResolvePropertyName(string propertyName) + { + return PrefixSystemPropertyNamesWithUnderscore == true && _systemProperties.Contains(propertyName) == true + ? "_" + base.ResolvePropertyName(propertyName) + : base.ResolvePropertyName(propertyName); + } + + private class PublishedContentValueProvider : IValueProvider + { + private readonly Func _func; + + public PublishedContentValueProvider(Func func) => _func = func; + + public object GetValue(object target) => _func((IPublishedContent)target); + + public void SetValue(object target, object value) => throw new NotImplementedException(); + } + + private class NoAttributeProvider : IAttributeProvider + { + public IList GetAttributes(bool inherit) => Array.Empty(); + + public IList GetAttributes(Type attributeType, bool inherit) => Array.Empty(); + } +#endif } } diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html index 6d96a3ee..a028948a 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html @@ -32,7 +32,7 @@
- + Vote up @@ -40,7 +40,7 @@
- + Vote down @@ -69,15 +69,7 @@
Telemetry

By default, the package sends telemetry data about which property-editors are being used (from Contentment only). For more details about the data being captured and transparency on the analysis, please visit leekelleher.com/umbraco/contentment/telemetry.

-

If you would prefer to opt-out and disable the telemetry feature, you can use the following code snippet to do so.

-
-
- Code snippet to disable Contentment telemetry -

Copy the C# class below. You can either save this to your ~/App_Code/ folder, or add it to your own code library.

- {{vm.disableTelemetryCode}} -

If you are already using a composer class, you can add the composition.DisableContentmentTelemetry(); line to it.

-
-
+

If you would prefer to opt-out and disable the telemetry feature, you can find a code snippet on the telemetry documentation page.

Telemetry
@@ -88,15 +80,7 @@
Telemetry
Tree dashboard
-

If you would like to remove this page and tree item from the Settings section, you can use the following code snippet to do so.

-
-
- Code snippet to disable Contentment tree dashboard -

Copy the C# class below. You can either save this to your ~/App_Code/ folder, or add it to your own code library.

- {{vm.disableTreeCode}} -

If you are already using a composer class, you can add the composition.DisableContentmentTree(); line to it.

-
-
+

If you would like to remove this page and tree item from the Settings section, you can find a code snippet on the tree dashboard documentation page.

diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js b/src/Umbraco.Community.Contentment/Web/UI/backoffice.js index 1cac7b64..f7dadd43 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js +++ b/src/Umbraco.Community.Contentment/Web/UI/backoffice.js @@ -85,32 +85,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Tree.Control } }; - vm.csharp = "csharp"; vm.telemetryEnabled = config.telemetry === true; - vm.disableTelemetryCode = `using Umbraco.Core.Composing; - -namespace Our.Umbraco.Web -{ - public class DisableContentmentTelemetryComposer : IUserComposer - { - public void Compose(Composition composition) - { - composition.DisableContentmentTelemetry(); - } - } -}`; - vm.disableTreeCode = `using Umbraco.Core.Composing; - -namespace Our.Umbraco.Web -{ - public class DisableContentmentTreeComposer : IUserComposer - { - public void Compose(Composition composition) - { - composition.DisableContentmentTree(); - } - } -}`; }; init(); diff --git a/src/_vars.ps1 b/src/_vars.ps1 index 4862f6dc..fde02072 100644 --- a/src/_vars.ps1 +++ b/src/_vars.ps1 @@ -4,4 +4,4 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. # Sets the path for development site. -$TargetDevWebsite = "C:\path\to\umbraco"; +$TargetDevWebsite = ""; From 7e5add143672bad058d4eb1c5e8b2d9c0fd8f35a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 15 Oct 2021 18:01:30 +0100 Subject: [PATCH 78/81] :notebook: Updated README --- .github/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/README.md b/.github/README.md index e488d6e4..cd4c1416 100644 --- a/.github/README.md +++ b/.github/README.md @@ -50,7 +50,7 @@ _**Please note...**_ - v2.x has been developed against **Umbraco v8.14.0** and it will still work on current Umbraco v8.x releases. - v1.x has been developed against **Umbraco v8.6.1**, it will still work on current Umbraco v8.x releases. -With Contentment v3.x (Umbraco v9.0 / .NET 5), you can only install a package from the [NuGet package repository](https://www.nuget.org/packages/Our.Umbraco.Community.Contentment). For previous Contentment versions, the package can be installed from either [Our Umbraco](https://our.umbraco.com/packages/backoffice-extensions/contentment/) or NuGet package repositories, or build manually from the source-code: +With Contentment v3.x on Umbraco v9.0 (.NET 5), you can only install a package from the [NuGet package repository](https://www.nuget.org/packages/Our.Umbraco.Community.Contentment). For Umbraco v8.x, the package can still be installed from either [Our Umbraco](https://our.umbraco.com/packages/backoffice-extensions/contentment/) or NuGet package repositories. ##### NuGet package repository @@ -58,9 +58,13 @@ To [install from NuGet](https://www.nuget.org/packages/Our.Umbraco.Community.Con PM> Install-Package Our.Umbraco.Community.Contentment +...or if you are using the `dotnet` command line interface? + + dotnet add package Our.Umbraco.Community.Contentment + ##### Our Umbraco package repository -For Contentment v1.x and v2.x, you can install from Our Umbraco, please download the package from: +If you are using Umbraco v8.x, and prefer to install Contentment from the backoffice, the package can be downloaded from the Our Umbraco package repository: > @@ -69,6 +73,7 @@ For Contentment v1.x and v2.x, you can install from Our Umbraco, please download - **Data List** - When using the Umbraco Content data source with an XPath query, inside a Nested Content editor, it will not be able to identify the contextual containing node ID. e.g. your XPath query will not work. [See #30 for details.](https://github.com/leekelleher/umbraco-contentment/issues/30) - When using the Umbraco Content data source with an XPath query that contains a `$` prefix parameter, the preview will not display the items. [See #120 for details.](https://github.com/leekelleher/umbraco-contentment/issues/120) + - With Umbraco v9 (Contentment v3), SQL data source does not support querying SQL CE. [See #172 for details.]() ### Documentation @@ -135,7 +140,7 @@ For more information about the Mozilla Public License, please visit: -Current development effort: 1050+ hours (between 2019-03-13 to 2021-07-09) +Current development effort: 1097+ hours (between 2019-03-13 to 2021-10-15) _To give you an idea of how much human developer time/effort has been put into making this package._ From 4f9d2b86d24b686f0ae252b8d8618d3ff44ca419 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 15 Oct 2021 18:01:57 +0100 Subject: [PATCH 79/81] Incremented version number, v3.0.0. Out of beta! --- VERSION | 2 +- .../Umbraco.Community.Contentment.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 44401a75..56fea8a0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-beta001 \ No newline at end of file +3.0.0 \ No newline at end of file diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index cdde0bd4..5250a6cb 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -7,7 +7,7 @@ Contentment for Umbraco Contentment, a collection of components for Umbraco. umbraco - 3.0.0-beta001 + 3.0.0 Umbrella Inc Ltd Lee Kelleher 2019 © Lee Kelleher From b7ae1b82d41af8661484e94e4cf5009a00044909 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 15 Oct 2021 18:16:56 +0100 Subject: [PATCH 80/81] :heart_eyes_cat: Updated CONTRIBUTING --- .github/CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6ef293c3..9b2d6ac5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -15,6 +15,13 @@ Once the feature or idea is fleshed out, let's hack! > The above is respectively taken from the [Clearwater framework repository](https://github.com/clearwater-rb/clearwater/blob/master/README.md#contributing).
> Kudos to [Jamie Gaskins](https://github.com/jgaskins) for framing the guidelines so succinctly. +**Branching information.** Given there are now multiple versions of Contentment that support multiple versions of Umbraco, please make note of the branching structure. + +- The main [`develop`](https://github.com/leekelleher/umbraco-contentment/tree/develop) branch is where the latest work happens. This defaults to the most recent version of Contentment, (at the time of writing, this is v3.x). +- The [`dev/v1.x](https://github.com/leekelleher/umbraco-contentment/tree/dev/v1.x) branch is for Contentment **v1.4.x** patch releases, this targets Umbraco **v8.6.1**. +- The [`dev/v2.x](https://github.com/leekelleher/umbraco-contentment/tree/dev/v2.x) branch is for Contentment **v2.2.x** patch releases, this targets Umbraco **v8.14.0**. +- The [`dev/v3.x](https://github.com/leekelleher/umbraco-contentment/tree/dev/v3.x) branch is for Contentment **v3.x** (current) releases, this targets both Umbraco **v8.17.0** and **v9.0.0**. + --- Further reading, I've been thinking a lot about Jeff Geerling's post ["Why I close PRs (OSS project maintainer notes)"](https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes) lately. If you do submit a PR and feel that I'm closing it down, this is probably the rationale behind it. From 012804e9503414135b1545caeec51bf60236fe5e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 15 Oct 2021 18:24:05 +0100 Subject: [PATCH 81/81] :heart_eyes_cat: Updated CONTRIBUTING --- .github/CONTRIBUTING.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9b2d6ac5..ed3595ae 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,8 +1,10 @@ This project is governed by a [Code of Conduct](CODE_OF_CONDUCT.md). -First, before writing any code, [please open an issue](https://github.com/leekelleher/umbraco-contentment/issues), let's have a discussion about any features or ideas that you may have. +### General gist -Once the feature or idea is fleshed out, let's hack! +First, before writing any code, [please open an issue](https://github.com/leekelleher/umbraco-contentment/issues), or [let's have a discussion](https://github.com/leekelleher/umbraco-contentment/discussions) about any features or ideas that you may have. + +Once the feature or idea is fleshed out, it's hacking time! 1. Fork it 2. Branch it @@ -15,14 +17,18 @@ Once the feature or idea is fleshed out, let's hack! > The above is respectively taken from the [Clearwater framework repository](https://github.com/clearwater-rb/clearwater/blob/master/README.md#contributing).
> Kudos to [Jamie Gaskins](https://github.com/jgaskins) for framing the guidelines so succinctly. -**Branching information.** Given there are now multiple versions of Contentment that support multiple versions of Umbraco, please make note of the branching structure. + +### Branching information + +Given there are now multiple versions of Contentment that support multiple versions of Umbraco, please make note of the branching structure. - The main [`develop`](https://github.com/leekelleher/umbraco-contentment/tree/develop) branch is where the latest work happens. This defaults to the most recent version of Contentment, (at the time of writing, this is v3.x). -- The [`dev/v1.x](https://github.com/leekelleher/umbraco-contentment/tree/dev/v1.x) branch is for Contentment **v1.4.x** patch releases, this targets Umbraco **v8.6.1**. -- The [`dev/v2.x](https://github.com/leekelleher/umbraco-contentment/tree/dev/v2.x) branch is for Contentment **v2.2.x** patch releases, this targets Umbraco **v8.14.0**. -- The [`dev/v3.x](https://github.com/leekelleher/umbraco-contentment/tree/dev/v3.x) branch is for Contentment **v3.x** (current) releases, this targets both Umbraco **v8.17.0** and **v9.0.0**. +- The [`dev/v1.x`](https://github.com/leekelleher/umbraco-contentment/tree/dev/v1.x) branch is for Contentment **v1.4.x** patch releases, this targets Umbraco **v8.6.1**. +- The [`dev/v2.x`](https://github.com/leekelleher/umbraco-contentment/tree/dev/v2.x) branch is for Contentment **v2.2.x** patch releases, this targets Umbraco **v8.14.0**. +- The [`dev/v3.x`](https://github.com/leekelleher/umbraco-contentment/tree/dev/v3.x) branch is for Contentment **v3.x** (current) releases, this targets both Umbraco **v8.17.0** and **v9.0.0**. + ---- +### Further reading -Further reading, I've been thinking a lot about Jeff Geerling's post ["Why I close PRs (OSS project maintainer notes)"](https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes) lately. If you do submit a PR and feel that I'm closing it down, this is probably the rationale behind it. +I've been thinking a lot about Jeff Geerling's post ["Why I close PRs (OSS project maintainer notes)"](https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes) lately. If you do submit a PR and feel that I'm closing down the conversation, this is most likely the rationale behind it.