diff --git a/Preflight.Site.NetCore/App_Plugins/Preflight/Backoffice/settings.json b/Preflight.Site.NetCore/App_Plugins/Preflight/Backoffice/settings.json index f6b6c6f..e14074a 100644 --- a/Preflight.Site.NetCore/App_Plugins/Preflight/Backoffice/settings.json +++ b/Preflight.Site.NetCore/App_Plugins/Preflight/Backoffice/settings.json @@ -41,7 +41,8 @@ "tab": "General", "order": 2, "view": "checkboxlist", - "core": true + "core": true, + "value": "Umbraco.Grid,Umbraco.TinyMCE,Umbraco.TextArea,Umbraco.TextBox,Umbraco.NestedContent" }, { "label": "User group opt in/out", diff --git a/Preflight.Site.V8/App_Data/Models/all.dll.path b/Preflight.Site.V8/App_Data/Models/all.dll.path index faf43db..0725b5f 100644 --- a/Preflight.Site.V8/App_Data/Models/all.dll.path +++ b/Preflight.Site.V8/App_Data/Models/all.dll.path @@ -1 +1 @@ -C:\Users\nwoulfe1\AppData\Local\Temp\Temporary ASP.NET Files\vs\3f510f17\6a0262c9\App_Web_all.generated.cs.8f9494c4.thjsv7f5.dll \ No newline at end of file +C:\Users\nwoulfe1\AppData\Local\Temp\Temporary ASP.NET Files\vs\3f510f17\6a0262c9\App_Web_all.generated.cs.8f9494c4.qdu7a-xv.dll \ No newline at end of file diff --git a/Preflight.Site.V8/App_Data/Umbraco.sdf b/Preflight.Site.V8/App_Data/Umbraco.sdf index fdae7f0..4c62f1d 100644 Binary files a/Preflight.Site.V8/App_Data/Umbraco.sdf and b/Preflight.Site.V8/App_Data/Umbraco.sdf differ diff --git a/src/Preflight.Backoffice/src/Preflight/Backoffice/settings.json b/src/Preflight.Backoffice/src/Preflight/Backoffice/settings.json index 071168b..9ebf98e 100644 --- a/src/Preflight.Backoffice/src/Preflight/Backoffice/settings.json +++ b/src/Preflight.Backoffice/src/Preflight/Backoffice/settings.json @@ -41,7 +41,8 @@ "tab": "General", "order": 2, "view": "checkboxlist", - "core": true + "core": true, + "value": "Umbraco.Grid,Umbraco.TinyMCE,Umbraco.TextArea,Umbraco.TextBox,Umbraco.NestedContent" }, { "label": "User group opt in/out", diff --git a/src/Preflight/Constants/KnownPropertyAlias.cs b/src/Preflight/Constants/KnownPropertyAlias.cs index 1814c20..ec7b756 100644 --- a/src/Preflight/Constants/KnownPropertyAlias.cs +++ b/src/Preflight/Constants/KnownPropertyAlias.cs @@ -1,12 +1,12 @@ using System.Collections.Generic; using System.Linq; -#if NET472 -using UmbConstants = Umbraco.Core.Constants; -#else +#if NETCOREAPP using UmbConstants = Umbraco.Cms.Core.Constants; +#else +using UmbConstants = Umbraco.Core.Constants; #endif -namespace Preflight.Constants +namespace Preflight { public static class KnownPropertyAlias { @@ -15,6 +15,7 @@ public static class KnownPropertyAlias public const string Textarea = UmbConstants.PropertyEditors.Aliases.TextArea; public const string Textbox = UmbConstants.PropertyEditors.Aliases.TextBox; public const string NestedContent = UmbConstants.PropertyEditors.Aliases.NestedContent; + public const string BlockList = UmbConstants.PropertyEditors.Aliases.BlockList; public static IEnumerable All = typeof(KnownPropertyAlias).GetFields() .Where(x => x.IsLiteral).Select(x => x.GetRawConstantValue().ToString()); diff --git a/src/Preflight/Constants/KnownSettings.cs b/src/Preflight/Constants/KnownSettings.cs index 82855f1..6761427 100644 --- a/src/Preflight/Constants/KnownSettings.cs +++ b/src/Preflight/Constants/KnownSettings.cs @@ -1,18 +1,19 @@ -namespace Preflight.Constants +namespace Preflight { public static class KnownSettings { - public const string BindSaveHandler = "Run Preflight on save"; - public const string EnsureSafeLinks = "Ensure safe links"; - public const string CancelSaveOnFail = "Cancel save when Preflight tests fail"; - public const string ReadabilityMin = "Readability target - minimum"; - public const string ReadabilityMax = "Readability target - maximum"; - public const string LongWordSyllables = "Long word syllable count"; - public const string NiceList = "Nice words"; - public const string NaughtyList = "Naughty words"; - public const string AutocorrectTerms = "Autocorrect terms"; - public const string GoogleApiKey = "Google SafeBrowsing API key"; - public const string UserGroupOptIn = "User group opt in/out"; - public const string PropertiesToTest = "Properties to test"; + // guid strings are used to identify the setting and for storing the related value + public const string BindSaveHandler = "a5b02d88-4555-4c81-8973-6119feb3da20"; + public const string EnsureSafeLinks = "2f1f3b0f-f6c6-45ea-a9fa-f342372c4208"; + public const string CancelSaveOnFail = "0483a065-9504-48e4-861d-6149b2e37cd3"; + public const string ReadabilityMin = "ad1ac044-5a44-45aa-86a8-ab1a1fedada2"; + public const string ReadabilityMax = "f3ab7fe3-0d29-4450-b07c-92a61a23e123"; + public const string LongWordSyllables = "a62b0522-88d8-4b80-b3e9-ef1e81ec7baa"; + public const string NiceList = "1a465035-41d4-44a4-bf64-87ede0cea3be"; + public const string NaughtyList = "edb82950-9d0b-4d1d-b727-96915014db4b"; + public const string AutocorrectTerms = "9e94c320-b1bf-4318-a331-00d35eb762fc"; + public const string GoogleApiKey = "bc6dff82-beac-4c5d-8d4e-e33559c78666"; + public const string UserGroupOptIn = "d1e534d6-cc35-4cba-bb14-dc4afeada605"; + public const string PropertiesToTest = "0e210e54-6936-444f-af8e-ae9e29cfb6eb"; } } diff --git a/src/Preflight/Constants/KnownStrings.cs b/src/Preflight/Constants/KnownStrings.cs index 5e038d3..482cb78 100644 --- a/src/Preflight/Constants/KnownStrings.cs +++ b/src/Preflight/Constants/KnownStrings.cs @@ -1,41 +1,28 @@ -namespace Preflight.Constants +namespace Preflight { public static class KnownStrings { public const string Name = "Preflight"; public const string Alias = "preflight"; public const string Icon = "icon-paper-plane"; - public const string AppSettingKey = "PreflightInstalled"; public const string SettingsTable = "PreflightSettings"; public const string ContentFailedChecks = "Content failed Preflight checks"; - public const string GridRteJsonPath = "$..controls[?(@.editor.view == 'rte' || @.editor.view == 'textstring' || @.editor.view == 'textarea' || @.editor.alias == 'rte' || @.editor.alias == 'headline' || @.editor.alias == 'quote')]"; - public const string GridValueJsonPath = "$..value"; - - public const string NcAlias = "ncContentTypeAlias"; + public const string Comma = ","; public const string One = "1"; + public const string Zero = "0"; + public const string CORE = "CORE"; public const string FRAMEWORK = "FRAMEWORK"; - public const string GridTextstringJsonPath = "$..controls[?(@.editor.alias == 'textstring')]"; - - public const string ClosingHtmlTags = @"(<\/li>|<\/h[1-6]{1}>)"; - public const string CharsToRemove = @"(<.*?>|\(|\)|\[|\]|,|\w'|'\w|\w""|""\w)"; - public const string DuplicateSpaces = @"\s+"; - public const string NewLine = "\n"; - public const string HrefXPath = "//a[@href]"; - public const string CacheKey = "Preflight_SafeBrowsing_"; - public const string SettingsCacheKey = "Preflight_Settings_"; - - public const string SafeBrowsingUrl = "https://safebrowsing.googleapis.com/v4/threatMatches:find?key="; + public const string PropertiesToTestSuffix = "PropertiesToTest"; + } - public static readonly char[] Vowels = { 'a', 'e', 'i', 'o', 'u', 'y' }; - public static readonly char[] WordDelimiters = { '.', '!', '?', ':', ';' }; - public static readonly string[] Endings = { "es", "ed" }; + public static partial class Constants + { - public static readonly string SettingsFilePath = "~/App_Plugins/Preflight/Backoffice/settings.json"; } } diff --git a/src/Preflight/Constants/Readability.cs b/src/Preflight/Constants/Readability.cs new file mode 100644 index 0000000..bac0ee8 --- /dev/null +++ b/src/Preflight/Constants/Readability.cs @@ -0,0 +1,24 @@ +namespace Preflight +{ + public static partial class Constants + { + public class Readability + { + public static readonly char[] Vowels = { 'a', 'e', 'i', 'o', 'u', 'y' }; + public static readonly char[] WordDelimiters = { '.', '!', '?', ':', ';' }; + public static readonly string[] Endings = { "es", "ed" }; + + public const string ClosingHtmlTags = @"(<\/li>|<\/h[1-6]{1}>)"; + public const string CharsToRemove = @"(<.*?>|\(|\)|\[|\]|,|\w'|'\w|\w""|""\w)"; + public const string DuplicateSpaces = @"\s+"; + public const string Ied = "ied"; + public const string Space = " "; + public const string Period = "."; + public const string Ampersand = "&"; + public const string AmpersandEntity = "&"; + + public const char l = 'l'; + public const char e = 'e'; + } + } +} diff --git a/src/Preflight/Constants/SettingType.cs b/src/Preflight/Constants/SettingType.cs index 860aad9..b3a4bf2 100644 --- a/src/Preflight/Constants/SettingType.cs +++ b/src/Preflight/Constants/SettingType.cs @@ -1,11 +1,11 @@ -namespace Preflight.Constants +namespace Preflight { public static class SettingType { - public const string Boolean = "views/propertyeditors/boolean/boolean.html"; - public const string Slider = "views/propertyeditors/slider/slider.html"; - public const string String = "views/propertyeditors/textbox/textbox.html"; - public const string MultipleTextbox = "views/propertyeditors/multipletextbox/multipletextbox.html"; - public const string CheckboxList = "views/propertyeditors/checkboxlist/checkboxlist.html"; + public const string Boolean = "boolean"; + public const string Slider = "slider"; + public const string String = "textbox"; + public const string MultipleTextbox = "multipletextbox"; + public const string CheckboxList = "checkboxlist"; } } diff --git a/src/Preflight/Controllers/PreflightApiController.cs b/src/Preflight/Controllers/PreflightApiController.cs index 52006f0..492ace5 100644 --- a/src/Preflight/Controllers/PreflightApiController.cs +++ b/src/Preflight/Controllers/PreflightApiController.cs @@ -3,17 +3,17 @@ using Preflight.Services; using System; using System.Net; -#if NET472 +#if NETCOREAPP +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; +#else using System.Web.Http; using Umbraco.Core.Services; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using IActionResult = System.Web.Http.IHttpActionResult; -#else -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Web.BackOffice.Controllers; -using Umbraco.Cms.Web.Common.Attributes; #endif namespace Preflight.Controllers diff --git a/src/Preflight/Controllers/PreflightTreeController.cs b/src/Preflight/Controllers/PreflightTreeController.cs index 7c5ec00..c254210 100644 --- a/src/Preflight/Controllers/PreflightTreeController.cs +++ b/src/Preflight/Controllers/PreflightTreeController.cs @@ -1,13 +1,4 @@ -using Preflight.Constants; -#if NET472 -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; -#else +#if NETCOREAPP using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Web.BackOffice.Trees; @@ -18,6 +9,14 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; using UmbConstants = Umbraco.Cms.Core.Constants; +#else +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; #endif namespace Preflight.Controllers @@ -38,21 +37,7 @@ private void SetRootNode(TreeNode root) root.HasChildren = false; root.MenuUrl = null; } - -#if NET472 - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - TreeNode root = base.CreateRootNode(queryStrings); - SetRootNode(root); - - return root; - } - - protected override MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) => null; - - protected override TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) => - new TreeNodeCollection(); -#else +#if NETCOREAPP public PreflightTreeController(ILocalizedTextService textService, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, IEventAggregator eventAggregator) : base(textService, umbracoApiControllerTypeCollection, eventAggregator) { @@ -70,6 +55,20 @@ protected override ActionResult CreateRootNode(FormCollection queryStr protected override ActionResult GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) => new TreeNodeCollection(); +#else + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + TreeNode root = base.CreateRootNode(queryStrings); + SetRootNode(root); + + return root; + } + + protected override MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) => null; + + protected override TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) => + new TreeNodeCollection(); + #endif } } diff --git a/src/Preflight/Executors/IContentSavingExecutor.cs b/src/Preflight/Executors/IContentSavingExecutor.cs index 1fe21db..3a793f5 100644 --- a/src/Preflight/Executors/IContentSavingExecutor.cs +++ b/src/Preflight/Executors/IContentSavingExecutor.cs @@ -1,20 +1,19 @@ -using Preflight.Constants; -using Preflight.Extensions; +using Preflight.Extensions; using Preflight.Models; using Preflight.Services; using System; using System.Collections.Generic; using System.Linq; -#if NET472 -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Preflight.Security; -using CharArrays = Umbraco.Core.Constants.CharArrays; -#else +#if NETCOREAPP using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using CharArrays = Umbraco.Cms.Core.Constants.CharArrays; +#else +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Preflight.Security; +using CharArrays = Umbraco.Core.Constants.CharArrays; #endif namespace Preflight.Executors @@ -70,17 +69,6 @@ public bool SaveCancelledDueToFailedTests(IContent content, out EventMessage mes // at least one property on the current document fails the preflight check if (!failed) return false; - // these values are retrieved in the notifications handler, and passed down to the client - // TODO => make it work - //HttpContext.Current.Items["PreflightFailed"] = true; - //HttpContext.Current.Items["PreflightCancelSaveOnFail"] = cancelSaveOnFail; - //HttpContext.Current.Items["PreflightNodeId"] = content.Id; - - //if (e.CanCancel && cancelSaveOnFail) - //{ - // e.CancelOperation(new EventMessage("PreflightFailed", content.Id.ToString())); - //} - message = new EventMessage("Save cancelled", content.Id.ToString()); return true; diff --git a/src/Preflight/Executors/ILinkGenerator.cs b/src/Preflight/Executors/ILinkGenerator.cs index 68a801f..c82fbfe 100644 --- a/src/Preflight/Executors/ILinkGenerator.cs +++ b/src/Preflight/Executors/ILinkGenerator.cs @@ -1,15 +1,15 @@ using System; using System.Linq.Expressions; -#if NET472 +#if NETCOREAPP +using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Extensions; +using CoreLinkGenerator = Microsoft.AspNetCore.Routing.LinkGenerator; +#else using Umbraco.Web; using Umbraco.Web.WebApi; using System.Web.Routing; using System.Web; using System.Web.Mvc; -#else -using Umbraco.Cms.Web.Common.Controllers; -using Umbraco.Extensions; -using CoreLinkGenerator = Microsoft.AspNetCore.Routing.LinkGenerator; #endif namespace Preflight.Executors @@ -21,15 +21,7 @@ public interface ILinkGenerator public class LinkGenerator : ILinkGenerator { -#if NET472 - public string GetUmbracoApiServiceBaseUrl(Expression> methodSelector) where T : UmbracoApiController - { - RequestContext requestContext = HttpContext.Current.Request.RequestContext; - var urlHelper = new UrlHelper(requestContext); - - return urlHelper.GetUmbracoApiServiceBaseUrl(methodSelector); - } -#else +#if NETCOREAPP private readonly CoreLinkGenerator _coreLinkGenerator; public LinkGenerator(CoreLinkGenerator coreLinkGenerator) @@ -39,6 +31,14 @@ public LinkGenerator(CoreLinkGenerator coreLinkGenerator) public string GetUmbracoApiServiceBaseUrl(Expression> methodSelector) where T : UmbracoApiController => _coreLinkGenerator.GetUmbracoApiServiceBaseUrl(methodSelector); +#else + public string GetUmbracoApiServiceBaseUrl(Expression> methodSelector) where T : UmbracoApiController + { + RequestContext requestContext = HttpContext.Current.Request.RequestContext; + var urlHelper = new UrlHelper(requestContext); + + return urlHelper.GetUmbracoApiServiceBaseUrl(methodSelector); + } #endif } } \ No newline at end of file diff --git a/src/Preflight/Executors/IPluginExecutor.cs b/src/Preflight/Executors/IPluginExecutor.cs new file mode 100644 index 0000000..75a4281 --- /dev/null +++ b/src/Preflight/Executors/IPluginExecutor.cs @@ -0,0 +1,104 @@ +using Preflight.Extensions; +using Preflight.Models; +using Preflight.Plugins; +using System; +using System.Linq; +using Preflight.Services; +using System.Collections.Generic; +#if NETCOREAPP +using Microsoft.Extensions.Logging; +#else +using Preflight.Logging; +#endif + +namespace Preflight.Executors +{ + public interface IPluginExecutor + { + PreflightPropertyResponseModel Execute(string name, string culture, string val, string alias, int id, bool fromSave, string parentAlias = ""); + } + + public class PluginExecutor : IPluginExecutor + { + private string _testableProperties; + private List _settings; + + private readonly ILogger _logger; + private readonly ISettingsService _settingsService; + private readonly PreflightPluginCollection _plugins; + + public PluginExecutor(PreflightPluginCollection plugins, ISettingsService settingsService) + { + _plugins = plugins; + _settingsService = settingsService; + } + + /// + /// Runs the set of plugins against the given string + /// + /// + /// + /// + public PreflightPropertyResponseModel Execute(string name, string culture, string val, string alias, int id, bool fromSave, string parentAlias = "") + { + _settings = _settingsService.Get().Settings; + _testableProperties = _settings.GetValue(KnownSettings.PropertiesToTest, culture); + + var model = new PreflightPropertyResponseModel + { + Label = name, + Name = name + }; + + if (val == null || !_testableProperties.Contains(alias) || (parentAlias.HasValue() && !_testableProperties.Contains(parentAlias))) + return model; + + foreach (IPreflightPlugin plugin in _plugins) + { + // settings on the plugin are the defaults - set to correct values from _settings + plugin.Settings = _settings.Where(s => s.Tab == plugin.Name)?.ToList(); + + // ignore disabled plugins + if (plugin.IsDisabled(culture)) continue; + if (!fromSave && plugin.IsOnSaveOnly(culture)) continue; + + string propsValue = plugin.Settings.FirstOrDefault(x => x.Alias.EndsWith(KnownStrings.PropertiesToTestSuffix))?.Value.ForVariant(culture); + string propsToTest = propsValue ?? string.Join(KnownStrings.Comma, KnownPropertyAlias.All); + + // only continue if the field alias is include for testing, or the parent alias has been set, and is included for testing + if (!propsToTest.Contains(alias) || (parentAlias.HasValue() && !propsToTest.Contains(parentAlias))) continue; + + try + { + Type pluginType = plugin.GetType(); + if (pluginType.GetMethod("Check") == null) continue; + + plugin.Check(id, culture, val, _settings); + + if (plugin.Result != null) + { + if (plugin.FailedCount == 0) + { + plugin.FailedCount = plugin.Failed ? 1 : 0; + } + + model.Plugins.Add(plugin); + } + } + catch (Exception e) + { + _logger.LogError(e, "Preflight couldn't take off: {Message}", e.Message); + } + } + + // mark as failed if any sub-tests have failed + model.FailedCount = model.Plugins.Sum(x => x.FailedCount); + model.Failed = model.FailedCount > 0; + + model.Plugins = model.Plugins.OrderBy(p => p.SortOrder).ToList(); + model.TotalTests = model.Plugins.Aggregate(0, (acc, x) => acc + x.TotalTests); + + return model; + } + } +} diff --git a/src/Preflight/Executors/ISendingContentModelExecutor.cs b/src/Preflight/Executors/ISendingContentModelExecutor.cs index 6ec4b2b..0b9bbd0 100644 --- a/src/Preflight/Executors/ISendingContentModelExecutor.cs +++ b/src/Preflight/Executors/ISendingContentModelExecutor.cs @@ -1,20 +1,19 @@ -using Preflight.Constants; -using Preflight.Extensions; +using Preflight.Extensions; using Preflight.Models; using Preflight.Services; using System; using System.Collections.Generic; using System.Linq; -#if NET472 -using Preflight.Security; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using CharArrays = Umbraco.Core.Constants.CharArrays; -#else +#if NETCOREAPP using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Models.ContentEditing; using CharArrays = Umbraco.Cms.Core.Constants.CharArrays; +#else +using Preflight.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using CharArrays = Umbraco.Core.Constants.CharArrays; #endif namespace Preflight.Executors diff --git a/src/Preflight/Executors/IServerVariablesParsingExecutor.cs b/src/Preflight/Executors/IServerVariablesParsingExecutor.cs index 629aeae..f854419 100644 --- a/src/Preflight/Executors/IServerVariablesParsingExecutor.cs +++ b/src/Preflight/Executors/IServerVariablesParsingExecutor.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; -using Preflight.Constants; using Preflight.Controllers; using System.Linq; -#if NET472 +#if NETCOREAPP +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core; +#else using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web; -#else -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core; #endif namespace Preflight.Executors @@ -35,10 +34,10 @@ public void Generate(IDictionary dictionary, IDictionary umbracoSettings = dictionary["umbracoSettings"] as Dictionary ?? new Dictionary(); diff --git a/src/Preflight/Extensions/ContentBaseExtensions.cs b/src/Preflight/Extensions/ContentBaseExtensions.cs index 12290c7..de687ac 100644 --- a/src/Preflight/Extensions/ContentBaseExtensions.cs +++ b/src/Preflight/Extensions/ContentBaseExtensions.cs @@ -1,11 +1,10 @@ -using Preflight.Constants; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -#if NET472 +#if NETCOREAPP +using Umbraco.Cms.Core.Models; +#else using Umbraco.Core.Models; using IProperty = Umbraco.Core.Models.Property; -#else -using Umbraco.Cms.Core.Models; #endif namespace Preflight.Extensions diff --git a/src/Preflight/Extensions/ContentTypeBaseExtensions.cs b/src/Preflight/Extensions/ContentTypeBaseExtensions.cs index 5bd65cd..3d83788 100644 --- a/src/Preflight/Extensions/ContentTypeBaseExtensions.cs +++ b/src/Preflight/Extensions/ContentTypeBaseExtensions.cs @@ -1,11 +1,10 @@ -using Preflight.Constants; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -#if NET472 +#if NETCOREAPP +using Umbraco.Cms.Core.Models; +#else using Umbraco.Core.Models; using IPropertyType = Umbraco.Core.Models.PropertyType; -#else -using Umbraco.Cms.Core.Models; #endif namespace Preflight.Extensions @@ -19,6 +18,7 @@ public static IEnumerable GetPreflightProperties(this IContentTyp p.PropertyEditorAlias == KnownPropertyAlias.NestedContent || p.PropertyEditorAlias == KnownPropertyAlias.Textbox || p.PropertyEditorAlias == KnownPropertyAlias.Textarea || + p.PropertyEditorAlias == KnownPropertyAlias.BlockList || p.PropertyEditorAlias == KnownPropertyAlias.Rte); } } diff --git a/src/Preflight/Extensions/PreflightPluginExtensions.cs b/src/Preflight/Extensions/PreflightPluginExtensions.cs index ebb2f47..a1b7795 100644 --- a/src/Preflight/Extensions/PreflightPluginExtensions.cs +++ b/src/Preflight/Extensions/PreflightPluginExtensions.cs @@ -1,22 +1,19 @@ -using Preflight.Plugins; +using Preflight.Models; +using Preflight.Plugins; +using System.Collections.Generic; using System.Linq; namespace Preflight.Extensions { public static class PreflightPluginExtensions { - public static bool IsDisabled(this IPreflightPlugin plugin) - { - return plugin.Settings.Any() && - plugin.Settings.Any(s => s.Alias == plugin.Name.DisabledAlias() && s.Value.ToString() == "1"); + public static bool IsDisabled(this IPreflightPlugin plugin, string culture) => + True(plugin.Settings, culture, plugin.Name.DisabledAlias()); - } - - public static bool IsOnSaveOnly(this IPreflightPlugin plugin) - { - return plugin.Settings.Any() && - plugin.Settings.Any(s => s.Alias == plugin.Name.OnSaveOnlyAlias() && s.Value.ToString() == "1"); - - } + public static bool IsOnSaveOnly(this IPreflightPlugin plugin, string culture) => + True(plugin.Settings, culture, plugin.Name.OnSaveOnlyAlias()); + + private static bool True(IEnumerable settings, string culture, string settingAlias) => + settings.Any(s => s.Alias == settingAlias && s.Value.ForVariant(culture) == KnownStrings.One); } } diff --git a/src/Preflight/Extensions/SettingsModelListExtensions.cs b/src/Preflight/Extensions/SettingsModelListExtensions.cs index 5f0667c..291afd2 100644 --- a/src/Preflight/Extensions/SettingsModelListExtensions.cs +++ b/src/Preflight/Extensions/SettingsModelListExtensions.cs @@ -7,9 +7,10 @@ namespace Preflight.Extensions { public static class SettingsModelListExtensions { - public static T GetValue(this IEnumerable settings, string label, string culture) where T: IConvertible + public static T GetValue(this IEnumerable settings, string guid, string culture) where T: IConvertible { - var stringValue = settings.FirstOrDefault(x => string.Equals(x.Label, label, StringComparison.InvariantCultureIgnoreCase)).Value.ForVariant(culture); + var guidGuid = new Guid(guid); + var stringValue = settings.FirstOrDefault(x => x.Guid == guidGuid).Value.ForVariant(culture); return ConvertObject(stringValue); } diff --git a/src/Preflight/Hubs/PreflightHub.cs b/src/Preflight/Hubs/PreflightHub.cs index f3bf46f..30b64f1 100644 --- a/src/Preflight/Hubs/PreflightHub.cs +++ b/src/Preflight/Hubs/PreflightHub.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; -using Preflight.Models; - -#if NET472 -using Microsoft.AspNet.SignalR; -#else +using Preflight.Models; +#if NETCOREAPP using Microsoft.AspNetCore.SignalR; +using System.Threading.Tasks; +#else +using Microsoft.AspNet.SignalR; #endif namespace Preflight.Hubs @@ -12,14 +11,14 @@ namespace Preflight.Hubs public interface IPreflightHub { -#if NET472 - void refresh(); - void preflightTest(PreflightPropertyResponseModel model); - void preflightComplete(); -#else +#if NETCOREAPP Task refresh(); Task preflightTest(PreflightPropertyResponseModel model); Task preflightComplete(); +#else + void refresh(); + void preflightTest(PreflightPropertyResponseModel model); + void preflightComplete(); #endif } diff --git a/src/Preflight/Hubs/PreflightHubRoutes.cs b/src/Preflight/Hubs/PreflightHubRoutes.cs index 2c8055c..8a5f8c8 100644 --- a/src/Preflight/Hubs/PreflightHubRoutes.cs +++ b/src/Preflight/Hubs/PreflightHubRoutes.cs @@ -1,5 +1,4 @@ -#if NET472 -#else +#if NETCOREAPP using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; diff --git a/src/Preflight/IBackOfficeSecurityAccessor.cs b/src/Preflight/IBackOfficeSecurityAccessor.cs index 1752084..30910f2 100644 --- a/src/Preflight/IBackOfficeSecurityAccessor.cs +++ b/src/Preflight/IBackOfficeSecurityAccessor.cs @@ -1,4 +1,4 @@ -#if NET472 +#if NETFRAMEWORK using Umbraco.Core.Models.Membership; using Umbraco.Web.Composing; diff --git a/src/Preflight/IIOHelper.cs b/src/Preflight/IIOHelper.cs index 43ca6aa..589e618 100644 --- a/src/Preflight/IIOHelper.cs +++ b/src/Preflight/IIOHelper.cs @@ -1,8 +1,8 @@ -#if NET472 -using Umbraco.Core.IO; -#else +#if NETCOREAPP using Microsoft.AspNetCore.Hosting; using System.IO; +#else +using Umbraco.Core.IO; #endif namespace Preflight.IO @@ -14,30 +14,29 @@ public interface IIOHelper string MapPath(string path); } +#if NETCOREAPP public class PreflightIoHelper : IIOHelper { -#if NET5_0 private readonly IWebHostEnvironment _webHostEnvironment; private readonly char[] TrimChars = new[] { '~', '/' }; - public PreflightIoHelper(IWebHostEnvironment webHostEnvironment) - { - _webHostEnvironment = webHostEnvironment; - } -#endif + public PreflightIoHelper(IWebHostEnvironment webHostEnvironment) => + _webHostEnvironment = webHostEnvironment; public string ResolveUrl(string url) => -#if NET472 - IOHelper.ResolveUrl(url); -#else Path.Combine(_webHostEnvironment.WebRootPath, url); -#endif public string MapPath(string path) => -#if NET472 - IOHelper.MapPath(path); -#else Path.Combine(_webHostEnvironment.ContentRootPath, path.TrimStart(TrimChars)); -#endif } +#else + public class PreflightIoHelper : IIOHelper + { + public string ResolveUrl(string url) => + IOHelper.ResolveUrl(url); + + public string MapPath(string path) => + IOHelper.MapPath(path); + } +#endif } \ No newline at end of file diff --git a/src/Preflight/ILogger.cs b/src/Preflight/ILogger.cs index d74696d..ec56d69 100644 --- a/src/Preflight/ILogger.cs +++ b/src/Preflight/ILogger.cs @@ -1,4 +1,4 @@ -#if NET472 +#if NETFRAMEWORK using System; using Umbraco.Core.Logging; diff --git a/src/Preflight/Migrations/PreflightMigrationPlan.cs b/src/Preflight/Migrations/PreflightMigrationPlan.cs index 93f7d4e..d0ad797 100644 --- a/src/Preflight/Migrations/PreflightMigrationPlan.cs +++ b/src/Preflight/Migrations/PreflightMigrationPlan.cs @@ -1,8 +1,7 @@ -using Preflight.Constants; -#if NET472 -using Umbraco.Core.Migrations; -#else +#if NETCOREAPP using Umbraco.Cms.Infrastructure.Migrations; +#else +using Umbraco.Core.Migrations; #endif namespace Preflight.Migrations diff --git a/src/Preflight/Migrations/Preflight_TwoZeroZero.cs b/src/Preflight/Migrations/Preflight_TwoZeroZero.cs index 2235778..a282db6 100644 --- a/src/Preflight/Migrations/Preflight_TwoZeroZero.cs +++ b/src/Preflight/Migrations/Preflight_TwoZeroZero.cs @@ -1,36 +1,57 @@ -using Preflight.Constants; -using Preflight.Migrations.Schema; -using Preflight.Services; -#if NET472 -using Preflight.Logging; -using Umbraco.Core.Migrations; -using Umbraco.Core.Services; -#else +using Preflight.Migrations.Schema; +using System.IO; +using Preflight.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using Preflight.IO; +using Preflight.Settings; +using Preflight.Plugins; +using System.Linq; +using Preflight.Extensions; +#if NETCOREAPP using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Migrations; +#else +using Preflight.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; #endif namespace Preflight.Migrations { public class Preflight_TwoZeroZero : MigrationBase { + private const string _settingsFilePath = "~/App_Plugins/Preflight/Backoffice/settings.json"; + private readonly ILogger _logger; - private readonly ISettingsService _settingsService; private readonly ILocalizationService _localizationService; + private readonly IIOHelper _ioHelper; + private readonly IScopeProvider _scopeProvider; + private readonly PreflightPluginCollection _plugins; - public Preflight_TwoZeroZero(IMigrationContext context, ILogger logger, ISettingsService settingsService, ILocalizationService localizationService) + public Preflight_TwoZeroZero( + IMigrationContext context, + ILogger logger, + ILocalizationService localizationService, + IIOHelper ioHelper, + PreflightPluginCollection plugins, + IScopeProvider scopeProvider) : base(context) { _logger = logger; - _settingsService = settingsService; _localizationService = localizationService; + _ioHelper = ioHelper; + _plugins = plugins; + _scopeProvider = scopeProvider; } -#if NET472 - public override void Migrate() -#else +#if NETCOREAPP protected override void Migrate() +#else + public override void Migrate() #endif { _logger.LogDebug("Creating Preflight settings table"); @@ -42,15 +63,63 @@ protected override void Migrate() _logger.LogDebug("Populate from existing settings.json"); - var settings = _settingsService.Get(); - var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); + // get all settings, update the value to a variant dictionary, + // then save the value and guid for each + var settings = new List(); + + using (var file = new StreamReader(_ioHelper.MapPath(_settingsFilePath))) + { + string json = file.ReadToEnd(); + settings = JsonConvert.DeserializeObject>(json); + } + + if (settings == null) + return; + + // update settings values to variant dictionaries + var allLanguages = _localizationService.GetAllLanguages().Select(l => l.IsoCode); + foreach (var setting in settings) + { + // if not a dictionary, make it one + try + { + var _ = setting.Value.ToVariantDictionary(); + } catch { + setting.Value = allLanguages.ToDictionary(key => key, value => setting.Value); + } + } + + // mash all known settings into a common object + // these provide the guid value + var allSettings = new CoreSettings().Settings + .Concat(new NaughtyNiceSettings().Settings).ToList(); - //foreach (var setting in settings.Settings) - //{ - // setting.Culture = defaultCulture; - //} + var pluginSettings = _plugins.Where(p => p.Settings.Any()).SelectMany(p => p.Settings); + allSettings.AddRange(pluginSettings); - Update.Table(KnownStrings.SettingsTable).Set(settings.Settings); + // generate the list of saveable settings + var settingsToSave = new List(); + + foreach (var setting in settings) + { + var schema = new SettingsSchema + { + Value = setting.Value.ToString(), + Setting = allSettings.FirstOrDefault(x => x.Alias == setting.Alias).Guid, + }; + + settingsToSave.Add(schema); + } + + // finally, persist just the guid and value for each setting + using (var scope = _scopeProvider.CreateScope()) + { + foreach (var setting in settingsToSave) + { + scope.Database.Insert(setting); + } + scope.Complete(); + } } } } \ No newline at end of file diff --git a/src/Preflight/Migrations/Schema/SettingsSchema.cs b/src/Preflight/Migrations/Schema/SettingsSchema.cs index 7f37587..138c2b8 100644 --- a/src/Preflight/Migrations/Schema/SettingsSchema.cs +++ b/src/Preflight/Migrations/Schema/SettingsSchema.cs @@ -1,9 +1,9 @@ using NPoco; -using Preflight.Constants; -#if NET472 -using Umbraco.Core.Persistence.DatabaseAnnotations; -#else +using System; +#if NETCOREAPP using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; +#else +using Umbraco.Core.Persistence.DatabaseAnnotations; #endif namespace Preflight.Migrations.Schema @@ -15,64 +15,18 @@ public class SettingsSchema { [Column("Id")] [PrimaryKeyColumn(AutoIncrement = true)] - public int Id { get; set; } - - [Column("Culture")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public string Culture { get; set; } - - [Column("Core")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public bool Core { get; set; } - - /// - /// A UI-friendly label for the setting - /// - [Column("Label")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public string Label { get; set; } + public int Id { get; set; } /// /// Set the default value for the setting /// [Column("Value")] - [NullSetting(NullSetting = NullSettings.NotNull)] public string Value { get; set; } /// - /// Describe the setting - /// - [Column("Description")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public string Description { get; set; } - - /// - /// Name of an Umbraco property editor - full path is built later - /// - [Column("View")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public string View { get; set; } - - /// - /// Where should the setting sit on the tab - /// - [Column("Order")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public int Order { get; set; } - - /// - /// Where should the setting be displayed - either reference an existing tab from SettingsTabNames, or add your own - /// Plugins default to the plugin name - /// - [Column("Tab")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public string Tab { get; set; } - - /// - /// Prevalues for the setting + /// Set the default value for the setting /// - [Column("Prevalues")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public string Prevalues { get; set; } + [Column("Setting")] + public Guid Setting { get; set; } } } diff --git a/src/Preflight/Models/SettingsModel.cs b/src/Preflight/Models/SettingsModel.cs index 5c1e753..91b6be0 100644 --- a/src/Preflight/Models/SettingsModel.cs +++ b/src/Preflight/Models/SettingsModel.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -using Preflight.Constants; using Preflight.Extensions; +using System; using System.Collections.Generic; using System.Linq; @@ -32,6 +32,18 @@ public class SettingsTab /// public class SettingsModel { + /// + /// + /// + [JsonProperty("guid")] + public Guid Guid { get; set; } + + [JsonProperty("id")] + internal int Id { get; set; } + + /// + /// + /// [JsonProperty("core")] internal bool Core { get; set; } @@ -103,9 +115,9 @@ public GenericSettingModel(string label) /// internal class DisabledSettingModel : SettingsModel { - public DisabledSettingModel(string tab, bool val) + public DisabledSettingModel(string tab, bool val, Guid guid) { - Value = val ? "1" : "0"; + Value = val ? KnownStrings.One : KnownStrings.Zero; Label = "Disabled"; Alias = tab.DisabledAlias(); Description = $"Disable the {tab} plugin"; @@ -113,6 +125,7 @@ public DisabledSettingModel(string tab, bool val) Order = -10; Core = true; Tab = tab; + Guid = guid; } } @@ -121,9 +134,9 @@ public DisabledSettingModel(string tab, bool val) /// internal class OnSaveOnlySettingModel : SettingsModel { - public OnSaveOnlySettingModel(string tab, bool val) + public OnSaveOnlySettingModel(string tab, bool val, Guid guid) { - Value = val ? "1" : "0"; + Value = val ? KnownStrings.One : KnownStrings.Zero; Label = "Run on save only"; Alias = tab.OnSaveOnlyAlias(); Description = "Restrict this plugin to run only in a save event"; @@ -131,6 +144,7 @@ public OnSaveOnlySettingModel(string tab, bool val) Order = -5; Core = true; Tab = tab; + Guid = guid; } } @@ -139,7 +153,7 @@ public OnSaveOnlySettingModel(string tab, bool val) /// internal class PropertiesToTestSettingModel : SettingsModel { - public PropertiesToTestSettingModel(string tab, string propsToTest) + public PropertiesToTestSettingModel(string tab, string propsToTest, Guid guid) { Value = propsToTest; Label = "Properties to test"; @@ -150,6 +164,7 @@ public PropertiesToTestSettingModel(string tab, string propsToTest) Order = -15; Core = true; Tab = tab; + Guid = guid; } } @@ -159,21 +174,30 @@ public PropertiesToTestSettingModel(string tab, string propsToTest) /// public static class PluginSettingsList { - public static IEnumerable Populate(string name, bool disabled, bool runOnSaveOnly, string propsToTest = "", params SettingsModel[] settings) + public static IEnumerable Populate( + string name, + bool disabled, + bool runOnSaveOnly, + string[] defaultSettingsGuids, + string propsToTest = "", + params SettingsModel[] settings) { if (!settings.Any()) return new List(); + if (defaultSettingsGuids.Length != 3) + throw new ArgumentOutOfRangeException(nameof(defaultSettingsGuids)); + if (!propsToTest.HasValue()) { - propsToTest = string.Join(",", KnownPropertyAlias.All); + propsToTest = string.Join(KnownStrings.Comma, KnownPropertyAlias.All); } List response = new List { - new DisabledSettingModel(name, disabled), - new OnSaveOnlySettingModel(name, runOnSaveOnly), - new PropertiesToTestSettingModel(name, propsToTest) + new DisabledSettingModel(name, disabled, new Guid(defaultSettingsGuids[0])), + new OnSaveOnlySettingModel(name, runOnSaveOnly, new Guid(defaultSettingsGuids[1])), + new PropertiesToTestSettingModel(name, propsToTest, new Guid(defaultSettingsGuids[2])), }; foreach (SettingsModel s in settings) diff --git a/src/Preflight/Models/SettingsTabNames.cs b/src/Preflight/Models/SettingsTabNames.cs index 8b3f203..477fa84 100644 --- a/src/Preflight/Models/SettingsTabNames.cs +++ b/src/Preflight/Models/SettingsTabNames.cs @@ -3,7 +3,7 @@ public class SettingsTabNames { public const string Readability = "Readability"; - public const string NaughtyAndNice = "Naught and nice"; + public const string NaughtyAndNice = "Naughty and nice"; public const string Autocorrect = "Autocorrect"; public const string General = "General"; public const string Links = "Links"; diff --git a/src/Preflight/Parsers/BlockListValueParser.cs b/src/Preflight/Parsers/BlockListValueParser.cs new file mode 100644 index 0000000..bf7e498 --- /dev/null +++ b/src/Preflight/Parsers/BlockListValueParser.cs @@ -0,0 +1,14 @@ +using Preflight.Models; +using System; +using System.Collections.Generic; + +namespace Preflight.Parsers +{ + public class BlockListValueParser : IPreflightValueParser + { + public List Parse(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Preflight/Parsers/GridValueParser.cs b/src/Preflight/Parsers/GridValueParser.cs new file mode 100644 index 0000000..bbdb1c2 --- /dev/null +++ b/src/Preflight/Parsers/GridValueParser.cs @@ -0,0 +1,101 @@ +using Newtonsoft.Json.Linq; +using Preflight.Executors; +using Preflight.Extensions; +using Preflight.Models; +using Preflight.Services; +using System.Collections.Generic; +using System.Linq; +#if NETCOREAPP +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Grid; +#else +using Umbraco.Core.Configuration.Grid; +#endif + +namespace Preflight.Parsers +{ + public class GridValueParser : IPreflightValueParser + { + private readonly IPluginExecutor _pluginExecutor; + private readonly IMessenger _messenger; + + private readonly IEnumerable _gridEditorConfig; + + private const string _gridRteJsonPath = "$..controls[?(@.editor.view == 'rte' || @.editor.view == 'textstring' || @.editor.view == 'textarea' || @.editor.alias == 'rte' || @.editor.alias == 'headline' || @.editor.alias == 'quote')]"; + private const string _gridValueJsonPath = "$..value"; + +#if NETCOREAPP + public GridValueParser(IOptions gridConfig, IPluginExecutor pluginExecutor, IMessenger messenger) +#else + public GridValueParser(IGridConfig gridConfig, IPluginExecutor pluginExecutor, IMessenger messenger) +#endif + { + _pluginExecutor = pluginExecutor; + _messenger = messenger; + +#if NETCOREAPP + _gridEditorConfig = gridConfig.Value.EditorsConfig.Editors; +#else + _gridEditorConfig = gridConfig.EditorsConfig.Editors; +#endif + } + + public List Parse(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave) + { + JObject asJson = JObject.Parse(propertyValue); + List response = new List(); + + IEnumerable rows = asJson.SelectTokens("..rows"); + foreach (JToken row in rows) + { + string rowName = row[0].Value("name"); + IEnumerable editors = row.SelectTokens(_gridRteJsonPath); + + foreach (JToken editor in editors) + { + // this is a bit messy - maps the grid editor view to the knownpropertyalias value + var editorViewAlias = ""; + var editorAlias = editor.SelectToken("$..editor.alias")?.ToString(); + + var gridEditorConfig = _gridEditorConfig.FirstOrDefault(x => x.Alias == editorAlias); + var gridEditorName = gridEditorConfig.Name; + var gridEditorView = gridEditorConfig.View; + + string label = $"{propertyName} ({rowName} - {gridEditorName})"; + + JToken value = editor.SelectToken(_gridValueJsonPath); + if (value == null || !value.ToString().HasValue()) + { + _messenger.SendTestResult(new PreflightPropertyResponseModel + { + Name = gridEditorName, + Label = label, + Remove = true + }); + } + else + { + if (gridEditorView.HasValue()) + { + switch (gridEditorView) + { + case "rte": editorViewAlias = KnownPropertyAlias.Rte; break; + case "textstring": editorViewAlias = KnownPropertyAlias.Textbox; break; + case "textarea": editorViewAlias = KnownPropertyAlias.Textarea; break; + } + } + + PreflightPropertyResponseModel model = _pluginExecutor.Execute(propertyName, culture, value.ToString(), editorViewAlias, nodeId, fromSave, KnownPropertyAlias.Grid); + + model.Label = label; + model.TotalTests = model.Plugins.Aggregate(0, (acc, x) => acc + x.TotalTests); + + response.Add(model); + } + } + } + + return response; + } + } +} diff --git a/src/Preflight/Parsers/IPreflightValueParser.cs b/src/Preflight/Parsers/IPreflightValueParser.cs new file mode 100644 index 0000000..b388d10 --- /dev/null +++ b/src/Preflight/Parsers/IPreflightValueParser.cs @@ -0,0 +1,10 @@ +using Preflight.Models; +using System.Collections.Generic; + +namespace Preflight.Parsers +{ + public interface IPreflightValueParser + { + List Parse(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave); + } +} diff --git a/src/Preflight/Parsers/NestedContentValueParser.cs b/src/Preflight/Parsers/NestedContentValueParser.cs new file mode 100644 index 0000000..690df4c --- /dev/null +++ b/src/Preflight/Parsers/NestedContentValueParser.cs @@ -0,0 +1,84 @@ +using Newtonsoft.Json.Linq; +using Preflight.Executors; +using Preflight.Extensions; +using Preflight.Models; +using Preflight.Services; +using System.Collections.Generic; +using System.Linq; +#if NETCOREAPP +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +#else +using Umbraco.Core.Models; +using Umbraco.Core.Services; +#endif + +namespace Preflight.Parsers +{ + public class NestedContentValueParser : IPreflightValueParser + { + private readonly IContentTypeService _contentTypeService; + private readonly IPluginExecutor _pluginExecutor; + private readonly IMessenger _messenger; + + private const string _ncAlias = "ncContentTypeAlias"; + + public NestedContentValueParser(IContentTypeService contentTypeService, IPluginExecutor pluginExecutor, IMessenger messenger) + { + _contentTypeService = contentTypeService; + _pluginExecutor = pluginExecutor; + _messenger = messenger; + } + + public List Parse(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave) + { + Dictionary cache = new Dictionary(); + List response = new List(); + + JArray asJson = JArray.Parse(propertyValue); + var index = 1; + + foreach (JObject o in asJson) + { + var typeAlias = o.Value(_ncAlias); + var type = cache.ContainsKey(typeAlias) ? cache[typeAlias] : _contentTypeService.Get(typeAlias); + + if (!cache.ContainsKey(typeAlias)) + { + cache.Add(typeAlias, type); + } + + var propsFromType = type.GetPreflightProperties(); + + foreach (var property in propsFromType) + { + var value = o.Value(property.Alias); + string label = $"{propertyName} (Item {index} - {property.Name})"; + + if (!value.HasValue()) + { + _messenger.SendTestResult(new PreflightPropertyResponseModel + { + Name = property.Name, + Label = label, + Remove = true + }); + } + else + { + PreflightPropertyResponseModel model = _pluginExecutor.Execute(propertyName, culture, value, property.PropertyEditorAlias, nodeId, fromSave, KnownPropertyAlias.NestedContent); + + model.Label = label; + model.TotalTests = model.Plugins.Aggregate(0, (acc, x) => acc + x.TotalTests); + + response.Add(model); + } + } + + index += 1; + } + + return response; + } + } +} diff --git a/src/Preflight/Parsers/ParserComposer.cs b/src/Preflight/Parsers/ParserComposer.cs new file mode 100644 index 0000000..66ebcfe --- /dev/null +++ b/src/Preflight/Parsers/ParserComposer.cs @@ -0,0 +1,77 @@ +using Preflight.Services; +using Preflight.Services.Implement; +using System; +using System.Collections.Generic; +#if NETCOREAPP +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +#else +using Umbraco.Core; +using Umbraco.Core.Composing; +#endif + +namespace Preflight.Parsers +{ +#if NETCOREAPP + public class PreflightParserComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient>(serviceProvider => key => + { + switch (key) + { + case ParserType.NestedContent: + return serviceProvider.GetService(); + case ParserType.Grid: + return serviceProvider.GetService(); + case ParserType.BlockList: + return serviceProvider.GetService(); + case ParserType.String: + return serviceProvider.GetService(); + default: + throw new KeyNotFoundException($"Could not get Preflight parser for {key}"); + } + }); + } + } +#else + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class PreflightParserComposer : IUserComposer + { + public void Compose(Composition composition) + { + composition.Register(); + + composition.Register(); + composition.Register(); + composition.Register(); + composition.Register(); + composition.Register>(serviceProvider => key => + { + switch (key) + { + case ParserType.NestedContent: + return serviceProvider.GetInstance(); + case ParserType.Grid: + return serviceProvider.GetInstance(); + case ParserType.BlockList: + return serviceProvider.GetInstance(); + case ParserType.String: + return serviceProvider.GetInstance(); + default: + throw new KeyNotFoundException($"Could not get Preflight parser for {key}"); + } + }); + } + } +#endif +} + diff --git a/src/Preflight/Parsers/ParserTypes.cs b/src/Preflight/Parsers/ParserTypes.cs new file mode 100644 index 0000000..98907b2 --- /dev/null +++ b/src/Preflight/Parsers/ParserTypes.cs @@ -0,0 +1,10 @@ +namespace Preflight.Parsers +{ + public enum ParserType + { + NestedContent = 1, + BlockList = 2, + Grid = 3, + String = 4, + } +} diff --git a/src/Preflight/Parsers/StringValueParser.cs b/src/Preflight/Parsers/StringValueParser.cs new file mode 100644 index 0000000..c32755c --- /dev/null +++ b/src/Preflight/Parsers/StringValueParser.cs @@ -0,0 +1,19 @@ +using Preflight.Executors; +using Preflight.Models; +using System; +using System.Collections.Generic; + +namespace Preflight.Parsers +{ + public class StringValueParser : IPreflightValueParser + { + private readonly IPluginExecutor _pluginExecutor; + + public StringValueParser(IPluginExecutor pluginExecutor) => _pluginExecutor = pluginExecutor; + + public List Parse(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Preflight/Plugins/AutocorrectPlugin.cs b/src/Preflight/Plugins/AutocorrectPlugin.cs index 6401426..0e9b934 100644 --- a/src/Preflight/Plugins/AutocorrectPlugin.cs +++ b/src/Preflight/Plugins/AutocorrectPlugin.cs @@ -1,13 +1,13 @@ -using Preflight.Constants; -using Preflight.Extensions; +using Preflight.Extensions; using Preflight.Models; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -#if NET472 -using CharArrays = Umbraco.Core.Constants.CharArrays; -#else +#if NETCOREAPP using CharArrays = Umbraco.Cms.Core.Constants.CharArrays; +#else +using CharArrays = Umbraco.Core.Constants.CharArrays; #endif namespace Preflight.Plugins @@ -37,6 +37,7 @@ public AutocorrectPlugin() Settings = PluginSettingsList.Populate(Name, false, true, + new[] { "7d34c7dd-0167-42c9-a1a4-f7245d5e555a", "9665c018-80be-402f-890a-4bb7f56deaac", "8a567a58-5417-4562-a1b9-12f1e80c8dbb" }, settings: new SettingsModel[] { new GenericSettingModel("Autocorrect terms") { @@ -44,7 +45,8 @@ public AutocorrectPlugin() View = SettingType.MultipleTextbox, Value = "replacethis|new term", Order = 1, - Core = true + Core = true, + Guid = new Guid(KnownSettings.AutocorrectTerms), } } ); diff --git a/src/Preflight/Plugins/IPreflightPlugin.cs b/src/Preflight/Plugins/IPreflightPlugin.cs index 2ce50f3..451fc5e 100644 --- a/src/Preflight/Plugins/IPreflightPlugin.cs +++ b/src/Preflight/Plugins/IPreflightPlugin.cs @@ -1,10 +1,10 @@ using Newtonsoft.Json; using Preflight.Models; using System.Collections.Generic; -#if NET472 -using Umbraco.Core.Composing; -#else +#if NETCOREAPP using Umbraco.Cms.Core.Composing; +#else +using Umbraco.Core.Composing; #endif namespace Preflight.Plugins diff --git a/src/Preflight/Plugins/LinkCheckerPlugin.cs b/src/Preflight/Plugins/LinkCheckerPlugin.cs index 7aad179..bf3c05a 100644 --- a/src/Preflight/Plugins/LinkCheckerPlugin.cs +++ b/src/Preflight/Plugins/LinkCheckerPlugin.cs @@ -1,7 +1,7 @@ -using Preflight.Constants; -using Preflight.Extensions; +using Preflight.Extensions; using Preflight.Models; using Preflight.Services; +using System; using System.Collections.Generic; using System.Linq; @@ -37,6 +37,7 @@ public LinkCheckerPlugin(ILinksService linksService, ISafeBrowsingService safeBr Settings = PluginSettingsList.Populate(Name, false, true, + new[] { "3f7e0bbc-7f76-4471-b700-ac1f20aaa015", "cec5f2d4-d8de-4271-97fe-e9e38cad5259", "a69f2558-2c79-48c5-9ee2-aede89515f74" }, settings: new SettingsModel[] { new GenericSettingModel("Ensure safe links") { @@ -44,7 +45,8 @@ public LinkCheckerPlugin(ILinksService linksService, ISafeBrowsingService safeBr View = SettingType.Boolean, Value = "0", Order = 1, - Core = true + Core = true, + Guid = new Guid(KnownSettings.EnsureSafeLinks), }, new GenericSettingModel("Google SafeBrowsing API key") { @@ -52,7 +54,8 @@ public LinkCheckerPlugin(ILinksService linksService, ISafeBrowsingService safeBr View = SettingType.String, Value = "Get your key from the Google API Console", Order = 2, - Core = true + Core = true, + Guid = new Guid(KnownSettings.GoogleApiKey), } } ); diff --git a/src/Preflight/Plugins/PluginCollection.cs b/src/Preflight/Plugins/PluginCollection.cs index e53ff4b..33ad3b0 100644 --- a/src/Preflight/Plugins/PluginCollection.cs +++ b/src/Preflight/Plugins/PluginCollection.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; -#if NET472 -using Umbraco.Core.Composing; -#else +#if NETCOREAPP using System; using Umbraco.Cms.Core.Composing; +#else +using Umbraco.Core.Composing; #endif namespace Preflight.Plugins @@ -15,10 +15,10 @@ public class PreflightPluginCollectionBuilder : LazyCollectionBuilderBase { -#if NET472 - public PreflightPluginCollection(IEnumerable plugins) : base(plugins) {} -#else +#if NETCOREAPP public PreflightPluginCollection(Func> plugins) : base(plugins) { } +#else + public PreflightPluginCollection(IEnumerable plugins) : base(plugins) {} #endif } } diff --git a/src/Preflight/Plugins/ReadabilityPlugin.cs b/src/Preflight/Plugins/ReadabilityPlugin.cs index a287eef..e680e11 100644 --- a/src/Preflight/Plugins/ReadabilityPlugin.cs +++ b/src/Preflight/Plugins/ReadabilityPlugin.cs @@ -1,6 +1,6 @@ -using Preflight.Constants; -using Preflight.Models; +using Preflight.Models; using Preflight.Services; +using System; using System.Collections.Generic; using System.Linq; @@ -38,6 +38,7 @@ public ReadabilityPlugin(IReadabilityService readabilityService) Settings = PluginSettingsList.Populate(Name, false, false, + new[] { "dfeab01a-e5c0-49ed-a018-9f1c546dfe10", "1485f35b-4c2e-4a53-a0b2-fefa02bcf810", "3984cf76-b598-4bb8-b073-698f39111f7c" }, settings: new SettingsModel[] { new GenericSettingModel("Readability target - minimum") { @@ -46,6 +47,7 @@ public ReadabilityPlugin(IReadabilityService readabilityService) View = SettingType.Slider, Order = 1, Core = true, + Guid = new Guid(KnownSettings.ReadabilityMin), }, new GenericSettingModel("Readability target - maximum") { @@ -54,6 +56,7 @@ public ReadabilityPlugin(IReadabilityService readabilityService) View = SettingType.Slider, Order = 2, Core = true, + Guid = new Guid(KnownSettings.ReadabilityMax), }, new GenericSettingModel("Long word syllable count") { @@ -63,6 +66,7 @@ public ReadabilityPlugin(IReadabilityService readabilityService) View = SettingType.Slider, Order = 3, Core = true, + Guid = new Guid(KnownSettings.LongWordSyllables), } } ); diff --git a/src/Preflight/PreflightComponent.cs b/src/Preflight/PreflightComponent.cs index 831124c..9a51657 100644 --- a/src/Preflight/PreflightComponent.cs +++ b/src/Preflight/PreflightComponent.cs @@ -1,4 +1,4 @@ -#if NET472 +#if NETFRAMEWORK using Preflight.Executors; using Preflight.Migrations; using System; @@ -50,8 +50,8 @@ public PreflightComponent( public void Initialize() { - //var upgrader = new Upgrader(new PreflightMigrationPlan()); - //upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger); + var upgrader = new Upgrader(new PreflightMigrationPlan()); + upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger); ServerVariablesParser.Parsing += ServerVariablesParser_Parsing; ContentService.Saving += ContentService_Saving; diff --git a/src/Preflight/PreflightComposer.cs b/src/Preflight/PreflightComposer.cs index 4cd05c4..a797f3c 100644 --- a/src/Preflight/PreflightComposer.cs +++ b/src/Preflight/PreflightComposer.cs @@ -3,13 +3,7 @@ using Preflight.Plugins; using Preflight.IO; using Preflight.Services.Implement; -#if NET472 -using Preflight.Security; -using Preflight.Logging; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Composing; -#else +#if NETCOREAPP using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Preflight.Handlers; @@ -18,41 +12,17 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; +#else +using Preflight.Security; +using Preflight.Logging; +using System.Web; +using Umbraco.Core; +using Umbraco.Core.Composing; #endif namespace Preflight { -#if NET472 - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - public class PreflightComposer : IUserComposer - { - public void Compose(Composition composition) - { - composition.Register(); - composition.Register(); - composition.Register(); - composition.Register(); - - composition.Register(); - composition.Register(); - composition.Register(); - composition.Register(); - composition.Register(); - composition.Register(); - - composition.Register(); - composition.Register(typeof(ILogger<>), typeof(Logger<>)); - composition.Register(); - - composition.WithCollectionBuilder() - .Add(() => composition.TypeLoader.GetTypes()); - - composition.Components().Append(); - - PreflightContext.Set(new HttpContextWrapper(HttpContext.Current)); - } - } -#else +#if NETCOREAPP public class PreflightComposer : IComposer { public void Compose(IUmbracoBuilder builder) @@ -65,11 +35,13 @@ public void Compose(IUmbracoBuilder builder) .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSignalR(); @@ -100,5 +72,37 @@ public void Compose(IUmbracoBuilder builder) }); } } +#else + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class PreflightComposer : IUserComposer + { + public void Compose(Composition composition) + { + composition.Register(); + composition.Register(); + composition.Register(); + composition.Register(); + composition.Register(); + + composition.Register(); + composition.Register(); + composition.Register(); + composition.Register(); + composition.Register(); + composition.Register(); + + composition.Register(); + composition.Register(); + composition.Register(typeof(ILogger<>), typeof(Logger<>)); + composition.Register(); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetTypes()); + + composition.Components().Append(); + + PreflightContext.Set(new HttpContextWrapper(HttpContext.Current)); + } + } #endif } diff --git a/src/Preflight/PreflightContext.cs b/src/Preflight/PreflightContext.cs index 0125578..258f39e 100644 --- a/src/Preflight/PreflightContext.cs +++ b/src/Preflight/PreflightContext.cs @@ -1,18 +1,43 @@ using System.Diagnostics; - -#if NET472 -using System.Web; -#else +#if NETCOREAPP using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; +#else +using System.Web; #endif namespace Preflight { public static class PreflightContext { -#if NET472 +#if NETCOREAPP + public static HttpContext Current => _httpContextAccessor.HttpContext; + + private static bool _isDebuggingEnabled; + public static bool IsDebuggingEnabled + { + get + { + return _isDebuggingEnabled || _hostEnvironment.IsDevelopment() && Debugger.IsAttached; ; + } + private set + { + _isDebuggingEnabled = value; + } + } + public static string HostUrl => Current.Request.Host.Value; + + private static IHttpContextAccessor _httpContextAccessor; + private static IWebHostEnvironment _hostEnvironment; + + internal static void Set(IHttpContextAccessor httpContextAccessor, IWebHostEnvironment hostEnvironment, bool isDebuggingEnabled = false) + { + _httpContextAccessor = httpContextAccessor; + _hostEnvironment = hostEnvironment; + IsDebuggingEnabled = isDebuggingEnabled; + } +#else private static HttpContextBase _context; /// @@ -58,32 +83,6 @@ internal static void Set(HttpContextBase context, bool isDebuggingEnabled = fals _context = context; IsDebuggingEnabled = isDebuggingEnabled; } -#else - public static HttpContext Current => _httpContextAccessor.HttpContext; - - private static bool _isDebuggingEnabled; - public static bool IsDebuggingEnabled - { - get - { - return _isDebuggingEnabled || _hostEnvironment.IsDevelopment() && Debugger.IsAttached; ; - } - private set - { - _isDebuggingEnabled = value; - } - } - public static string HostUrl => Current.Request.Host.Value; - - private static IHttpContextAccessor _httpContextAccessor; - private static IWebHostEnvironment _hostEnvironment; - - internal static void Set(IHttpContextAccessor httpContextAccessor, IWebHostEnvironment hostEnvironment, bool isDebuggingEnabled = false) - { - _httpContextAccessor = httpContextAccessor; - _hostEnvironment = hostEnvironment; - IsDebuggingEnabled = isDebuggingEnabled; - } #endif } } \ No newline at end of file diff --git a/src/Preflight/Services/IContentChecker.cs b/src/Preflight/Services/IContentChecker.cs index 63a4bf5..ec058c2 100644 --- a/src/Preflight/Services/IContentChecker.cs +++ b/src/Preflight/Services/IContentChecker.cs @@ -1,8 +1,8 @@ using Preflight.Models; -#if NET472 -using Umbraco.Core.Models; -#else +#if NETCOREAPP using Umbraco.Cms.Core.Models; +#else +using Umbraco.Core.Models; #endif namespace Preflight.Services diff --git a/src/Preflight/Services/IMessenger.cs b/src/Preflight/Services/IMessenger.cs new file mode 100644 index 0000000..33a337e --- /dev/null +++ b/src/Preflight/Services/IMessenger.cs @@ -0,0 +1,10 @@ +using Preflight.Models; + +namespace Preflight.Services +{ + public interface IMessenger + { + void SendTestResult(PreflightPropertyResponseModel model); + void PreflightComplete(); + } +} diff --git a/src/Preflight/Services/IValueParserService.cs b/src/Preflight/Services/IValueParserService.cs new file mode 100644 index 0000000..07db2f1 --- /dev/null +++ b/src/Preflight/Services/IValueParserService.cs @@ -0,0 +1,13 @@ +using Preflight.Models; +using System.Collections.Generic; + +namespace Preflight.Services +{ + public interface IValueParserService + { + List ParseNestedContent(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave); + List ParseGridContent(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave); + List ParseBlockListContent(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave); + PreflightPropertyResponseModel ParseStringContent(string propertyName, string propertyValue, string culture, string propertyAlias, int nodeId, bool fromSave); + } +} diff --git a/src/Preflight/Services/Implement/ContentChecker.cs b/src/Preflight/Services/Implement/ContentChecker.cs index 9caa68c..80bb976 100644 --- a/src/Preflight/Services/Implement/ContentChecker.cs +++ b/src/Preflight/Services/Implement/ContentChecker.cs @@ -1,27 +1,16 @@ -using Newtonsoft.Json.Linq; -using Preflight.Constants; -using Preflight.Extensions; -using Preflight.Hubs; +using Preflight.Extensions; using Preflight.Models; -using Preflight.Plugins; using System; using System.Collections.Generic; using System.Linq; -#if NET472 -using Microsoft.AspNet.SignalR; -using Preflight.Logging; -using Umbraco.Core; -using Umbraco.Core.Configuration.Grid; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using IProperty = Umbraco.Core.Models.Property; -#else -using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Logging; +#if NETCOREAPP using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Configuration.Grid; using Umbraco.Cms.Core.Services; +#else +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using IProperty = Umbraco.Core.Models.Property; #endif namespace Preflight.Services.Implement @@ -32,65 +21,19 @@ namespace Preflight.Services.Implement internal class ContentChecker : IContentChecker { private readonly IContentService _contentService; - private readonly IContentTypeService _contentTypeService; - private readonly ISettingsService _settingsService; - private readonly ILogger _logger; - private readonly PreflightPluginCollection _plugins; - -#if NET472 - private readonly IHubContext _hubContext; -#else - private readonly IHubContext _hubContext; -#endif + private readonly IMessenger _messenger; + private readonly IValueParserService _valueParserService; private int _id; private bool _fromSave; - private List _settings; - private string _testableProperties; - - private readonly IEnumerable _gridEditorConfig; -#if NET472 - public ContentChecker(ISettingsService settingsService, IContentService contentService, IGridConfig gridConfig, - IContentTypeService contentTypeService, ILogger logger, PreflightPluginCollection plugins) -#else - public ContentChecker(IHubContext hubContext, ISettingsService settingsService, IOptions gridConfig, - IContentService contentService, IContentTypeService contentTypeService, ILogger logger, PreflightPluginCollection plugins) -#endif + public ContentChecker(IContentService contentService, IMessenger messenger, IValueParserService valueParserService) { - _settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); - _contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _plugins = plugins ?? throw new ArgumentNullException(nameof(plugins)); - -#if NET472 - _hubContext = GlobalHost.ConnectionManager.GetHubContext(); - _gridEditorConfig = gridConfig.EditorsConfig.Editors; -#else - _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); - _gridEditorConfig = gridConfig.Value.EditorsConfig.Editors; -#endif - } - - /// - /// Intialise variables for this testing run - /// - private void Initialise(string culture) - { - _settings = _settingsService.Get().Settings; - _testableProperties = _settings.GetValue(KnownSettings.PropertiesToTest, culture); + _messenger = messenger ?? throw new ArgumentNullException(nameof(messenger)); + _valueParserService = valueParserService ?? throw new ArgumentNullException(nameof(valueParserService)); } - private void SendTestResult(PreflightPropertyResponseModel model) - { - _hubContext.Clients.All.preflightTest(model); - } - - private void PreflightComplete() - { - _hubContext.Clients.All.preflightComplete(); - } /// /// @@ -98,9 +41,9 @@ private void PreflightComplete() /// public bool CheckDirty(DirtyProperties dirtyProperties) { - Initialise(dirtyProperties.Culture); - _id = dirtyProperties.Id; + _fromSave = false; + var failed = false; foreach (SimpleProperty prop in dirtyProperties.Properties) @@ -110,7 +53,7 @@ public bool CheckDirty(DirtyProperties dirtyProperties) // only continue if the prop has a value if (!propValue.HasValue()) { - SendTestResult(new PreflightPropertyResponseModel + _messenger.SendTestResult(new PreflightPropertyResponseModel { Name = prop.Name, Remove = true @@ -122,7 +65,7 @@ public bool CheckDirty(DirtyProperties dirtyProperties) failed = TestAndBroadcast(prop.Name, dirtyProperties.Culture, propValue, prop.Editor) || failed; } - PreflightComplete(); + _messenger.PreflightComplete(); return failed; } @@ -132,6 +75,7 @@ public bool CheckDirty(DirtyProperties dirtyProperties) /// /// /// + /// /// /// public bool CheckContent(int id, string culture, bool fromSave) => CheckContent(_contentService.GetById(id), culture, fromSave); @@ -141,25 +85,26 @@ public bool CheckDirty(DirtyProperties dirtyProperties) /// Checks all testable properties on the given IContent item /// /// + /// /// /// public bool CheckContent(IContent content, string culture, bool fromSave) { - Initialise(culture); - + _id = content.Id; _fromSave = fromSave; + var failed = false; IEnumerable props = content.GetPreflightProperties(); foreach (IProperty prop in props) { - string propValue = (prop.GetValue(culture) ?? prop.GetValue()).ToString(); + string propValue = (prop.GetValue(culture) ?? prop.GetValue())?.ToString(); // only continue if the prop has a value if (!propValue.HasValue()) { - SendTestResult(new PreflightPropertyResponseModel + _messenger.SendTestResult(new PreflightPropertyResponseModel { Name = prop.PropertyType.Name, Remove = true @@ -171,7 +116,7 @@ public bool CheckContent(IContent content, string culture, bool fromSave) failed = TestAndBroadcast(prop.PropertyType.Name, culture, propValue, prop.PropertyType.PropertyEditorAlias) || failed; } - PreflightComplete(); + _messenger.PreflightComplete(); return failed; } @@ -181,6 +126,7 @@ public bool CheckContent(IContent content, string culture, bool fromSave) /// /// /// + /// /// /// /// @@ -193,15 +139,18 @@ private bool TestAndBroadcast(string name, string culture, string value, string switch (alias) { case KnownPropertyAlias.NestedContent: - testResult = ExtractValuesFromNestedContentProperty(name, culture, value); + testResult = _valueParserService.ParseNestedContent(name, value, culture, _id, _fromSave); break; case KnownPropertyAlias.Grid: - testResult = ExtractValuesFromGridProperty(name, culture, value); + testResult = _valueParserService.ParseGridContent(name, value, culture, _id, _fromSave); + break; + case KnownPropertyAlias.BlockList: + testResult = _valueParserService.ParseBlockListContent(name, value, culture, _id, _fromSave); break; case KnownPropertyAlias.Rte: case KnownPropertyAlias.Textarea: case KnownPropertyAlias.Textbox: - testResult = new[] { RunPluginsAgainstValue(name, culture, value, alias) }.ToList(); + testResult = new[] { _valueParserService.ParseStringContent(name, value, culture, alias, _id, _fromSave) }.ToList(); break; } @@ -217,200 +166,11 @@ private bool TestAndBroadcast(string name, string culture, string value, string } // announce the result - SendTestResult(result); + _messenger.SendTestResult(result); } } return failed; - } - - - /// - /// Extracts the testable values from a Nested Content instance, and passes each to - /// - /// - /// - /// - private List ExtractValuesFromNestedContentProperty(string propName, string culture, string propValue) - { - Dictionary cache = new Dictionary(); - List response = new List(); - - JArray asJson = JArray.Parse(propValue); - var index = 1; - - foreach (JObject o in asJson) - { - var typeAlias = o.Value(KnownStrings.NcAlias); - var type = cache.ContainsKey(typeAlias) ? cache[typeAlias] : _contentTypeService.Get(typeAlias); - - if (!cache.ContainsKey(typeAlias)) - { - cache.Add(typeAlias, type); - } - - var propsFromType = type.GetPreflightProperties(); - - foreach (var property in propsFromType) - { - var value = o.Value(property.Alias); - string label = $"{propName} (Item {index} - {property.Name})"; - - if (!value.HasValue()) - { - SendTestResult(new PreflightPropertyResponseModel - { - Name = property.Name, - Label = label, - Remove = true - }); - } - else - { - PreflightPropertyResponseModel model = RunPluginsAgainstValue(propName, culture, value, property.PropertyEditorAlias, KnownPropertyAlias.NestedContent); - - model.Label = label; - model.TotalTests = model.Plugins.Aggregate(0, (acc, x) => acc + x.TotalTests); - - response.Add(model); - } - } - - index += 1; - } - - return response; - } - - - /// - /// Extracts the testable values from a single Grid editor, and passes each to - /// - /// - /// - /// - private List ExtractValuesFromGridProperty(string name, string culture, string propValue) - { - JObject asJson = JObject.Parse(propValue); - List response = new List(); - - IEnumerable rows = asJson.SelectTokens("..rows"); - foreach (JToken row in rows) - { - string rowName = row[0].Value("name"); - IEnumerable editors = row.SelectTokens(KnownStrings.GridRteJsonPath); - - foreach (JToken editor in editors) - { - // this is a bit messy - maps the grid editor view to the knownpropertyalias value - var editorViewAlias = ""; - var editorAlias = editor.SelectToken("$..editor.alias")?.ToString(); - - var gridEditorConfig = _gridEditorConfig.FirstOrDefault(x => x.Alias == editorAlias); - var gridEditorName = gridEditorConfig.Name; - var gridEditorView = gridEditorConfig.View; - - string label = $"{name} ({rowName} - {gridEditorName})"; - - JToken value = editor.SelectToken(KnownStrings.GridValueJsonPath); - if (value == null || !value.ToString().HasValue()) - { - SendTestResult(new PreflightPropertyResponseModel - { - Name = gridEditorName, - Label = label, - Remove = true - }); - } - else - { - if (gridEditorView.HasValue()) - { - switch (gridEditorView) - { - case "rte": editorViewAlias = KnownPropertyAlias.Rte; break; - case "textstring": editorViewAlias = KnownPropertyAlias.Textbox; break; - case "textarea": editorViewAlias = KnownPropertyAlias.Textarea; break; - } - } - - PreflightPropertyResponseModel model = RunPluginsAgainstValue(name, culture, value.ToString(), editorViewAlias, KnownPropertyAlias.Grid); - - model.Label = label; - model.TotalTests = model.Plugins.Aggregate(0, (acc, x) => acc + x.TotalTests); - - response.Add(model); - } - } - } - - return response; - } - - - /// - /// Runs the set of plugins against the given string - /// - /// - /// - /// - private PreflightPropertyResponseModel RunPluginsAgainstValue(string name, string culture, string val, string alias, string parentAlias = "") - { - var model = new PreflightPropertyResponseModel - { - Label = name, - Name = name - }; - - if (val == null || !_testableProperties.Contains(alias) || (parentAlias.HasValue() && !_testableProperties.Contains(parentAlias))) - return model; - - foreach (IPreflightPlugin plugin in _plugins) - { - // settings on the plugin are the defaults - set to correct values from _settings - plugin.Settings = _settings.Where(s => s.Tab == plugin.Name)?.ToList(); - - // ignore disabled plugins - if (plugin.IsDisabled()) continue; - if (!_fromSave && plugin.IsOnSaveOnly()) continue; - - string propsValue = plugin.Settings.FirstOrDefault(x => x.Alias.Contains("PropertiesToTest"))?.Value.ForVariant(culture); - string propsToTest = propsValue ?? string.Join(",", KnownPropertyAlias.All); - - // only continue if the field alias is include for testing, or the parent alias has been set, and is included for testing - if (!propsToTest.Contains(alias) || (parentAlias.HasValue() && !propsToTest.Contains(parentAlias))) continue; - - try - { - Type pluginType = plugin.GetType(); - if (pluginType.GetMethod("Check") == null) continue; - - plugin.Check(_id, culture, val, _settings); - - if (plugin.Result != null) - { - if (plugin.FailedCount == 0) - { - plugin.FailedCount = plugin.Failed ? 1 : 0; - } - - model.Plugins.Add(plugin); - } - } - catch (Exception e) - { - _logger.LogError(e, "Preflight couldn't take off: {Message}", e.Message); - } - } - - // mark as failed if any sub-tests have failed - model.FailedCount = model.Plugins.Sum(x => x.FailedCount); - model.Failed = model.FailedCount > 0; - - model.Plugins = model.Plugins.OrderBy(p => p.SortOrder).ToList(); - model.TotalTests = model.Plugins.Aggregate(0, (acc, x) => acc + x.TotalTests); - - return model; - } + } } } diff --git a/src/Preflight/Services/Implement/LinksService.cs b/src/Preflight/Services/Implement/LinksService.cs index e71193d..8cbe22e 100644 --- a/src/Preflight/Services/Implement/LinksService.cs +++ b/src/Preflight/Services/Implement/LinksService.cs @@ -1,5 +1,4 @@ using HtmlAgilityPack; -using Preflight.Constants; using Preflight.Extensions; using Preflight.Models; using System; diff --git a/src/Preflight/Services/Implement/Messenger.cs b/src/Preflight/Services/Implement/Messenger.cs new file mode 100644 index 0000000..41bdc92 --- /dev/null +++ b/src/Preflight/Services/Implement/Messenger.cs @@ -0,0 +1,31 @@ +using Preflight.Hubs; +using Preflight.Models; +#if NETCOREAPP +using Microsoft.AspNetCore.SignalR; +#else +using Microsoft.AspNet.SignalR; +#endif + +namespace Preflight.Services.Implement +{ + public class Messenger : IMessenger + { +#if NETCOREAPP + private readonly IHubContext _hubContext; + + public Messenger(IHubContext hubContext) => _hubContext = hubContext; +#else + private readonly IHubContext _hubContext; + + public Messenger() => + _hubContext = GlobalHost.ConnectionManager.GetHubContext(); +#endif + + public void SendTestResult(PreflightPropertyResponseModel model) => + _hubContext.Clients.All.preflightTest(model); + + public void PreflightComplete() => + _hubContext.Clients.All.preflightComplete(); + + } +} diff --git a/src/Preflight/Services/Implement/ReadabilityService.cs b/src/Preflight/Services/Implement/ReadabilityService.cs index c7e3fdb..86de8ff 100644 --- a/src/Preflight/Services/Implement/ReadabilityService.cs +++ b/src/Preflight/Services/Implement/ReadabilityService.cs @@ -1,14 +1,14 @@ -using Preflight.Constants; -using Preflight.Extensions; +using Preflight.Extensions; using Preflight.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -#if NET472 -using CharArrays = Umbraco.Core.Constants.CharArrays; -#else +using Readability = Preflight.Constants.Readability; +#if NETCOREAPP using CharArrays = Umbraco.Cms.Core.Constants.CharArrays; +#else +using CharArrays = Umbraco.Core.Constants.CharArrays; #endif namespace Preflight.Services.Implement @@ -47,12 +47,12 @@ public ReadabilityResponseModel Check(string text, string culture, List longWords = new List(); List blacklisted = new List(); - text = Regex.Replace(text, KnownStrings.ClosingHtmlTags, "."); - text = text.Replace(KnownStrings.NewLine, " "); - text = Regex.Replace(text, KnownStrings.CharsToRemove, string.Empty).Replace("&", "&"); - text = Regex.Replace(text, KnownStrings.DuplicateSpaces, " "); + text = Regex.Replace(text, Readability.ClosingHtmlTags, Readability.Period); + text = text.Replace(Environment.NewLine, Readability.Space); + text = Regex.Replace(text, Readability.CharsToRemove, string.Empty).Replace(Readability.AmpersandEntity, Readability.Ampersand); + text = Regex.Replace(text, Readability.DuplicateSpaces, Readability.Space); - List sentences = text.Split(KnownStrings.WordDelimiters).Where(s => s.HasValue() && s.Length > 3).ToList(); + List sentences = text.Split(Readability.WordDelimiters).Where(s => s.HasValue() && s.Length > 3).ToList(); // calc words/sentence double totalWords = 0; @@ -125,7 +125,7 @@ private static int CountSyllables(string word) { var foundVowel = false; - foreach (char vowel in KnownStrings.Vowels) + foreach (char vowel in Constants.Readability.Vowels) { //don't count diphthongs if (vowel == character && lastWasVowel) @@ -157,7 +157,7 @@ private static int CountSyllables(string word) string lastTwoChars = currentWord.Substring(currentWord.Length - 2).ToLower(); string lastThreeChars = currentWord.Substring(currentWord.Length - 3).ToLower(); - if (KnownStrings.Endings.Contains(lastTwoChars) && lastThreeChars != "ied" || lastTwoChars.First() != 'l' && lastTwoChars.Last() == 'e') + if (Readability.Endings.Contains(lastTwoChars) && lastThreeChars != Readability.Ied || lastTwoChars.First() != Readability.l && lastTwoChars.Last() == Readability.e) { numVowels--; } diff --git a/src/Preflight/Services/Implement/SafeBrowsingService.cs b/src/Preflight/Services/Implement/SafeBrowsingService.cs index 1dd967b..0a7fe7c 100644 --- a/src/Preflight/Services/Implement/SafeBrowsingService.cs +++ b/src/Preflight/Services/Implement/SafeBrowsingService.cs @@ -1,14 +1,12 @@ using HtmlAgilityPack; using Newtonsoft.Json; -using Preflight.Constants; using Preflight.Extensions; using Preflight.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; -#if NET472 -#else +#if NETCOREAPP using Umbraco.Extensions; #endif @@ -17,6 +15,8 @@ namespace Preflight.Services.Implement public class SafeBrowsingService : ISafeBrowsingService { private readonly ICacheManager _cacheManager; + private const string _safeBrowsingUrl = "https://safebrowsing.googleapis.com/v4/threatMatches:find?key="; + private const string _cacheKey = "Preflight_SafeBrowsing_"; public SafeBrowsingService(ICacheManager cacheManager) { @@ -50,7 +50,7 @@ public List Check(string text, string apiKey) foreach (string href in hrefs) { - if (!_cacheManager.TryGet(KnownStrings.CacheKey + href, out BrokenLinkModel cacheItem)) + if (!_cacheManager.TryGet(_cacheKey + href, out BrokenLinkModel cacheItem)) continue; fromCache.Add(cacheItem); @@ -72,7 +72,7 @@ public List Check(string text, string apiKey) foreach (BrokenLinkModel item in response) { - _cacheManager.Set(KnownStrings.CacheKey + item.Href, item); + _cacheManager.Set(_cacheKey + item.Href, item); } } @@ -92,7 +92,7 @@ private static SafeBrowsingResponseModel SafeBrowsingLookup(IEnumerable try { string result; - string url = KnownStrings.SafeBrowsingUrl + apiKey; + string url = _safeBrowsingUrl + apiKey; ThreatEntry[] threatEntries = urls.Select(u => new ThreatEntry { Url = u }).ToArray(); diff --git a/src/Preflight/Services/Implement/SettingsService.cs b/src/Preflight/Services/Implement/SettingsService.cs index 4a34a3b..0e51782 100644 --- a/src/Preflight/Services/Implement/SettingsService.cs +++ b/src/Preflight/Services/Implement/SettingsService.cs @@ -1,21 +1,23 @@ -using Newtonsoft.Json; -using Preflight.Constants; -using Preflight.Extensions; +using Preflight.Extensions; using Preflight.Models; using Preflight.Plugins; using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using Preflight.IO; -#if NET472 -using Preflight.Logging; -using Umbraco.Core; -using Umbraco.Core.Services; -#else +using Preflight.Migrations.Schema; +using Preflight.Settings; +#if NETCOREAPP using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; +using CharArrays = Umbraco.Cms.Core.Constants.CharArrays; +#else +using Preflight.Logging; +using Umbraco.Core; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using CharArrays = Umbraco.Core.Constants.CharArrays; #endif namespace Preflight.Services.Implement @@ -24,29 +26,31 @@ public class SettingsService : ISettingsService { private readonly ILogger _logger; private readonly IUserService _userService; - private readonly IIOHelper _ioHelper; private readonly ICacheManager _cacheManager; private readonly ILocalizationService _localizationService; + private readonly IScopeProvider _scopeProvider; private readonly PreflightPluginCollection _plugins; + private const string _settingsCacheKey = "Preflight_Settings_"; + /// /// /// /// public SettingsService( - ILogger logger, - IUserService userService, - PreflightPluginCollection plugins, - IIOHelper ioHelper, - ICacheManager cacheManager, - ILocalizationService localizationService) + ILogger logger, + IUserService userService, + PreflightPluginCollection plugins, + ICacheManager cacheManager, + ILocalizationService localizationService, + IScopeProvider scopeProvider) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _plugins = plugins ?? throw new ArgumentNullException(nameof(plugins)); - _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); _cacheManager = cacheManager ?? throw new ArgumentNullException(nameof(cacheManager)); _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); } /// @@ -54,14 +58,14 @@ public SettingsService( /// public PreflightSettings Get() { - //if (_cacheManager.TryGet(KnownStrings.SettingsCacheKey, out PreflightSettings fromCache)) - // return fromCache; + if (_cacheManager.TryGet(_settingsCacheKey, out PreflightSettings fromCache)) + return fromCache; PreflightSettings settings = GetSettings(); if (settings != null) { - _cacheManager.Set(KnownStrings.SettingsCacheKey, settings); + _cacheManager.Set(_settingsCacheKey, settings); return settings; } @@ -78,13 +82,20 @@ public bool Save(PreflightSettings settings) { try { - _cacheManager.Set(KnownStrings.SettingsCacheKey, settings); + _cacheManager.Set(_settingsCacheKey, settings); - // only persist the settings, tabs can be regenerated on startup - using (var file = new StreamWriter(_ioHelper.MapPath(KnownStrings.SettingsFilePath), false)) + using (var scope = _scopeProvider.CreateScope()) { - var serializer = new JsonSerializer(); - serializer.Serialize(file, settings.Settings); + foreach (var setting in settings.Settings) + { + scope.Database.Update(new SettingsSchema + { + Id = setting.Id, + Setting = setting.Guid, + Value = setting.Value.ToString(), + }); + } + scope.Complete(); } return true; } @@ -101,67 +112,82 @@ private PreflightSettings GetSettings() var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); var allLanguages = _localizationService.GetAllLanguages().Select(l => l.IsoCode); - // only get here when nothing is cached - List settings = new List(); - List tabs = new List(); - - // json initially stores the core checks only - // once it has been saved in the backoffice, settings store all current plugins, with alias - using (var file = new StreamReader(_ioHelper.MapPath(KnownStrings.SettingsFilePath))) + List schema = new List(); + using (var scope = _scopeProvider.CreateScope()) { - string json = file.ReadToEnd(); - settings = JsonConvert.DeserializeObject>(json); + schema = scope.Database.Fetch(); + scope.Complete(); } + List settings = CollateCoreAndPluginSettings(); - // populate prevalues for the groups setting - // intersect ensures any removed groups aren't kept as settings values - // since group name and alias can differ, needs to store both in the prevalue, and manage rebuilding this on the client side - var allGroups = _userService.GetAllUserGroups(); - var groupSetting = settings.FirstOrDefault(x => string.Equals(x.Label, KnownSettings.UserGroupOptIn, StringComparison.InvariantCultureIgnoreCase)); + MergeSchemaValuesIntoSettings(allLanguages, schema, settings); - if (groupSetting != null) - { - var groupNames = allGroups.Select(x => new { value = x.Name, key = x.Alias }); - groupSetting.Prevalues = groupNames; + EnsureGroupsAreStillValid(settings); - // here the value is a string and needs to be manually updated to a dictionary + var tabs = GenerateTabsFromSettings(settings); - var value = allLanguages.ToDictionary(k => k, v => groupSetting.Value?.ToString()); - if (value.Any()) - { - var newValue = new Dictionary(); - foreach (var variantValue in value) - { - var groupSettingValue = variantValue.Value.Split(',').Intersect(groupNames.Select(x => x.value)); - newValue.Add(variantValue.Key, string.Join(",", groupSettingValue)); - } + FinaliseSettings(tabs, settings); - groupSetting.Value = newValue; - } - } + // tabs are sorted alpha, with general first + return new PreflightSettings + { + Settings = settings.DistinctBy(s => (s.Tab, s.Label)).ToList(), + Tabs = tabs.GroupBy(x => x.Name) + .Select(y => y.First()) + .OrderBy(i => i.Name != SettingsTabNames.General) + .ThenBy(i => i.Name).ToList() + }; + } - // populate prevalues for subsetting testable properties, default value to all if nothing exists - var testablePropertiesProp = settings.FirstOrDefault(x => string.Equals(x.Label, KnownSettings.PropertiesToTest, StringComparison.InvariantCultureIgnoreCase)); + private List CollateCoreAndPluginSettings() + { + var settings = new CoreSettings().Settings + .Concat(new NaughtyNiceSettings().Settings).ToList(); - if (testablePropertiesProp != null) + var pluginSettings = _plugins.Where(p => p.Settings.Any()).SelectMany(p => p.Settings); + settings.AddRange(pluginSettings); + return settings; + } + + /// + /// + /// + /// + /// + private static void FinaliseSettings(List tabs, List settings) + { + foreach (SettingsModel s in settings) { - testablePropertiesProp.Prevalues = KnownPropertyAlias.All.Select(x => new { value = x, key = x }); + if (!s.Alias.HasValue()) + { + s.Alias = s.Label.Camel(); + } - var value = allLanguages.ToDictionary(k => k, v => testablePropertiesProp.Value?.ToString()); - if (!value.Any()) + if (!s.View.Contains(".html")) { - var newValue = new Dictionary(); - foreach (var variantValue in value) - { - newValue.Add(variantValue.Key, string.Join(",", KnownPropertyAlias.All)); - } + s.View = "views/propertyeditors/" + s.View + "/" + s.View + ".html"; + } - testablePropertiesProp.Value = newValue; + if (!tabs.Any(x => x.Name == s.Tab)) + { + tabs.Add(new SettingsTab + { + Name = s.Tab + }); } } + } + + /// + /// + /// + /// + /// + private List GenerateTabsFromSettings(List settings) + { + List tabs = new List(); - // adds all discovered plugins foreach (IPreflightPlugin plugin in _plugins) { foreach (SettingsModel setting in plugin.Settings) @@ -183,47 +209,71 @@ private PreflightSettings GetSettings() }); } - foreach (SettingsModel s in settings) + return tabs; + } + + /// + /// populate prevalues for the groups setting + /// intersect ensures any removed groups aren't kept as settings values + /// since group name and alias can differ, needs to store both in the prevalue, and manage rebuilding this on the client side + /// + /// + private void EnsureGroupsAreStillValid(List settings) + { + var allGroups = _userService.GetAllUserGroups(); + var groupSetting = settings.FirstOrDefault(x => string.Equals(x.Label, KnownSettings.UserGroupOptIn, StringComparison.InvariantCultureIgnoreCase)); + + if (groupSetting != null) { - if (!s.Alias.HasValue()) - { - s.Alias = s.Label.Camel(); - } + var groupNames = allGroups.Select(x => new { value = x.Name, key = x.Alias }); + groupSetting.Prevalues = groupNames; - if (!s.View.Contains(".html")) + if (groupSetting.Value != null) { - s.View = "views/propertyeditors/" + s.View + "/" + s.View + ".html"; + var newValue = new Dictionary(); + foreach (var variantValue in groupSetting.Value.ToVariantDictionary()) + { + var groupSettingValue = variantValue.Value.Split(CharArrays.Comma).Intersect(groupNames.Select(x => x.value)); + newValue.Add(variantValue.Key, string.Join(KnownStrings.Comma, groupSettingValue)); + } + + groupSetting.Value = newValue; } + } + } - if (!tabs.Any(x => x.Name == s.Tab)) + /// + /// + /// + /// + /// + /// + private static void MergeSchemaValuesIntoSettings(IEnumerable allLanguages, List schema, List settings) + { + foreach (SettingsModel setting in settings) + { + var schemaItem = schema.FirstOrDefault(s => s.Setting == setting.Guid); + var value = schemaItem.Value; + + // if no value came from the db, assume it's a new plugin and use the default + var valueDictionary = value.HasValue() ? + value.ToVariantDictionary() : + allLanguages.ToDictionary(k => k, v => setting.Value.ToString()); + + // have new languages been added? better check... + // if any are missing, add the default value + if (valueDictionary.Count != allLanguages.Count()) { - tabs.Add(new SettingsTab + var missingLangs = allLanguages.Except(valueDictionary.Keys); + foreach (var lang in missingLangs) { - Name = s.Tab - }); + valueDictionary[lang] = setting.Value.ToString(); + } } - // if the values are already variant, do nothing - // otherwise create a dictionary for all languages, with the default value - try - { - var _ = s.Value.ToVariantDictionary(); - } - catch - { - s.Value = allLanguages.ToDictionary(key => key, value => s.Value?.ToString() ?? ""); - } + setting.Value = valueDictionary; + setting.Id = schemaItem.Id; } - - // tabs are sorted alpha, with general first - return new PreflightSettings - { - Settings = settings.DistinctBy(s => (s.Tab, s.Label)).ToList(), - Tabs = tabs.GroupBy(x => x.Name) - .Select(y => y.First()) - .OrderBy(i => i.Name != SettingsTabNames.General) - .ThenBy(i => i.Name).ToList() - }; - } + } } } diff --git a/src/Preflight/Services/Implement/ValueParserService.cs b/src/Preflight/Services/Implement/ValueParserService.cs new file mode 100644 index 0000000..0427507 --- /dev/null +++ b/src/Preflight/Services/Implement/ValueParserService.cs @@ -0,0 +1,42 @@ +using Preflight.Executors; +using Preflight.Models; +using Preflight.Parsers; +using System; +using System.Collections.Generic; + +namespace Preflight.Services.Implement +{ + public class ValueParserService : IValueParserService + { + private Func _parserDelegate; + private readonly IPluginExecutor _pluginExecutor; + + public ValueParserService(Func parserDelegate, IPluginExecutor pluginExecutor) + { + _parserDelegate = parserDelegate; + _pluginExecutor = pluginExecutor; + } + public List ParseBlockListContent(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave) + { + IPreflightValueParser parser = _parserDelegate(ParserType.BlockList); + return parser.Parse(propertyName, propertyValue, culture, nodeId, fromSave); + } + + public List ParseGridContent(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave) + { + IPreflightValueParser parser = _parserDelegate(ParserType.Grid); + return parser.Parse(propertyName, propertyValue, culture, nodeId, fromSave); + } + + public List ParseNestedContent(string propertyName, string propertyValue, string culture, int nodeId, bool fromSave) + { + IPreflightValueParser parser = _parserDelegate(ParserType.NestedContent); + return parser.Parse(propertyName, propertyValue, culture, nodeId, fromSave); + } + + public PreflightPropertyResponseModel ParseStringContent(string propertyName, string propertyValue, string culture, string propertyAlias, int nodeId, bool fromSave) + { + return _pluginExecutor.Execute(propertyName, culture, propertyValue, propertyAlias, nodeId, fromSave); + } + } +} diff --git a/src/Preflight/Settings/CoreSettings.cs b/src/Preflight/Settings/CoreSettings.cs new file mode 100644 index 0000000..8367ba9 --- /dev/null +++ b/src/Preflight/Settings/CoreSettings.cs @@ -0,0 +1,59 @@ +using Preflight.Models; +using System; +using System.Linq; + +namespace Preflight.Settings +{ + public class CoreSettings + { + public SettingsModel[] Settings + { + get + { + return new SettingsModel[] { + new GenericSettingModel("Run Preflight on save") + { + Description = "Set to true and Preflight will run on all saves, and alert users to any errors.", + View = SettingType.Boolean, + Tab = SettingsTabNames.General, + Value = 0, + Order = 0, + Core = true, + Guid = new Guid(KnownSettings.BindSaveHandler), + }, + new GenericSettingModel("Cancel save when Preflight tests fail") + { + Description = "Set to true and Preflight will cancel the save event, if tests fail and Preflight is set to run on save.", + View = SettingType.Boolean, + Tab = SettingsTabNames.General, + Value = 0, + Order = 1, + Core = true, + Guid = new Guid(KnownSettings.CancelSaveOnFail), + }, + new GenericSettingModel("Properties to test") + { + Description = "Restrict Preflight to a subset of testable properties.", + View = SettingType.CheckboxList, + Tab = SettingsTabNames.General, + Value = 0, + Prevalues = KnownPropertyAlias.All.Select(x => new { value = x, key = x }), + Order = 2, + Core = true, + Guid = new Guid(KnownSettings.PropertiesToTest), + }, + new GenericSettingModel("User group opt in/out") + { + Description = "Select user groups to opt in to testing.", + View = SettingType.CheckboxList, + Tab = SettingsTabNames.General, + Value = "Administrators,Editors", + Order = 3, + Core = true, + Guid = new Guid(KnownSettings.UserGroupOptIn), + }, + }; + } + } + } +} diff --git a/src/Preflight/Settings/NaughtyNiceSettings.cs b/src/Preflight/Settings/NaughtyNiceSettings.cs new file mode 100644 index 0000000..3c05e0f --- /dev/null +++ b/src/Preflight/Settings/NaughtyNiceSettings.cs @@ -0,0 +1,37 @@ +using Preflight.Models; +using System; + +namespace Preflight.Settings +{ + public class NaughtyNiceSettings + { + public SettingsModel[] Settings + { + get + { + return new SettingsModel[] { + new GenericSettingModel("Nice words") + { + Description = "These words will be excluded from the readability check.", + View = SettingType.MultipleTextbox, + Tab = SettingsTabNames.NaughtyAndNice, + Value = "Umbraco,preflight,hippopotamus", + Order = 0, + Core = true, + Guid = new Guid(KnownSettings.NiceList), + }, + new GenericSettingModel("Naughty words") + { + Description = "These words should never be used.", + View = SettingType.MultipleTextbox, + Tab = SettingsTabNames.NaughtyAndNice, + Value = "bannedword,never_use_this", + Order = 1, + Core = true, + Guid = new Guid(KnownSettings.NaughtyList), + }, + }; + } + } + } +}