From eff2e7a65bef1d0cea50c52e925ed30e6ec29196 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 19 Jan 2018 19:08:12 +0100 Subject: [PATCH] Manifest refactoring --- .../Configuration/Grid/GridEditorsConfig.cs | 17 +- .../Configuration/UmbracoConfig.cs | 5 +- src/Umbraco.Core/IO/IOHelper.cs | 6 + .../Manifest/GridEditorConverter.cs | 35 -- src/Umbraco.Core/Manifest/ManifestBuilder.cs | 105 ---- src/Umbraco.Core/Manifest/ManifestParser.cs | 397 +++--------- .../Manifest/ManifestValidatorConverter.cs | 6 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 38 +- .../Manifest/ParameterEditorConverter.cs | 28 +- .../Manifest/PreValueFieldConverter.cs | 18 - .../Manifest/PropertyEditorConverter.cs | 95 ++- .../PropertyEditors/GridEditor.cs | 18 +- .../PropertyEditors/ParameterEditor.cs | 63 +- .../ParameterEditorCollectionBuilder.cs | 13 +- .../PropertyEditors/ParameterValueEditor.cs | 12 +- .../PropertyEditors/PreValueEditor.cs | 2 +- .../PropertyEditors/PreValueField.cs | 9 +- .../PropertyEditors/PropertyEditor.cs | 90 ++- .../PropertyEditorCollectionBuilder.cs | 14 +- .../PropertyEditors/PropertyValueEditor.cs | 45 +- .../Runtime/CoreRuntimeComponent.cs | 4 +- ...ationConverter.cs => JsonReadConverter.cs} | 94 +-- src/Umbraco.Core/Umbraco.Core.csproj | 5 +- .../Manifest/ManifestParserTests.cs | 577 ++++++------------ .../Models/Mapping/AutoMapperTests.cs | 5 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 5 +- .../JsInitializationTests.cs | 2 +- .../Editors/BackOfficeController.cs | 40 +- .../UI/JavaScript/AssetInitialization.cs | 94 +-- .../UI/JavaScript/CssInitialization.cs | 40 +- .../UI/JavaScript/JsInitialization.cs | 96 ++- 31 files changed, 660 insertions(+), 1318 deletions(-) delete mode 100644 src/Umbraco.Core/Manifest/GridEditorConverter.cs delete mode 100644 src/Umbraco.Core/Manifest/ManifestBuilder.cs delete mode 100644 src/Umbraco.Core/Manifest/PreValueFieldConverter.cs rename src/Umbraco.Core/Serialization/{JsonCreationConverter.cs => JsonReadConverter.cs} (81%) diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index aec50725e3..f6478c3dbb 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -32,7 +32,8 @@ namespace Umbraco.Core.Configuration.Grid { Func> getResult = () => { - var parser = new ManifestParser(_logger, _appPlugins, _runtimeCache); + // fixme - should use the common one somehow! + ignoring _appPlugins here! + var parser = new ManifestParser(_runtimeCache, _logger); var editors = new List(); var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js"); @@ -40,10 +41,7 @@ namespace Umbraco.Core.Configuration.Grid { try { - var arr = JArray.Parse(File.ReadAllText(gridConfig)); - //ensure the contents parse correctly to objects - var parsed = parser.GetGridEditors(arr); - editors.AddRange(parsed); + editors.AddRange(parser.ParseGridEditors(File.ReadAllText(gridConfig))); } catch (Exception ex) { @@ -51,16 +49,13 @@ namespace Umbraco.Core.Configuration.Grid } } - - var builder = new ManifestBuilder(_runtimeCache, parser); - foreach (var gridEditor in builder.GridEditors) + // add manifest editors, skip duplicates + foreach (var gridEditor in parser.Manifest.GridEditors) { - //no duplicates! (based on alias) if (editors.Contains(gridEditor) == false) - { editors.Add(gridEditor); - } } + return editors; }; diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index c549350616..6daa9f6f6f 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -20,10 +20,7 @@ namespace Umbraco.Core.Configuration private static readonly Lazy Lazy = new Lazy(() => new UmbracoConfig()); - public static UmbracoConfig For - { - get { return Lazy.Value; } - } + public static UmbracoConfig For => Lazy.Value; #endregion diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 2175ac4e89..2e5f758107 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -44,6 +44,12 @@ namespace Umbraco.Core.IO return retval; } + public static string ResolveVirtualUrl(string path) + { + if (string.IsNullOrWhiteSpace(path)) return path; + return path.StartsWith("~/") ? ResolveUrl(path) : path; + } + //Replaces tildes with the root dir public static string ResolveUrl(string virtualPath) { diff --git a/src/Umbraco.Core/Manifest/GridEditorConverter.cs b/src/Umbraco.Core/Manifest/GridEditorConverter.cs deleted file mode 100644 index 4c1c21076e..0000000000 --- a/src/Umbraco.Core/Manifest/GridEditorConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Ensures that virtual paths are taken care of - /// - internal class GridEditorConverter : JsonCreationConverter - { - protected override GridEditor Create(Type objectType, JObject jObject) - { - return new GridEditor(); - } - - protected override void Deserialize(JObject jObject, GridEditor target, JsonSerializer serializer) - { - base.Deserialize(jObject, target, serializer); - - if (target.View.IsNullOrWhiteSpace() == false && target.View.StartsWith("~/")) - { - target.View = IOHelper.ResolveUrl(target.View); - } - - if (target.Render.IsNullOrWhiteSpace() == false && target.Render.StartsWith("~/")) - { - target.Render = IOHelper.ResolveUrl(target.Render); - } - } - } -} diff --git a/src/Umbraco.Core/Manifest/ManifestBuilder.cs b/src/Umbraco.Core/Manifest/ManifestBuilder.cs deleted file mode 100644 index bebde823e8..0000000000 --- a/src/Umbraco.Core/Manifest/ManifestBuilder.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using Umbraco.Core.Cache; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Manifest -{ - /// - /// This reads in the manifests and stores some definitions in memory so we can look them on the server side - /// - internal class ManifestBuilder - { - private readonly IRuntimeCacheProvider _cache; - private readonly ManifestParser _parser; - - public ManifestBuilder(IRuntimeCacheProvider cache, ManifestParser parser) - { - _cache = cache; - _parser = parser; - } - - private const string GridEditorsKey = "grideditors"; - private const string PropertyEditorsKey = "propertyeditors"; - private const string ParameterEditorsKey = "parametereditors"; - - /// - /// Returns all grid editors found in the manfifests - /// - internal IEnumerable GridEditors - { - get - { - return _cache.GetCacheItem>( - typeof (ManifestBuilder) + GridEditorsKey, - () => - { - var editors = new List(); - foreach (var manifest in _parser.GetManifests()) - { - if (manifest.GridEditors != null) - { - editors.AddRange(_parser.GetGridEditors(manifest.GridEditors)); - } - - } - return editors; - }, new TimeSpan(0, 10, 0)); - } - } - - /// - /// Returns all property editors found in the manfifests - /// - internal IEnumerable PropertyEditors - { - get - { - return _cache.GetCacheItem>( - typeof(ManifestBuilder) + PropertyEditorsKey, - () => - { - var editors = new List(); - foreach (var manifest in _parser.GetManifests()) - { - if (manifest.PropertyEditors != null) - { - editors.AddRange(_parser.GetPropertyEditors(manifest.PropertyEditors)); - } - - } - return editors; - }, new TimeSpan(0, 10, 0)); - } - } - - /// - /// Returns all parameter editors found in the manfifests and all property editors that are flagged to be parameter editors - /// - internal IEnumerable ParameterEditors - { - get - { - return _cache.GetCacheItem>( - typeof (ManifestBuilder) + ParameterEditorsKey, - () => - { - var editors = new List(); - foreach (var manifest in _parser.GetManifests()) - { - if (manifest.ParameterEditors != null) - { - editors.AddRange(_parser.GetParameterEditors(manifest.ParameterEditors)); - } - } - return editors; - }, new TimeSpan(0, 10, 0)); - } - } - - } -} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 506ff97dd4..38cfb1fce2 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -3,10 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -16,345 +15,139 @@ namespace Umbraco.Core.Manifest /// /// Parses the Main.js file and replaces all tokens accordingly. /// - internal class ManifestParser + public class ManifestParser { + private readonly IRuntimeCacheProvider _cache; + private readonly string _path; private readonly ILogger _logger; - private readonly DirectoryInfo _pluginsDir; - private readonly IRuntimeCacheProvider _cache; + private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - //used to strip comments - private static readonly Regex CommentsSurround = new Regex(@"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/", RegexOptions.Compiled); - private static readonly Regex CommentsLine = new Regex(@"^\s*//.*?$", RegexOptions.Compiled | RegexOptions.Multiline); + /// + /// Initializes a new instance of the class. + /// + public ManifestParser(IRuntimeCacheProvider cache, ILogger logger) // fixme is LightInject going to pick that one? + : this(cache, "~/App_Plugins", logger) + { } - public ManifestParser(ILogger logger, DirectoryInfo pluginsDir, IRuntimeCacheProvider cache) + /// + /// Initializes a new instance of the class. + /// + public ManifestParser(IRuntimeCacheProvider cache, string path, ILogger logger) { - if (logger == null) throw new ArgumentNullException("logger"); - if (pluginsDir == null) throw new ArgumentNullException("pluginsDir"); - _pluginsDir = pluginsDir; - _logger = logger; - _cache = cache; + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); + _path = path.StartsWith("~/") ? IOHelper.MapPath(path) : path; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// - /// Parse the grid editors from the json array - /// - /// - /// - internal IEnumerable GetGridEditors(JArray jsonEditors) - { - return JsonConvert.DeserializeObject>( - jsonEditors.ToString(), - new GridEditorConverter()); - } - - /// - /// Parse the property editors from the json array - /// - /// - /// - internal IEnumerable GetPropertyEditors(JArray jsonEditors) - { - return JsonConvert.DeserializeObject>( - jsonEditors.ToString(), - new PropertyEditorConverter(_logger), - new PreValueFieldConverter()); - } - - /// - /// Parse the property editors from the json array - /// - /// - /// - internal IEnumerable GetParameterEditors(JArray jsonEditors) - { - return JsonConvert.DeserializeObject>( - jsonEditors.ToString(), - new ParameterEditorConverter()); - } - - /// - /// Get all registered manifests + /// Gets all manifests, merged into a single manifest object. /// /// - /// - /// This ensures that we only build and look for all manifests once per Web app (based on the IRuntimeCache) - /// - public IEnumerable GetManifests() - { - return _cache.GetCacheItem>(typeof (ManifestParser) + "GetManifests", () => + public PackageManifest Manifest + => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => { - //get all Manifest.js files in the appropriate folders - var manifestFileContents = GetAllManifestFileContents(_pluginsDir); - return CreateManifests(manifestFileContents.ToArray()); - }, new TimeSpan(0, 10, 0)); - } + var manifests = GetManifests(); + return MergeManifests(manifests); + }); /// - /// Get the file contents from all declared manifest files + /// Gets all manifests. /// - /// - /// - private IEnumerable GetAllManifestFileContents(DirectoryInfo currDir) + private IEnumerable GetManifests() { - var depth = FolderDepth(_pluginsDir, currDir); + var manifests = new List(); - if (depth < 1) + foreach (var path in GetManifestFiles()) { - var result = new List(); - if (currDir.Exists) - { - var dirs = currDir.GetDirectories(); - - foreach (var d in dirs) - { - result.AddRange(GetAllManifestFileContents(d)); - } - } - return result; - } - - //look for files here - return currDir.GetFiles("Package.manifest") - .Select(f => File.ReadAllText(f.FullName)) - .ToList(); - } - - /// - /// Get the folder depth compared to the base folder - /// - /// - /// - /// - internal int FolderDepth(DirectoryInfo baseDir, DirectoryInfo currDir) - { - var removed = currDir.FullName.Remove(0, baseDir.FullName.Length).TrimStart('\\').TrimEnd('\\'); - return removed.Split(new char[] {'\\'}, StringSplitOptions.RemoveEmptyEntries).Length; - } - - /// - /// Creates a list of PropertyEditorManifest from the file contents of each manifest file - /// - /// - /// - /// - /// This ensures that comments are removed (but they have to be /* */ style comments - /// and ensures that virtual paths are replaced with real ones - /// - internal IEnumerable CreateManifests(params string[] manifestFileContents) - { - var result = new List(); - foreach (var m in manifestFileContents) - { - var manifestContent = m; - - if (manifestContent.IsNullOrWhiteSpace()) continue; - - // Strip byte object marker, JSON.NET does not like it - var preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - - // Strangely StartsWith(preamble) would always return true - if (manifestContent.Substring(0, 1) == preamble) - manifestContent = manifestContent.Remove(0, preamble.Length); - - if (manifestContent.IsNullOrWhiteSpace()) continue; - - //remove any comments first - var replaced = CommentsSurround.Replace(manifestContent, match => " "); - replaced = CommentsLine.Replace(replaced, match => ""); - - JObject deserialized; try { - deserialized = JsonConvert.DeserializeObject(replaced); + var text = File.ReadAllText(path); + text = TrimPreamble(text); + if (string.IsNullOrWhiteSpace(text)) + continue; + var manifest = ParseManifest(text); + manifests.Add(manifest); } - catch (Exception ex) + catch (Exception e) { - _logger.Error("An error occurred parsing manifest with contents: " + m, ex); - continue; + _logger.Error($"Failed to parse manifest at \"{path}\", ignoring.", e); } - - //validate the javascript - var init = deserialized.Properties().Where(x => x.Name == "javascript").ToArray(); - if (init.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'javascript' element"); - } - - //validate the css - var cssinit = deserialized.Properties().Where(x => x.Name == "css").ToArray(); - if (cssinit.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'css' element"); - } - - //validate the property editors section - var propEditors = deserialized.Properties().Where(x => x.Name == "propertyEditors").ToArray(); - if (propEditors.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'propertyEditors' element"); - } - - //validate the parameterEditors section - var paramEditors = deserialized.Properties().Where(x => x.Name == "parameterEditors").ToArray(); - if (paramEditors.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'parameterEditors' element"); - } - - //validate the gridEditors section - var gridEditors = deserialized.Properties().Where(x => x.Name == "gridEditors").ToArray(); - if (gridEditors.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'gridEditors' element"); - } - - var jConfig = init.Any() ? (JArray)deserialized["javascript"] : new JArray(); - ReplaceVirtualPaths(jConfig); - - var cssConfig = cssinit.Any() ? (JArray)deserialized["css"] : new JArray(); - ReplaceVirtualPaths(cssConfig); - - //replace virtual paths for each property editor - if (deserialized["propertyEditors"] != null) - { - foreach (JObject p in deserialized["propertyEditors"]) - { - if (p["editor"] != null) - { - ReplaceVirtualPaths((JObject) p["editor"]); - } - if (p["preValues"] != null) - { - ReplaceVirtualPaths((JObject)p["preValues"]); - } - } - } - - //replace virtual paths for each property editor - if (deserialized["gridEditors"] != null) - { - foreach (JObject p in deserialized["gridEditors"]) - { - if (p["view"] != null) - { - ReplaceVirtualPaths(p["view"]); - } - if (p["render"] != null) - { - ReplaceVirtualPaths(p["render"]); - } - } - } - - var manifest = new PackageManifest() - { - JavaScriptInitialize = jConfig, - StylesheetInitialize = cssConfig, - PropertyEditors = propEditors.Any() ? (JArray)deserialized["propertyEditors"] : new JArray(), - ParameterEditors = paramEditors.Any() ? (JArray)deserialized["parameterEditors"] : new JArray(), - GridEditors = gridEditors.Any() ? (JArray)deserialized["gridEditors"] : new JArray() - }; - result.Add(manifest); } - return result; + + return manifests; } /// - /// Replaces any virtual paths found in properties + /// Merges all manifests into one. /// - /// - private void ReplaceVirtualPaths(JArray jarr) + private static PackageManifest MergeManifests(IEnumerable manifests) { - foreach (var i in jarr) + var scripts = new HashSet(); + var stylesheets = new HashSet(); + var propertyEditors = new List(); + var parameterEditors = new List(); + var gridEditors = new List(); + + foreach (var manifest in manifests) { - ReplaceVirtualPaths(i); + if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); + if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); + if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); + if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); + if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); } + + return new PackageManifest + { + Scripts = scripts.ToArray(), + Stylesheets = stylesheets.ToArray(), + PropertyEditors = propertyEditors.ToArray(), + ParameterEditors = parameterEditors.ToArray(), + GridEditors = gridEditors.ToArray() + }; + } + + // gets all manifest files (recursively) + private IEnumerable GetManifestFiles() + => Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); + + private static string TrimPreamble(string text) + { + // strangely StartsWith(preamble) would always return true + if (text.Substring(0, 1) == Utf8Preamble) + text = text.Remove(0, Utf8Preamble.Length); + + return text; } /// - /// Replaces any virtual paths found in properties + /// Parses a manifest. /// - /// - private void ReplaceVirtualPaths(JToken jToken) + internal PackageManifest ParseManifest(string text) { - if (jToken.Type == JTokenType.Object) - { - //recurse - ReplaceVirtualPaths((JObject)jToken); - } - else - { - var value = jToken as JValue; - if (value != null) - { - if (value.Type == JTokenType.String) - { - if (value.Value().StartsWith("~/")) - { - //replace the virtual path - value.Value = IOHelper.ResolveUrl(value.Value()); - } - } - } - } + if (string.IsNullOrWhiteSpace(text)) + throw new ArgumentNullOrEmptyException(nameof(text)); + + var manifest = JsonConvert.DeserializeObject(text, + new PropertyEditorConverter(_logger), + new ParameterEditorConverter(), + new ManifestValidatorConverter()); + + // scripts and stylesheets are raw string, must process here + for (var i = 0; i < manifest.Scripts.Length; i++) + manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); + for (var i = 0; i < manifest.Stylesheets.Length; i++) + manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + + return manifest; } - /// - /// Replaces any virtual paths found in properties - /// - /// - private void ReplaceVirtualPaths(JObject jObj) + // purely for tests + internal IEnumerable ParseGridEditors(string text) { - foreach (var p in jObj.Properties().Select(x => x.Value)) - { - ReplaceVirtualPaths(p); - } + return JsonConvert.DeserializeObject>(text); } - - /// - /// Merges two json objects together - /// - /// - /// - /// set to true if we will keep the receiver value if the proeprty already exists - /// - /// taken from - /// http://stackoverflow.com/questions/4002508/does-c-sharp-have-a-library-for-parsing-multi-level-cascading-json/4002550#4002550 - /// - internal static void MergeJObjects(JObject receiver, JObject donor, bool keepOriginal = false) - { - foreach (var property in donor) - { - var receiverValue = receiver[property.Key] as JObject; - var donorValue = property.Value as JObject; - if (receiverValue != null && donorValue != null) - { - MergeJObjects(receiverValue, donorValue); - } - else if (receiver[property.Key] == null || !keepOriginal) - { - receiver[property.Key] = property.Value; - } - } - } - - /// - /// Merges the donor array values into the receiver array - /// - /// - /// - internal static void MergeJArrays(JArray receiver, JArray donor) - { - foreach (var item in donor) - { - if (!receiver.Any(x => x.Equals(item))) - { - receiver.Add(item); - } - } - } - - } } diff --git a/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs b/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs index e2ad9c6694..3c93797d30 100644 --- a/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs +++ b/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs @@ -6,13 +6,13 @@ using Umbraco.Core.Serialization; namespace Umbraco.Core.Manifest { /// - /// Used when deserialing the validation collection, any serialized property editors are from a manifest and thus the - /// validators are manifest validators. + /// Implements a json read converter for . /// - internal class ManifestValidatorConverter : JsonCreationConverter + internal class ManifestValidatorConverter : JsonReadConverter { protected override IPropertyValidator Create(Type objectType, JObject jObject) { + // all validators coming from manifests are ManifestPropertyValidator instances return new ManifestPropertyValidator(); } } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 0167cf1d5c..86028d91dd 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,35 +1,27 @@ -using Newtonsoft.Json.Linq; +using System; +using Newtonsoft.Json; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Manifest { /// - /// Represents a manifest file for packages + /// Represents the content of a package manifest. /// - internal class PackageManifest + public class PackageManifest { - /// - /// The json array used to initialize the application with the JS dependencies required - /// - public JArray JavaScriptInitialize { get; set; } + [JsonProperty("javascript")] + public string[] Scripts { get; set; } = Array.Empty(); - /// - /// The json array used to initialize the application with the CSS dependencies required - /// - public JArray StylesheetInitialize { get; set; } + [JsonProperty("css")] + public string[] Stylesheets { get; set; }= Array.Empty(); - /// - /// The json array of property editors - /// - public JArray PropertyEditors { get; set; } + [JsonProperty("propertyEditors")] + public PropertyEditor[] PropertyEditors { get; set; } = Array.Empty(); - /// - /// The json array of parameter editors - /// - public JArray ParameterEditors { get; set; } + [JsonProperty("parameterEditors")] + public ParameterEditor[] ParameterEditors { get; set; } = Array.Empty(); - /// - /// The json array of grid editors - /// - public JArray GridEditors { get; set; } + [JsonProperty("gridEditors")] + public GridEditor[] GridEditors { get; set; } = Array.Empty(); } } diff --git a/src/Umbraco.Core/Manifest/ParameterEditorConverter.cs b/src/Umbraco.Core/Manifest/ParameterEditorConverter.cs index a69e45b28c..3f2055129b 100644 --- a/src/Umbraco.Core/Manifest/ParameterEditorConverter.cs +++ b/src/Umbraco.Core/Manifest/ParameterEditorConverter.cs @@ -7,25 +7,33 @@ using Umbraco.Core.Serialization; namespace Umbraco.Core.Manifest { /// - /// Used to convert a parameter editor manifest to a property editor object + /// Implements a json read converter for . /// - internal class ParameterEditorConverter : JsonCreationConverter + internal class ParameterEditorConverter : JsonReadConverter { + /// protected override ParameterEditor Create(Type objectType, JObject jObject) { return new ParameterEditor(); + } - - protected override void Deserialize(JObject jObject, ParameterEditor target, JsonSerializer serializer) + /// + protected override void Deserialize(JObject jobject, ParameterEditor target, JsonSerializer serializer) { - //since it's a manifest editor, we need to create it's instance. - //we need to specify the view value for the editor here otherwise we'll get an exception. - target.ManifestDefinedParameterValueEditor = new ParameterValueEditor + if (jobject.Property("view") != null) { - View = jObject["view"].ToString() - }; + // the deserializer will first try to get the property, and that would throw since + // the editor would try to create a new value editor, so we have to set a + // value editor by ourselves, which will then be populated by the deserializer. + target.ValueEditor = new ParameterValueEditor(); - base.Deserialize(jObject, target, serializer); + // the 'view' property in the manifest is at top-level, and needs to be moved + // down one level to the actual value editor. + jobject["editor"] = new JObject { ["view"] = jobject["view"] }; + jobject.Property("view").Remove(); + } + + base.Deserialize(jobject, target, serializer); } } } diff --git a/src/Umbraco.Core/Manifest/PreValueFieldConverter.cs b/src/Umbraco.Core/Manifest/PreValueFieldConverter.cs deleted file mode 100644 index 2e77a74d48..0000000000 --- a/src/Umbraco.Core/Manifest/PreValueFieldConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Used to convert a pre-value field manifest to a real pre value field - /// - internal class PreValueFieldConverter : JsonCreationConverter - { - protected override PreValueField Create(Type objectType, JObject jObject) - { - return new PreValueField(); - } - } -} diff --git a/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs b/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs index a4a542f05b..7ca14925c8 100644 --- a/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs +++ b/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; @@ -10,89 +8,74 @@ using Umbraco.Core.Serialization; namespace Umbraco.Core.Manifest { /// - /// Used to convert a property editor manifest to a property editor object + /// Implements a json read converter for . /// - internal class PropertyEditorConverter : JsonCreationConverter + internal class PropertyEditorConverter : JsonReadConverter { private readonly ILogger _logger; + /// + /// Initializes a new instance of the class. + /// public PropertyEditorConverter(ILogger logger) { _logger = logger; } + /// protected override PropertyEditor Create(Type objectType, JObject jObject) { return new PropertyEditor(_logger); } - protected override void Deserialize(JObject jObject, PropertyEditor target, JsonSerializer serializer) + /// + protected override void Deserialize(JObject jobject, PropertyEditor target, JsonSerializer serializer) { - if (jObject["editor"] != null) + if (jobject["editor"] != null) { - //since it's a manifest editor, we need to create it's instance. - //we need to specify the view value for the editor here otherwise we'll get an exception. - target.ManifestDefinedPropertyValueEditor = new PropertyValueEditor - { - View = jObject["editor"]["view"].ToString() - }; - - //the manifest JSON is a simplified json for the validators which is actually a dictionary, however, the - //c# model requires an array of validators not a dictionary so we need to change the json to an array - //to deserialize properly. - JArray converted; - if (TryConvertValidatorDictionaryToArray(jObject["editor"]["validation"] as JObject, out converted)) - { - jObject["editor"]["validation"] = converted; - } + // the deserializer will first try to get the property, and that would throw since + // the editor would try to create a new value editor, so we have to set a + // value editor by ourselves, which will then be populated by the deserializer. + target.ValueEditor = new PropertyValueEditor(); + // in the manifest, validators are a simple dictionary eg + // { + // required: true, + // regex: '\\d*' + // } + // and we need to turn this into a list of IPropertyValidator + // so, rewrite the json structure accordingly + if (jobject["editor"]["validation"] is JObject validation) + jobject["editor"]["validation"] = RewriteValidators(validation); } - if (jObject["prevalues"] != null) - { - target.ManifestDefinedPreValueEditor = new PreValueEditor(); - //the manifest JSON is a simplified json for the validators which is actually a dictionary, however, the - //c# model requires an array of validators not a dictionary so we need to change the json to an array - //to deserialize properly. - var fields = jObject["prevalues"]["fields"] as JArray; - if (fields != null) + // see note about validators, above - same applies to field validators + if (jobject["prevalues"]?["fields"] is JArray jarray) + { + foreach (var field in jarray) { - foreach (var f in fields) - { - JArray converted; - if (TryConvertValidatorDictionaryToArray(f["validation"] as JObject, out converted)) - { - f["validation"] = converted; - } - } + // see note above, for editor + if (field["validation"] is JObject validation) + field["validation"] = RewriteValidators(validation); } } - base.Deserialize(jObject, target, serializer); + base.Deserialize(jobject, target, serializer); } - private bool TryConvertValidatorDictionaryToArray(JObject validation, out JArray result) + private static JArray RewriteValidators(JObject validation) { - if (validation == null) + var jarray = new JArray(); + + foreach (var v in validation) { - result = null; - return false; + var key = v.Key; + var val = v.Value?.Type == JTokenType.Boolean ? string.Empty : v.Value; + var jo = new JObject { { "type", key }, { "config", val } }; + jarray.Add(jo); } - result = new JArray(); - foreach (var entry in validation) - { - //in a special case if the value is simply 'true' (boolean) this just indicates that the - // validator is enabled, the config should just be empty. - var formattedItem = JObject.FromObject(new { type = entry.Key, config = entry.Value }); - if (entry.Value.Type == JTokenType.Boolean) - { - formattedItem["config"] = ""; - } - - result.Add(formattedItem); - } - return true; + return jarray; } } } diff --git a/src/Umbraco.Core/PropertyEditors/GridEditor.cs b/src/Umbraco.Core/PropertyEditors/GridEditor.cs index d021237c2a..986eed9ccc 100644 --- a/src/Umbraco.Core/PropertyEditors/GridEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/GridEditor.cs @@ -1,11 +1,15 @@ using System.Collections.Generic; using Newtonsoft.Json; using Umbraco.Core.Configuration.Grid; +using Umbraco.Core.IO; namespace Umbraco.Core.PropertyEditors { - internal class GridEditor : IGridEditorConfig + public class GridEditor : IGridEditorConfig { + private string _view; + private string _render; + public GridEditor() { Config = new Dictionary(); @@ -18,10 +22,18 @@ namespace Umbraco.Core.PropertyEditors public string Alias { get; set; } [JsonProperty("view", Required = Required.Always)] - public string View { get; set; } + public string View + { + get => _view; + set => _view = IOHelper.ResolveVirtualUrl(value); + } [JsonProperty("render")] - public string Render { get; set; } + public string Render + { + get => _render; + set => _render = IOHelper.ResolveVirtualUrl(value); + } [JsonProperty("icon", Required = Required.Always)] public string Icon { get; set; } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs index a410c60eda..a1d738d973 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; -using Umbraco.Core.IO; namespace Umbraco.Core.PropertyEditors { @@ -10,30 +9,25 @@ namespace Umbraco.Core.PropertyEditors /// public class ParameterEditor : IParameterEditor { - private readonly ParameterEditorAttribute _attribute; + private ParameterValueEditor _valueEditor; + private ParameterValueEditor _valueEditorAssigned; + /// /// The constructor will setup the property editor based on the attribute if one is found /// public ParameterEditor() { Configuration = new Dictionary(); - //assign properties based on the attribute if it is found - _attribute = GetType().GetCustomAttribute(false); - if (_attribute != null) - { - //set the id/name from the attribute - Alias = _attribute.Alias; - Name = _attribute.Name; - } - } - /// - /// These are assigned by default normally based on parameter editor attributes or manifest definitions, - /// developers have the chance to override CreateValueEditor if they don't want to use the pre-defined instance - /// - internal ParameterValueEditor ManifestDefinedParameterValueEditor = null; + // assign properties based on the attribute, if it is found + _attribute = GetType().GetCustomAttribute(false); + if (_attribute == null) return; + + Alias = _attribute.Alias; + Name = _attribute.Name; + } /// /// The id of the property editor @@ -53,17 +47,19 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("config")] public IDictionary Configuration { get; set; } - [JsonIgnore] + [JsonProperty("editor")] public ParameterValueEditor ValueEditor { - get { return CreateValueEditor(); } + get => _valueEditor ?? (_valueEditor = CreateValueEditor()); + set + { + _valueEditorAssigned = value; + _valueEditor = null; + } } [JsonIgnore] - IValueEditor IParameterEditor.ValueEditor - { - get { return ValueEditor; } - } + IValueEditor IParameterEditor.ValueEditor => ValueEditor; // fixme - because we must, but - bah /// /// Creates a value editor instance @@ -71,25 +67,18 @@ namespace Umbraco.Core.PropertyEditors /// protected virtual ParameterValueEditor CreateValueEditor() { - if (ManifestDefinedParameterValueEditor != null) - { - //detect if the view is a virtual path (in most cases, yes) then convert it - if (ManifestDefinedParameterValueEditor.View.StartsWith("~/")) - { - ManifestDefinedParameterValueEditor.View = IOHelper.ResolveUrl(ManifestDefinedParameterValueEditor.View); - } - return ManifestDefinedParameterValueEditor; - } + // handle assigned editor + if (_valueEditorAssigned != null) + return _valueEditorAssigned; - //create a new editor + // create a new editor var editor = new ParameterValueEditor(); - if (_attribute.EditorView.IsNullOrWhiteSpace()) - { - throw new NotImplementedException("This method must be implemented if a view is not explicitly set"); - } + var view = _attribute?.EditorView; + if (string.IsNullOrWhiteSpace(view)) + throw new InvalidOperationException("The editor does not specify a view."); + editor.View = view; - editor.View = _attribute.EditorView; return editor; } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollectionBuilder.cs index 015fc19af6..824d177541 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollectionBuilder.cs @@ -6,14 +6,14 @@ using Umbraco.Core.Manifest; namespace Umbraco.Core.PropertyEditors { - internal class ParameterEditorCollectionBuilder : LazyCollectionBuilderBase + public class ParameterEditorCollectionBuilder : LazyCollectionBuilderBase { - private readonly ManifestBuilder _manifestBuilder; + private readonly ManifestParser _manifestParser; - public ParameterEditorCollectionBuilder(IServiceContainer container, ManifestBuilder manifestBuilder) + public ParameterEditorCollectionBuilder(IServiceContainer container, ManifestParser manifestParser) : base(container) { - _manifestBuilder = manifestBuilder; + _manifestParser = manifestParser; } protected override ParameterEditorCollectionBuilder This => this; @@ -33,8 +33,9 @@ namespace Umbraco.Core.PropertyEditors return base.CreateItems(args) .Where(x => (x is PropertyEditor) == false || ((PropertyEditor) x).IsParameterEditor) - .Union(_manifestBuilder.ParameterEditors) - .Union(_manifestBuilder.PropertyEditors.Where(x => x.IsParameterEditor)); + .Union(_manifestParser.Manifest.ParameterEditors) + .Union(_manifestParser.Manifest.PropertyEditors.Where(x => x.IsParameterEditor)) + .ToList(); } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterValueEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterValueEditor.cs index 5d8f693686..5f9066ee54 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterValueEditor.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.PropertyEditors +using Umbraco.Core.IO; + +namespace Umbraco.Core.PropertyEditors { // fixme - can we kill this and use "ValueEditor" for both macro and all? @@ -7,6 +9,8 @@ /// public class ParameterValueEditor : IValueEditor { + private string _view; + /// /// Initializes a new instance of the class. /// @@ -25,6 +29,10 @@ /// /// Gets or sets the editor view. /// - public string View { get; set; } + public string View + { + get => _view; + set => _view = IOHelper.ResolveVirtualUrl(value); + } } } diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs index cebeb8cb25..ceb9a82924 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs @@ -97,7 +97,7 @@ namespace Umbraco.Core.PropertyEditors /// If fields are specified then the master View and Validators will be ignored /// [JsonProperty("fields")] - public List Fields { get; private set; } + public List Fields { get; internal set; } /// /// A method to format the posted values from the editor to the values to be persisted diff --git a/src/Umbraco.Core/PropertyEditors/PreValueField.cs b/src/Umbraco.Core/PropertyEditors/PreValueField.cs index 71d84b450a..ea21677f52 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueField.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueField.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Newtonsoft.Json; +using Umbraco.Core.IO; using Umbraco.Core.Manifest; namespace Umbraco.Core.PropertyEditors @@ -9,6 +10,8 @@ namespace Umbraco.Core.PropertyEditors /// public class PreValueField { + private string _view; + /// /// Standard constructor /// @@ -73,7 +76,11 @@ namespace Umbraco.Core.PropertyEditors /// * a simple view name which will map to the views/prevalueeditors/{view}.html /// [JsonProperty("view", Required = Required.Always)] - public string View { get; set; } + public string View + { + get => _view; + set => _view = IOHelper.ResolveVirtualUrl(value); + } /// /// A collection of validators for the pre value field diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index 8d1d02f14c..2ee0775750 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using Newtonsoft.Json; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -21,6 +20,11 @@ namespace Umbraco.Core.PropertyEditors { private readonly PropertyEditorAttribute _attribute; + private PropertyValueEditor _valueEditor; + private PropertyValueEditor _valueEditorAssigned; + private PreValueEditor _preValueEditor; + private PreValueEditor _preValueEditorAssigned; + /// /// Initializes a new instance of the class. /// @@ -49,23 +53,11 @@ namespace Umbraco.Core.PropertyEditors /// protected ILogger Logger { get; } - /// - /// These are assigned by default normally based on property editor attributes or manifest definitions, - /// developers have the chance to override CreateValueEditor if they don't want to use the pre-defined instance - /// - internal PropertyValueEditor ManifestDefinedPropertyValueEditor = null; - - /// - /// These are assigned by default normally based on property editor attributes or manifest definitions, - /// developers have the chance to override CreatePreValueEditor if they don't want to use the pre-defined instance - /// - internal PreValueEditor ManifestDefinedPreValueEditor = null; - /// /// Gets or sets a value indicating whether this editor can be used as a parameter editor. /// [JsonProperty("isParameterEditor")] - public bool IsParameterEditor { get; internal set; } + public bool IsParameterEditor { get; internal set; } // fixme understand + explain /// /// Gets or sets the unique alias of the property editor. @@ -95,50 +87,61 @@ namespace Umbraco.Core.PropertyEditors /// Gets or sets a value indicating whether the property editor is deprecated. /// [JsonIgnore] - public bool IsDeprecated { get; internal set; } // fixme kill it all in v8 + public bool IsDeprecated { get; internal set; } // fixme - kill it all in v8 [JsonProperty("editor", Required = Required.Always)] - public PropertyValueEditor ValueEditor => CreateValueEditor(); + public PropertyValueEditor ValueEditor + { + get => _valueEditor ?? (_valueEditor = CreateValueEditor()); + set + { + _valueEditorAssigned = value; + _valueEditor = null; + } + } [JsonIgnore] - IValueEditor IParameterEditor.ValueEditor => ValueEditor; + IValueEditor IParameterEditor.ValueEditor => ValueEditor; // fixme - because we must, but - bah [JsonProperty("prevalues")] - public PreValueEditor PreValueEditor => CreatePreValueEditor(); + public PreValueEditor PreValueEditor + { + get => _preValueEditor ?? (_preValueEditor = CreatePreValueEditor()); + set + { + _preValueEditorAssigned = value; + _preValueEditor = null; + } + } [JsonProperty("defaultConfig")] public virtual IDictionary DefaultPreValues { get; set; } [JsonIgnore] - IDictionary IParameterEditor.Configuration => DefaultPreValues; + IDictionary IParameterEditor.Configuration => DefaultPreValues; // fixme - because we must, but - bah /// - /// Creates a value editor instance + /// Creates a value editor instance. /// protected virtual PropertyValueEditor CreateValueEditor() { - // handle manifest-defined editors - if (ManifestDefinedPropertyValueEditor != null) - { - // map view path if virtual - if (ManifestDefinedPropertyValueEditor.View.StartsWith("~/")) - ManifestDefinedPropertyValueEditor.View = IOHelper.ResolveUrl(ManifestDefinedPropertyValueEditor.View); - return ManifestDefinedPropertyValueEditor; - } + // handle assigned editor + if (_valueEditorAssigned != null) + return _valueEditorAssigned; // create a new editor var editor = new PropertyValueEditor(); + var view = _attribute?.EditorView; if (string.IsNullOrWhiteSpace(view)) throw new InvalidOperationException("The editor does not specify a view."); - if (view.StartsWith("~/")) view = IOHelper.ResolveUrl(view); editor.View = view; + editor.ValueType = _attribute.ValueType; editor.HideLabel = _attribute.HideLabel; return editor; - } /// @@ -146,17 +149,9 @@ namespace Umbraco.Core.PropertyEditors /// protected virtual PreValueEditor CreatePreValueEditor() { - // handle manifest-defined editors - if (ManifestDefinedPreValueEditor != null) - { - foreach (var field in ManifestDefinedPreValueEditor.Fields) - { - // map view path if virtual - if (field.View.StartsWith("~/")) - field.View = IOHelper.ResolveUrl(field.View); - } - return ManifestDefinedPreValueEditor; - } + // handle assigned editor + if (_preValueEditorAssigned != null) + return _preValueEditorAssigned; // else return an empty one return new PreValueEditor(); @@ -216,17 +211,4 @@ namespace Umbraco.Core.PropertyEditors return configuration; } } - - // fixme clear that one! breaking everything! - //public class PropertyEditor : PropertyEditor - //{ - // public PropertyEditor(ILogger logger) - // : base(logger) - // { } - - // public virtual TConfiguration MapConfiguration(string configuration) - // { - // return JsonConvert.DeserializeObject(configuration); - // } - //} } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollectionBuilder.cs index 587261ae1e..cd91fa7077 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollectionBuilder.cs @@ -8,19 +8,19 @@ namespace Umbraco.Core.PropertyEditors { public class PropertyEditorCollectionBuilder : LazyCollectionBuilderBase { - public PropertyEditorCollectionBuilder(IServiceContainer container) - : base(container) - { } + private readonly ManifestParser _manifestParser; - // have to property-inject that one as it is internal & the builder is public - [Inject] - internal ManifestBuilder ManifestBuilder { get; set; } + public PropertyEditorCollectionBuilder(IServiceContainer container, ManifestParser manifestParser) + : base(container) + { + _manifestParser = manifestParser; + } protected override PropertyEditorCollectionBuilder This => this; protected override IEnumerable CreateItems(params object[] args) { - return base.CreateItems(args).Union(ManifestBuilder.PropertyEditors); + return base.CreateItems(args).Union(_manifestParser.Manifest.PropertyEditors); } } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs index 5f71cdbf40..fda3d7d77f 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Xml.Linq; using Newtonsoft.Json; using Umbraco.Core.Composing; +using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Manifest; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.Services; @@ -18,6 +17,8 @@ namespace Umbraco.Core.PropertyEditors /// public class PropertyValueEditor : IValueEditor { + private string _view; + /// /// Initializes a new instance of the class. /// @@ -61,8 +62,7 @@ namespace Umbraco.Core.PropertyEditors /// public virtual void ConfigureForDisplay(PreValueCollection preValues) { - if (preValues == null) throw new ArgumentNullException("preValues"); - _preVals = preValues; + _preVals = preValues ?? throw new ArgumentNullException(nameof(preValues)); } /// @@ -73,7 +73,11 @@ namespace Umbraco.Core.PropertyEditors /// folder, or (3) a view name which maps to views/propertyeditors/{view}/{view}.html. /// [JsonProperty("view", Required = Required.Always)] - public string View { get; set; } + public string View + { + get => _view; + set => _view = IOHelper.ResolveVirtualUrl(value); + } /// /// The value type which reflects how it is validated and stored in the database @@ -84,9 +88,11 @@ namespace Umbraco.Core.PropertyEditors /// /// A collection of validators for the pre value editor /// - [JsonProperty("validation", ItemConverterType = typeof(ManifestValidatorConverter))] + [JsonProperty("validation")] public List Validators { get; private set; } + // fixme - need to explain and understand these two + what is "overridable pre-values" + /// /// Returns the validator used for the required field validation which is specified on the PropertyType /// @@ -96,10 +102,7 @@ namespace Umbraco.Core.PropertyEditors /// The default validator used is the RequiredValueValidator but this can be overridden by property editors /// if they need to do some custom validation, or if the value being validated is a json object. /// - public virtual ManifestValueValidator RequiredValidator - { - get { return new RequiredManifestValueValidator(); } - } + public virtual ManifestValueValidator RequiredValidator => new RequiredManifestValueValidator(); /// /// Returns the validator used for the regular expression field validation which is specified on the PropertyType @@ -110,10 +113,7 @@ namespace Umbraco.Core.PropertyEditors /// The default validator used is the RegexValueValidator but this can be overridden by property editors /// if they need to do some custom validation, or if the value being validated is a json object. /// - public virtual ManifestValueValidator RegexValidator - { - get { return new RegexValidator(); } - } + public virtual ManifestValueValidator RegexValidator => new RegexValidator(); /// /// Returns the true DataTypeDatabaseType from the string representation ValueType. @@ -144,7 +144,7 @@ namespace Umbraco.Core.PropertyEditors case PropertyEditorValueTypes.Time: return DataTypeDatabaseType.Date; default: - throw new ArgumentException("Not a valid value type.", "valueType"); + throw new ArgumentException("Not a valid value type.", nameof(valueType)); } } @@ -157,10 +157,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Set this to true if the property editor is for display purposes only /// - public virtual bool IsReadOnly - { - get { return false; } - } + public virtual bool IsReadOnly => false; /// /// Used to try to convert the string value to the correct CLR type based on the DatabaseDataType specified for this value editor @@ -170,14 +167,8 @@ namespace Umbraco.Core.PropertyEditors internal Attempt TryConvertValueToCrlType(object value) { //this is a custom check to avoid any errors, if it's a string and it's empty just make it null - var s = value as string; - if (s != null) - { - if (s.IsNullOrWhiteSpace()) - { - value = null; - } - } + if (value is string s && string.IsNullOrWhiteSpace(s)) + value = null; Type valueType; //convert the string to a known type diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs index 309edffd27..4fa204a581 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs @@ -55,9 +55,7 @@ namespace Umbraco.Core.Runtime composition.Container.RegisterSingleton(factory => factory.GetInstance().XsltFileSystem, Constants.Composing.FileSystems.XsltFileSystem); // register manifest builder, will be injected in eg PropertyEditorCollectionBuilder - composition.Container.RegisterSingleton(factory - => new ManifestParser(factory.GetInstance(), new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), factory.GetInstance())); - composition.Container.RegisterSingleton(); + composition.Container.RegisterSingleton(); composition.Container.RegisterCollectionBuilder() .Add(factory => factory.GetInstance().GetPropertyEditors()); diff --git a/src/Umbraco.Core/Serialization/JsonCreationConverter.cs b/src/Umbraco.Core/Serialization/JsonReadConverter.cs similarity index 81% rename from src/Umbraco.Core/Serialization/JsonCreationConverter.cs rename to src/Umbraco.Core/Serialization/JsonReadConverter.cs index 3639337b68..18e93d4a14 100644 --- a/src/Umbraco.Core/Serialization/JsonCreationConverter.cs +++ b/src/Umbraco.Core/Serialization/JsonReadConverter.cs @@ -1,47 +1,47 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Umbraco.Core.Serialization -{ - - internal abstract class JsonCreationConverter : JsonConverter - { - /// - /// Create an instance of objectType, based properties in the JSON object - /// - /// type of object expected - /// contents of JSON object that will be deserialized - /// - protected abstract T Create(Type objectType, JObject jObject); - - public override bool CanConvert(Type objectType) - { - return typeof(T).IsAssignableFrom(objectType); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - // Load JObject from stream - var jObject = JObject.Load(reader); - - // Create target object based on JObject - var target = Create(objectType, jObject); - - Deserialize(jObject, target, serializer); - - return target; - } - - protected virtual void Deserialize(JObject jObject, T target, JsonSerializer serializer) - { - // Populate the object properties - serializer.Populate(jObject.CreateReader(), target); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - } -} +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Core.Serialization +{ + + internal abstract class JsonReadConverter : JsonConverter + { + /// + /// Create an instance of objectType, based properties in the JSON object + /// + /// type of object expected + /// contents of JSON object that will be deserialized + /// + protected abstract T Create(Type objectType, JObject jObject); + + public override bool CanConvert(Type objectType) + { + return typeof(T).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // Load JObject from stream + var jObject = JObject.Load(reader); + + // Create target object based on JObject + var target = Create(objectType, jObject); + + Deserialize(jObject, target, serializer); + + return target; + } + + protected virtual void Deserialize(JObject jobject, T target, JsonSerializer serializer) + { + // Populate the object properties + serializer.Populate(jobject.CreateReader(), target); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index dcf0cf25ef..942facddfd 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -489,14 +489,11 @@ - - - @@ -1286,7 +1283,7 @@ - + diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index b25f7da6b0..88e7d9b595 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Linq; using Moq; using System.Text; @@ -7,11 +6,9 @@ using NUnit.Framework; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Manifest { @@ -24,20 +21,105 @@ namespace Umbraco.Tests.Manifest [SetUp] public void Setup() { - _parser = new ManifestParser(Mock.Of(), new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), NullCacheProvider.Instance); + _parser = new ManifestParser(NullCacheProvider.Instance, Mock.Of()); } [Test] - public void Parse_Property_Editors_With_Pre_Vals() + public void CanParseComments() { - var a = JsonConvert.DeserializeObject(@"[ + const string json1 = @" +// this is a single-line comment +{ + ""x"": 2, // this is an end-of-line comment + ""y"": 3, /* this is a single line comment block +/* comment */ ""z"": /* comment */ 4, + ""t"": ""this is /* comment */ a string"", + ""u"": ""this is // more comment in a string"" +} +"; + + var jobject = (JObject) JsonConvert.DeserializeObject(json1); + Assert.AreEqual("2", jobject.Property("x").Value.ToString()); + Assert.AreEqual("3", jobject.Property("y").Value.ToString()); + Assert.AreEqual("4", jobject.Property("z").Value.ToString()); + Assert.AreEqual("this is /* comment */ a string", jobject.Property("t").Value.ToString()); + Assert.AreEqual("this is // more comment in a string", jobject.Property("u").Value.ToString()); + } + + [Test] + public void ThrowOnJsonError() + { + // invalid json, missing the final ']' on javascript + const string json = @"{ +propertyEditors: []/*we have empty property editors**/, +javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2.js' }"; + + // parsing fails + Assert.Throws(() => _parser.ParseManifest(json)); + } + + [Test] + public void CanParseManifest_ScriptsAndStylesheets() + { + var json = "{}"; + var manifest = _parser.ParseManifest(json); + Assert.AreEqual(0, manifest.Scripts.Length); + + json = "{javascript: []}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(0, manifest.Scripts.Length); + + json = "{javascript: ['~/test.js', '~/test2.js']}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.Scripts.Length); + + json = "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.Scripts.Length); + + Assert.AreEqual("/test.js", manifest.Scripts[0]); + Assert.AreEqual("/test2.js", manifest.Scripts[1]); + + // kludge is gone - must filter before parsing + json = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()) + "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}"; + Assert.Throws(() => _parser.ParseManifest(json)); + + json = "{}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(0, manifest.Stylesheets.Length); + + json = "{css: []}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(0, manifest.Stylesheets.Length); + + json = "{css: ['~/style.css', '~/folder-name/sdsdsd/stylesheet.css']}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.Stylesheets.Length); + + json = "{propertyEditors: [], css: ['~/stylesheet.css', '~/random-long-name.css']}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.Stylesheets.Length); + + + + json = "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js'], css: ['~/stylesheet.css', '~/random-long-name.css']}"; + manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.Scripts.Length); + Assert.AreEqual(2, manifest.Stylesheets.Length); + } + + [Test] + public void CanParseManifest_PropertyEditors() + { + const string json = @"{'propertyEditors': [ { alias: 'Test.Test1', name: 'Test 1', editor: { view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html', valueType: 'int', + hideLabel: true, validation: { 'required': true, 'Regex': '\\d*' @@ -60,147 +142,10 @@ namespace Umbraco.Tests.Manifest } ] } - } -]"); - var parser = _parser.GetPropertyEditors(a); - - Assert.AreEqual(1, parser.Count()); - Assert.AreEqual(2, parser.ElementAt(0).PreValueEditor.Fields.Count); - Assert.AreEqual("key1", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).Key); - Assert.AreEqual("Some config 1", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).Name); - Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).View); - Assert.AreEqual(1, parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).Validators.Count); - - Assert.AreEqual("key2", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).Key); - Assert.AreEqual("Some config 2", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).Name); - Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).View); - Assert.AreEqual(0, parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).Validators.Count()); - } - - [Test] - public void Parse_Grid_Editors() - { - var a = JsonConvert.DeserializeObject(@"[ - { - alias: 'Test.Test1', - name: 'Test 1', - view: 'blah', - icon: 'hello' }, { alias: 'Test.Test2', name: 'Test 2', - config: { key1: 'some default val' }, - view: '~/hello/world.cshtml', - icon: 'helloworld' - }, - { - alias: 'Test.Test3', - name: 'Test 3', - config: { key1: 'some default val' }, - view: '/hello/world.html', - render: '~/hello/world.cshtml', - icon: 'helloworld' - } -]"); - var parser = _parser.GetGridEditors(a).ToArray(); - - Assert.AreEqual(3, parser.Count()); - - Assert.AreEqual("Test.Test1", parser.ElementAt(0).Alias); - Assert.AreEqual("Test 1", parser.ElementAt(0).Name); - Assert.AreEqual("blah", parser.ElementAt(0).View); - Assert.AreEqual("hello", parser.ElementAt(0).Icon); - Assert.IsNull(parser.ElementAt(0).Render); - Assert.AreEqual(0, parser.ElementAt(0).Config.Count); - - Assert.AreEqual("Test.Test2", parser.ElementAt(1).Alias); - Assert.AreEqual("Test 2", parser.ElementAt(1).Name); - Assert.AreEqual("/hello/world.cshtml", parser.ElementAt(1).View); - Assert.AreEqual("helloworld", parser.ElementAt(1).Icon); - Assert.IsNull(parser.ElementAt(1).Render); - Assert.AreEqual(1, parser.ElementAt(1).Config.Count); - Assert.AreEqual("some default val", parser.ElementAt(1).Config["key1"]); - - Assert.AreEqual("Test.Test3", parser.ElementAt(2).Alias); - Assert.AreEqual("Test 3", parser.ElementAt(2).Name); - Assert.AreEqual("/hello/world.html", parser.ElementAt(2).View); - Assert.AreEqual("helloworld", parser.ElementAt(2).Icon); - Assert.AreEqual("/hello/world.cshtml", parser.ElementAt(2).Render); - Assert.AreEqual(1, parser.ElementAt(2).Config.Count); - Assert.AreEqual("some default val", parser.ElementAt(2).Config["key1"]); - - } - - [Test] - public void Parse_Property_Editors() - { - - var a = JsonConvert.DeserializeObject(@"[ - { - alias: 'Test.Test1', - name: 'Test 1', - icon: 'icon-war', - editor: { - view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html', - valueType: 'int', - validation: { - required : true, - regex : '\\d*' - } - } - }, - { - alias: 'Test.Test2', - name: 'Test 2', - group: 'customgroup', - defaultConfig: { key1: 'some default pre val' }, - editor: { - view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html', - hideLabel: true - } - } -]"); - var parser = _parser.GetPropertyEditors(a); - - Assert.AreEqual(2, parser.Count()); - - Assert.AreEqual(false, parser.ElementAt(0).ValueEditor.HideLabel); - Assert.AreEqual("Test.Test1", parser.ElementAt(0).Alias); - Assert.AreEqual("Test 1", parser.ElementAt(0).Name); - Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html", parser.ElementAt(0).ValueEditor.View); - Assert.AreEqual("int", parser.ElementAt(0).ValueEditor.ValueType); - Assert.AreEqual(2, parser.ElementAt(0).ValueEditor.Validators.Count()); - var manifestValidator1 = parser.ElementAt(0).ValueEditor.Validators.ElementAt(0) as ManifestPropertyValidator; - Assert.IsNotNull(manifestValidator1); - Assert.AreEqual("required", manifestValidator1.Type); - var manifestValidator2 = parser.ElementAt(0).ValueEditor.Validators.ElementAt(1) as ManifestPropertyValidator; - Assert.IsNotNull(manifestValidator2); - Assert.AreEqual("regex", manifestValidator2.Type); - - //groups and icons - Assert.AreEqual("common", parser.ElementAt(0).Group); - Assert.AreEqual("customgroup", parser.ElementAt(1).Group); - - Assert.AreEqual("icon-war", parser.ElementAt(0).Icon); - Assert.AreEqual("icon-autofill", parser.ElementAt(1).Icon); - - - Assert.AreEqual(true, parser.ElementAt(1).ValueEditor.HideLabel); - Assert.AreEqual("Test.Test2", parser.ElementAt(1).Alias); - Assert.AreEqual("Test 2", parser.ElementAt(1).Name); - Assert.IsTrue(parser.ElementAt(1).DefaultPreValues.ContainsKey("key1")); - Assert.AreEqual("some default pre val", parser.ElementAt(1).DefaultPreValues["key1"]); - } - - [Test] - public void Property_Editors_Can_Be_Parameter_Editor() - { - - var a = JsonConvert.DeserializeObject(@"[ - { - alias: 'Test.Test1', - name: 'Test 1', isParameterEditor: true, defaultConfig: { key1: 'some default val' }, editor: { @@ -211,31 +156,76 @@ namespace Umbraco.Tests.Manifest regex : '\\d*' } } - }, - { - alias: 'Test.Test2', - name: 'Test 2', - defaultConfig: { key1: 'some default pre val' }, - editor: { - view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html' - } } -]"); - var parser = _parser.GetPropertyEditors(a); +]}"; - Assert.AreEqual(1, parser.Count(x => x.IsParameterEditor)); + var manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.PropertyEditors.Length); - IParameterEditor parameterEditor = parser.First(); - Assert.AreEqual(1, parameterEditor.Configuration.Count); - Assert.IsTrue(parameterEditor.Configuration.ContainsKey("key1")); - Assert.AreEqual("some default val", parameterEditor.Configuration["key1"]); + var editor = manifest.PropertyEditors[1]; + Assert.IsTrue(editor.IsParameterEditor); + + editor = manifest.PropertyEditors[0]; + Assert.AreEqual("Test.Test1", editor.Alias); + Assert.AreEqual("Test 1", editor.Name); + Assert.IsFalse(editor.IsParameterEditor); + + var valueEditor = editor.ValueEditor; + Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html", valueEditor.View); + Assert.AreEqual("int", valueEditor.ValueType); + Assert.IsTrue(valueEditor.HideLabel); + + // these two don't make much sense here + // valueEditor.RegexValidator; + // valueEditor.RequiredValidator; + + var validators = valueEditor.Validators; + Assert.AreEqual(2, validators.Count); + var validator = validators[0]; + var v = validator as ManifestPropertyValidator; + Assert.IsNotNull(v); + Assert.AreEqual("required", v.Type); + Assert.AreEqual("", v.Config); + validator = validators[1]; + v = validator as ManifestPropertyValidator; + Assert.IsNotNull(v); + Assert.AreEqual("Regex", v.Type); + Assert.AreEqual("\\d*", v.Config); + + // this is not part of the manifest + var preValues = editor.DefaultPreValues; + Assert.IsNull(preValues); + + var preValueEditor = editor.PreValueEditor; + Assert.IsNotNull(preValueEditor); + Assert.IsNotNull(preValueEditor.Fields); + Assert.AreEqual(2, preValueEditor.Fields.Count); + + var f = preValueEditor.Fields[0]; + Assert.AreEqual("key1", f.Key); + Assert.AreEqual("Some config 1", f.Name); + Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html", f.View); + var fvalidators = f.Validators; + Assert.IsNotNull(fvalidators); + Assert.AreEqual(1, fvalidators.Count); + var fv = fvalidators[0] as ManifestPropertyValidator; + Assert.IsNotNull(fv); + Assert.AreEqual("required", fv.Type); + Assert.AreEqual("", fv.Config); + + f = preValueEditor.Fields[1]; + Assert.AreEqual("key2", f.Key); + Assert.AreEqual("Some config 2", f.Name); + Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html", f.View); + fvalidators = f.Validators; + Assert.IsNotNull(fvalidators); + Assert.AreEqual(0, fvalidators.Count); } [Test] - public void Parse_Parameter_Editors() + public void CanParseManifest_ParameterEditors() { - - var a = JsonConvert.DeserializeObject(@"[ + const string json = @"{'parameterEditors': [ { alias: 'parameter1', name: 'My Parameter', @@ -246,116 +236,47 @@ namespace Umbraco.Tests.Manifest name: 'Another parameter', config: { key1: 'some config val' }, view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html' + }, + { + alias: 'parameter3', + name: 'Yet another parameter' } -]"); - var parser = _parser.GetParameterEditors(a); +]}"; - Assert.AreEqual(2, parser.Count()); - Assert.AreEqual("parameter1", parser.ElementAt(0).Alias); - Assert.AreEqual("My Parameter", parser.ElementAt(0).Name); - Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html", parser.ElementAt(0).ValueEditor.View); + var manifest = _parser.ParseManifest(json); + Assert.AreEqual(3, manifest.ParameterEditors.Length); - Assert.AreEqual("parameter2", parser.ElementAt(1).Alias); - Assert.AreEqual("Another parameter", parser.ElementAt(1).Name); - Assert.IsTrue(parser.ElementAt(1).Configuration.ContainsKey("key1")); - Assert.AreEqual("some config val", parser.ElementAt(1).Configuration["key1"]); - } + var editor = manifest.ParameterEditors[1]; + Assert.AreEqual("parameter2", editor.Alias); + Assert.AreEqual("Another parameter", editor.Name); - [Test] - public void Merge_JArrays() - { - var obj1 = JArray.FromObject(new[] { "test1", "test2", "test3" }); - var obj2 = JArray.FromObject(new[] { "test1", "test2", "test3", "test4" }); + var config = editor.Configuration; + Assert.AreEqual(1, config.Count); + Assert.IsTrue(config.ContainsKey("key1")); + Assert.AreEqual("some config val", config["key1"]); - ManifestParser.MergeJArrays(obj1, obj2); + var valueEditor = editor.ValueEditor; + Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html", valueEditor.View); - Assert.AreEqual(4, obj1.Count()); - } - - [Test] - public void Merge_JObjects_Replace_Original() - { - var obj1 = JObject.FromObject(new - { - Property1 = "Value1", - Property2 = "Value2", - Property3 = "Value3" - }); - - var obj2 = JObject.FromObject(new + editor = manifest.ParameterEditors[2]; + Assert.Throws(() => { - Property3 = "Value3/2", - Property4 = "Value4", - Property5 = "Value5" + var _ = editor.ValueEditor; }); - - ManifestParser.MergeJObjects(obj1, obj2); - - Assert.AreEqual(5, obj1.Properties().Count()); - Assert.AreEqual("Value3/2", obj1.Properties().ElementAt(2).Value.Value()); } [Test] - public void Merge_JObjects_Keep_Original() + public void CanParseManifest_GridEditors() { - var obj1 = JObject.FromObject(new - { - Property1 = "Value1", - Property2 = "Value2", - Property3 = "Value3" - }); - - var obj2 = JObject.FromObject(new - { - Property3 = "Value3/2", - Property4 = "Value4", - Property5 = "Value5" - }); - - ManifestParser.MergeJObjects(obj1, obj2, true); - - Assert.AreEqual(5, obj1.Properties().Count()); - Assert.AreEqual("Value3", obj1.Properties().ElementAt(2).Value.Value()); - } - - - [TestCase("C:\\Test", "C:\\Test\\MyFolder\\AnotherFolder", 2)] - [TestCase("C:\\Test", "C:\\Test\\MyFolder\\AnotherFolder\\YetAnother", 3)] - [TestCase("C:\\Test", "C:\\Test\\", 0)] - public void Get_Folder_Depth(string baseFolder, string currFolder, int expected) - { - Assert.AreEqual(expected, - _parser.FolderDepth( - new DirectoryInfo(baseFolder), - new DirectoryInfo(currFolder))); - } - - - - //[Test] - //public void Parse_Property_Editor() - //{ - - //} - - [Test] - public void Create_Manifests_Editors() - { - var package1 = @"{ -propertyEditors: [], -javascript: ['~/test.js', '~/test2.js']}"; - - var package2 = "{css: ['~/style.css', '~/folder-name/sdsdsd/stylesheet.css']}"; - - var package3 = @"{ + const string json = @"{ 'javascript': [ ], 'css': [ ], 'gridEditors': [ { 'name': 'Small Hero', 'alias': 'small-hero', - 'view': '/App_Plugins/MyPlugin/small-hero/editortemplate.html', - 'render': '/Views/Partials/Grid/Editors/SmallHero.cshtml', + 'view': '~/App_Plugins/MyPlugin/small-hero/editortemplate.html', + 'render': '~/Views/Partials/Grid/Editors/SmallHero.cshtml', 'icon': 'icon-presentation', 'config': { 'image': { @@ -373,144 +294,32 @@ javascript: ['~/test.js', '~/test2.js']}"; { 'name': 'Document Links By Category', 'alias': 'document-links-by-category', - 'view': '/App_Plugins/MyPlugin/document-links-by-category/editortemplate.html', - 'render': '/Views/Partials/Grid/Editors/DocumentLinksByCategory.cshtml', + 'view': '~/App_Plugins/MyPlugin/document-links-by-category/editortemplate.html', + 'render': '~/Views/Partials/Grid/Editors/DocumentLinksByCategory.cshtml', 'icon': 'icon-umb-members' } ] }"; - var package4 = @"{'propertyEditors': [ - { - alias: 'Test.Test1', - name: 'Test 1', - editor: { - view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html', - valueType: 'int', - validation: { - 'required': true, - 'Regex': '\\d*' - } - }, - prevalues: { - fields: [ - { - label: 'Some config 1', - key: 'key1', - view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html', - validation: { - required: true - } - }, - { - label: 'Some config 2', - key: 'key2', - view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html' - } - ] - } - } -]}"; + var manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.GridEditors.Length); - var package5 = @"{'parameterEditors': [ - { - alias: 'parameter1', - name: 'My Parameter', - view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html' - }, - { - alias: 'parameter2', - name: 'Another parameter', - config: { key1: 'some config val' }, - view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html' - } -]}"; + var editor = manifest.GridEditors[0]; + Assert.AreEqual("small-hero", editor.Alias); + Assert.AreEqual("Small Hero", editor.Name); + Assert.AreEqual("/App_Plugins/MyPlugin/small-hero/editortemplate.html", editor.View); + Assert.AreEqual("/Views/Partials/Grid/Editors/SmallHero.cshtml", editor.Render); + Assert.AreEqual("icon-presentation", editor.Icon); - var result = _parser.CreateManifests(package1, package2, package3, package4, package5).ToArray(); - - var paramEditors = result.SelectMany(x => _parser.GetParameterEditors(x.ParameterEditors)).ToArray(); - var propEditors = result.SelectMany(x => _parser.GetPropertyEditors(x.PropertyEditors)).ToArray(); - var gridEditors = result.SelectMany(x => _parser.GetGridEditors(x.GridEditors)).ToArray(); - - Assert.AreEqual(2, gridEditors.Count()); - Assert.AreEqual(2, paramEditors.Count()); - Assert.AreEqual(1, propEditors.Count()); + var config = editor.Config; + Assert.AreEqual(2, config.Count); + Assert.IsTrue(config.ContainsKey("image")); + var c = config["image"]; + Assert.IsInstanceOf(c); // fixme - is this what we want? + Assert.IsTrue(config.ContainsKey("link")); + c = config["link"]; + Assert.IsInstanceOf(c); // fixme - is this what we want? + // fixme - should we resolveUrl in configs? } - - [Test] - public void Create_Manifest_With_Line_Comments() - { - var content4 = @"{ -//here's the property editors -propertyEditors: [], -//and here's the javascript -javascript: ['~/test.js', '~/test2.js']}"; - - var result = _parser.CreateManifests(null, content4); - - Assert.AreEqual(1, result.Count()); - } - - [Test] - public void Create_Manifest_With_Surround_Comments() - { - var content4 = @"{ -propertyEditors: []/*we have empty property editors**/, -javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2.js']}"; - - var result = _parser.CreateManifests(null, content4); - - Assert.AreEqual(1, result.Count()); - } - - [Test] - public void Create_Manifest_With_Error() - { - //NOTE: This is missing the final closing ] - var content4 = @"{ -propertyEditors: []/*we have empty property editors**/, -javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2.js' }"; - - var result = _parser.CreateManifests(null, content4); - - //an error has occurred and been logged but processing continues - Assert.AreEqual(0, result.Count()); - } - - [Test] - public void Create_Manifest_From_File_Content() - { - var content1 = "{}"; - var content2 = "{javascript: []}"; - var content3 = "{javascript: ['~/test.js', '~/test2.js']}"; - var content4 = "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}"; - var content5 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()) + "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}"; - - var result = _parser.CreateManifests(null, content1, content2, content3, content4, content5); - - Assert.AreEqual(5, result.Count()); - Assert.AreEqual(0, result.ElementAt(1).JavaScriptInitialize.Count); - Assert.AreEqual(2, result.ElementAt(2).JavaScriptInitialize.Count); - Assert.AreEqual(2, result.ElementAt(3).JavaScriptInitialize.Count); - Assert.AreEqual(2, result.ElementAt(4).JavaScriptInitialize.Count); - } - - [Test] - public void Parse_Stylesheet_Initialization() - { - var content1 = "{}"; - var content2 = "{css: []}"; - var content3 = "{css: ['~/style.css', '~/folder-name/sdsdsd/stylesheet.css']}"; - var content4 = "{propertyEditors: [], css: ['~/stylesheet.css', '~/random-long-name.css']}"; - - var result = _parser.CreateManifests(null, content1, content2, content3, content4); - - Assert.AreEqual(4, result.Count()); - Assert.AreEqual(0, result.ElementAt(1).StylesheetInitialize.Count); - Assert.AreEqual(2, result.ElementAt(2).StylesheetInitialize.Count); - Assert.AreEqual(2, result.ElementAt(3).StylesheetInitialize.Count); - } - - } } diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs index 63e06ffa0c..50940db1fd 100644 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs @@ -21,9 +21,10 @@ namespace Umbraco.Tests.Models.Mapping { base.Compose(); - var manifestBuilder = new ManifestBuilder( + var manifestBuilder = new ManifestParser( CacheHelper.CreateDisabledCacheHelper().RuntimeCache, - new ManifestParser(Logger, new DirectoryInfo(TestHelper.CurrentAssemblyDirectory), CacheHelper.CreateDisabledCacheHelper().RuntimeCache)); + TestHelper.CurrentAssemblyDirectory, + Logger); Container.Register(_ => manifestBuilder); Func> typeListProducerList = Enumerable.Empty; diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 660d644b2f..2d16239fc3 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -303,10 +303,7 @@ namespace Umbraco.Tests.Testing Container.RegisterSingleton(); // somehow property editor ends up wanting this - Container.RegisterSingleton(f => new ManifestBuilder( - f.GetInstance(), - new ManifestParser(f.GetInstance(), new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), f.GetInstance()) - )); + Container.RegisterSingleton(); // note - don't register collections, use builders Container.RegisterCollectionBuilder(); diff --git a/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs index aa238760ce..dacba39163 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Web.AngularIntegration [Test] public void Parse_Main() { - var result = JsInitialization.ParseMain(new[] {"[World]", "Hello" }); + var result = JsInitialization.WriteScript(new[] {"[World]", "Hello" }); Assert.AreEqual(@"LazyLoad.js([World], function () { //we need to set the legacy UmbClientMgr path diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 430eae64a4..761058d633 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -32,6 +32,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Composing; using Action = Umbraco.Web._Legacy.Actions.Action; using Constants = Umbraco.Core.Constants; +using JArray = Newtonsoft.Json.Linq.JArray; namespace Umbraco.Web.Editors { @@ -47,7 +48,7 @@ namespace Umbraco.Web.Editors [DisableClientCache] public class BackOfficeController : UmbracoController { - private readonly IRuntimeState _runtime; + private readonly ManifestParser _manifestParser; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; @@ -55,9 +56,9 @@ namespace Umbraco.Web.Editors private const string TokenPasswordResetCode = "PasswordResetCode"; private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode }; - public BackOfficeController(IRuntimeState runtime) + public BackOfficeController(ManifestParser manifestParser) { - _runtime = runtime; + _manifestParser = manifestParser; } protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); @@ -184,12 +185,12 @@ namespace Umbraco.Web.Editors [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] public JavaScriptResult Application() { - var parser = GetManifestParser(); + var parser = _manifestParser; var initJs = new JsInitialization(parser); var initCss = new CssInitialization(parser); //get the legacy ActionJs file references to append as well - var legacyActionJsRef = new JArray(GetLegacyActionJs(LegacyJsActionType.JsUrl)); + var legacyActionJsRef = GetLegacyActionJs(LegacyJsActionType.JsUrl); var result = initJs.GetJavascriptInitialization(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); result += initCss.GetStylesheetInitialization(HttpContext); @@ -205,24 +206,24 @@ namespace Umbraco.Web.Editors [HttpGet] public JsonNetResult GetManifestAssetList() { - Func getResult = () => + JArray GetAssetList() { - var parser = GetManifestParser(); + var parser = _manifestParser; var initJs = new JsInitialization(parser); var initCss = new CssInitialization(parser); - var jsResult = initJs.GetJavascriptInitializationArray(HttpContext, new JArray()); - var cssResult = initCss.GetStylesheetInitializationArray(HttpContext); - ManifestParser.MergeJArrays(jsResult, cssResult); - return jsResult; - }; + var assets = new List(); + assets.AddRange(initJs.GetScriptFiles(HttpContext, Enumerable.Empty())); + assets.AddRange(initCss.GetStylesheetFiles(HttpContext)); + return new JArray(assets); + } //cache the result if debugging is disabled var result = HttpContext.IsDebuggingEnabled - ? getResult() + ? GetAssetList() : ApplicationCache.RuntimeCache.GetCacheItem( - typeof(BackOfficeController) + "GetManifestAssetList", - () => getResult(), - new TimeSpan(0, 10, 0)); + "Umbraco.Web.Editors.BackOfficeController.GetManifestAssetList", + GetAssetList, + new TimeSpan(0, 2, 0)); return new JsonNetResult { Data = result, Formatting = Formatting.Indented }; } @@ -333,13 +334,6 @@ namespace Umbraco.Web.Editors return RedirectToLocal(Url.Action("Default", "BackOffice")); } - private ManifestParser GetManifestParser() - { - var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins")); - var parser = new ManifestParser(Logger, plugins, ApplicationCache.RuntimeCache); - return parser; - } - /// /// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info, /// otherwise process the external login info. diff --git a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs index 2f7d2b6b5e..9c5e0f1ec2 100644 --- a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs @@ -2,12 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; using System.Web; using ClientDependency.Core; using ClientDependency.Core.Config; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; using Umbraco.Web.Composing; using Umbraco.Web.PropertyEditors; @@ -15,88 +12,53 @@ namespace Umbraco.Web.UI.JavaScript { internal abstract class AssetInitialization { - /// - /// Get all dependencies declared on property editors - /// - /// - /// - /// - protected JArray ScanPropertyEditors(ClientDependencyType cdfType, HttpContextBase httpContext) + protected IEnumerable ScanPropertyEditors(ClientDependencyType assetType, HttpContextBase httpContext) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - var cdfAttributes = - Current.PropertyEditors + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + var attributes = Current.PropertyEditors .SelectMany(x => x.GetType().GetCustomAttributes(false)) - .Where(x => x.AssetType == cdfType) + .Where(x => x.AssetType == assetType) .Select(x => x.DependencyFile) .ToList(); - string jsOut; - string cssOut; var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; - renderer.RegisterDependencies(cdfAttributes, new HashSet(), out jsOut, out cssOut, httpContext); + renderer.RegisterDependencies(attributes, new HashSet(), out var scripts, out var stylesheets, httpContext); - var toParse = cdfType == ClientDependencyType.Javascript ? jsOut : cssOut; - - var result = new JArray(); - //split the result by the delimiter and add to the array - foreach (var u in toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries)) - { - result.Add(u); - } - return result; + var toParse = assetType == ClientDependencyType.Javascript ? scripts : stylesheets; + return toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); } - /// - /// This will use CDF to optimize the asset file collection - /// - /// - /// - /// - /// - /// Return the asset URLs that should be loaded, if the application is in debug mode then the URLs returned will be the same as the ones - /// passed in with the CDF version query strings appended so cache busting works correctly. - /// - protected JArray OptimizeAssetCollection(JArray fileRefs, ClientDependencyType cdfType, HttpContextBase httpContext) + protected IEnumerable OptimizeAssetCollection(IEnumerable assets, ClientDependencyType assetType, HttpContextBase httpContext) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); - var depenencies = fileRefs.Select(x => + var requestUrl = httpContext.Request.Url; + if (requestUrl == null) throw new ArgumentException("HttpContext.Request.Url is null.", nameof(httpContext)); + + var dependencies = assets.Select(x => { - var asString = x.ToString(); - if (asString.StartsWith("/") == false) + // most declarations with be made relative to the /umbraco folder, so things + // ike lib/blah/blah.js so we need to turn them into absolutes here + if (x.StartsWith("/") == false && Uri.IsWellFormedUriString(x, UriKind.Relative)) { - //most declarations with be made relative to the /umbraco folder, so things like lib/blah/blah.js - // so we need to turn them into absolutes here - if (Uri.IsWellFormedUriString(asString, UriKind.Relative)) - { - var absolute = new Uri(httpContext.Request.Url, asString); - return (IClientDependencyFile)new BasicFile(cdfType) { FilePath = absolute.AbsolutePath }; - } + return (IClientDependencyFile) new BasicFile(assetType) { FilePath = new Uri(requestUrl, x).AbsolutePath }; } - return cdfType == ClientDependencyType.Javascript - ? (IClientDependencyFile)new JavascriptFile(asString) - : (IClientDependencyFile)new CssFile(asString); - }).Where(x => x != null).ToList(); - //Get the output string for these registrations which will be processed by CDF correctly to stagger the output based + return assetType == ClientDependencyType.Javascript + ? (IClientDependencyFile) new JavascriptFile(x) + : (IClientDependencyFile) new CssFile(x); + }).ToList(); + + // get the output string for these registrations which will be processed by CDF correctly to stagger the output based // on internal vs external resources. The output will be delimited based on our custom Umbraco.Web.UI.JavaScript.DependencyPathRenderer - string jsOut; - string cssOut; var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; - renderer.RegisterDependencies(depenencies, new HashSet(), out jsOut, out cssOut, httpContext); + renderer.RegisterDependencies(dependencies, new HashSet(), out var scripts, out var stylesheets, httpContext); - var urls = cdfType == ClientDependencyType.Javascript - ? jsOut.Split(new string[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries) - : cssOut.Split(new string[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); + var urls = assetType == ClientDependencyType.Javascript + ? scripts.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries) + : stylesheets.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); - var result = new JArray(); - foreach (var u in urls) - { - result.Add(u); - } - return result; + return urls; } - } } diff --git a/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs b/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs index 15ec5bb4f6..53f2136150 100644 --- a/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs @@ -1,12 +1,9 @@ using System.Web; using ClientDependency.Core; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.IO; using Umbraco.Core.Manifest; namespace Umbraco.Web.UI.JavaScript @@ -14,50 +11,39 @@ namespace Umbraco.Web.UI.JavaScript internal class CssInitialization : AssetInitialization { private readonly ManifestParser _parser; + public CssInitialization(ManifestParser parser) { _parser = parser; } /// - /// Processes all found manifest files and outputs yepnope.injectcss calls for all css files found in all manifests + /// Processes all found manifest files, and outputs css inject calls for all css files found in all manifests. /// public string GetStylesheetInitialization(HttpContextBase httpContext) { - var result = GetStylesheetInitializationArray(httpContext); - - return ParseMain(result); + var files = GetStylesheetFiles(httpContext); + return WriteScript(files); } - public JArray GetStylesheetInitializationArray(HttpContextBase httpContext) + public IEnumerable GetStylesheetFiles(HttpContextBase httpContext) { - var merged = new JArray(); - foreach (var m in _parser.GetManifests()) - { - ManifestParser.MergeJArrays(merged, m.StylesheetInitialize); - } + var stylesheets = new HashSet(); + var optimizedManifest = OptimizeAssetCollection(_parser.Manifest.Stylesheets, ClientDependencyType.Css, httpContext); + foreach (var stylesheet in optimizedManifest) + stylesheets.Add(stylesheet); - //now we can optimize if in release mode - merged = OptimizeAssetCollection(merged, ClientDependencyType.Css, httpContext); + foreach (var stylesheet in ScanPropertyEditors(ClientDependencyType.Css, httpContext)) + stylesheets.Add(stylesheet); - //now we need to merge in any found cdf declarations on property editors - ManifestParser.MergeJArrays(merged, ScanPropertyEditors(ClientDependencyType.Css, httpContext)); - - return merged; + return stylesheets.ToArray(); } - - /// - /// Parses the CssResources.Main and returns a yepnop.injectCss format - /// - /// - /// - internal static string ParseMain(JArray files) + internal static string WriteScript(IEnumerable files) { var sb = new StringBuilder(); foreach (var file in files) sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file); - return sb.ToString(); } diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs index 26b554eda5..dffe92f33c 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs @@ -1,17 +1,13 @@ using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Text.RegularExpressions; using System.Web; using ClientDependency.Core; -using ClientDependency.Core.Config; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Manifest; using System.Linq; +using System.Text; namespace Umbraco.Web.UI.JavaScript { @@ -28,65 +24,65 @@ namespace Umbraco.Web.UI.JavaScript _parser = parser; } - //used to strip comments - internal static readonly Regex Comments = new Regex("(/\\*.*\\*/)", RegexOptions.Compiled); - //used for dealing with js functions inside of json (which is not a supported json syntax) + // deal with javascript functions inside of json (not a supported json syntax) private const string PrefixJavaScriptObject = "@@@@"; - private static readonly Regex JsFunctionParser = new Regex(string.Format("(\"{0}(.*?)\")+", PrefixJavaScriptObject), - RegexOptions.Multiline - | RegexOptions.CultureInvariant - | RegexOptions.Compiled); - //used to replace the tokens in the js main + private static readonly Regex JsFunctionParser = new Regex($"(\"{PrefixJavaScriptObject}(.*?)\")+", + RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); + + // replace tokens in the js main private static readonly Regex Token = new Regex("(\"##\\w+?##\")", RegexOptions.Compiled); /// /// Processes all found manifest files and outputs the main.js file containing all plugin manifests /// - public string GetJavascriptInitialization(HttpContextBase httpContext, JArray umbracoInit, JArray additionalJsFiles = null) + public string GetJavascriptInitialization(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) { - var result = GetJavascriptInitializationArray(httpContext, umbracoInit, additionalJsFiles); + var files = GetScriptFiles(httpContext, umbracoInit, additionalJsFiles); - return ParseMain( - result.ToString(), - IOHelper.ResolveUrl(SystemDirectories.Umbraco)); + var jarray = new StringBuilder(); + jarray.AppendLine("["); + var first = true; + foreach (var file in files) + { + if (first) first = false; + else jarray.AppendLine(","); + jarray.Append("\""); + jarray.Append(file); + jarray.Append("\""); + + } + jarray.Append("]"); + + return WriteScript(jarray.ToString(), IOHelper.ResolveUrl(SystemDirectories.Umbraco)); } - public JArray GetJavascriptInitializationArray(HttpContextBase httpContext, JArray umbracoInit, JArray additionalJsFiles = null) + public IEnumerable GetScriptFiles(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) { - foreach (var m in _parser.GetManifests()) - { - ManifestParser.MergeJArrays(umbracoInit, m.JavaScriptInitialize); - } - - //merge in the additional ones specified if there are any + var scripts = new HashSet(); + foreach (var script in umbracoInit) + scripts.Add(script); + foreach (var script in _parser.Manifest.Scripts) + scripts.Add(script); if (additionalJsFiles != null) - { - ManifestParser.MergeJArrays(umbracoInit, additionalJsFiles); - } + foreach (var script in additionalJsFiles) + scripts.Add(script); - //now we can optimize if in release mode - umbracoInit = OptimizeAssetCollection(umbracoInit, ClientDependencyType.Javascript, httpContext); + scripts = new HashSet(OptimizeAssetCollection(scripts, ClientDependencyType.Javascript, httpContext)); - //now we need to merge in any found cdf declarations on property editors - ManifestParser.MergeJArrays(umbracoInit, ScanPropertyEditors(ClientDependencyType.Javascript, httpContext)); + foreach (var script in ScanPropertyEditors(ClientDependencyType.Javascript, httpContext)) + scripts.Add(script); - return umbracoInit; + return scripts.ToArray(); } /// /// Returns the default config as a JArray /// /// - internal static JArray GetDefaultInitialization() + internal static IEnumerable GetDefaultInitialization() { - var init = Resources.JsInitialize; - var deserialized = JsonConvert.DeserializeObject(init); - var result = new JArray(); - foreach (var j in deserialized.Where(j => j.Type == JTokenType.String)) - { - result.Add(j); - } - return result; + var resources = JsonConvert.DeserializeObject(Resources.JsInitialize); + return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()); } /// @@ -94,22 +90,18 @@ namespace Umbraco.Web.UI.JavaScript /// /// /// - internal static string ParseMain(params string[] replacements) + internal static string WriteScript(params string[] replacements) { var count = 0; + // replace, catering for the special syntax when we have + // js function() objects contained in the json + return Token.Replace(Resources.Main, match => { - var replaced = replacements[count]; - - //we need to cater for the special syntax when we have js function() objects contained in the json - var jsFunctionParsed = JsFunctionParser.Replace(replaced, "$2"); - - count++; - - return jsFunctionParsed; + var replacement = replacements[count++]; + return JsFunctionParser.Replace(replacement, "$2"); }); } - } }