2019-01-25 10:04:18 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
using System.Text;
|
2019-01-25 10:04:18 +01:00
|
|
|
|
using Newtonsoft.Json;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
using Umbraco.Core.Cache;
|
2019-01-25 10:04:18 +01:00
|
|
|
|
using Umbraco.Core.Exceptions;
|
|
|
|
|
|
using Umbraco.Core.IO;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
using Umbraco.Core.Logging;
|
2019-01-25 10:04:18 +01:00
|
|
|
|
using Umbraco.Core.PropertyEditors;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
namespace Umbraco.Core.Manifest
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Parses the Main.js file and replaces all tokens accordingly.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class ManifestParser
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
|
|
|
|
|
|
|
|
|
|
|
|
private readonly IAppPolicyCache _cache;
|
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
private readonly ManifestValueValidatorCollection _validators;
|
|
|
|
|
|
|
|
|
|
|
|
private string _path;
|
2018-12-05 09:05:47 +01:00
|
|
|
|
|
2018-11-28 16:19:50 +01:00
|
|
|
|
/// <summary>
|
2019-01-25 10:04:18 +01:00
|
|
|
|
/// Initializes a new instance of the <see cref="ManifestParser"/> class.
|
2018-11-28 16:19:50 +01:00
|
|
|
|
/// </summary>
|
2019-01-25 10:04:18 +01:00
|
|
|
|
public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ILogger logger)
|
|
|
|
|
|
: this(appCaches, validators, "~/App_Plugins", logger)
|
2018-10-08 19:22:20 +02:00
|
|
|
|
{ }
|
2018-07-06 17:36:33 +02:00
|
|
|
|
|
2018-11-28 16:19:50 +01:00
|
|
|
|
/// <summary>
|
2019-01-25 10:04:18 +01:00
|
|
|
|
/// Initializes a new instance of the <see cref="ManifestParser"/> class.
|
2018-11-28 16:19:50 +01:00
|
|
|
|
/// </summary>
|
2019-01-25 10:04:18 +01:00
|
|
|
|
private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, string path, ILogger logger)
|
2018-12-05 09:05:47 +01:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
if (appCaches == null) throw new ArgumentNullException(nameof(appCaches));
|
|
|
|
|
|
_cache = appCaches.RuntimeCache;
|
|
|
|
|
|
_validators = validators ?? throw new ArgumentNullException(nameof(validators));
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path));
|
|
|
|
|
|
Path = path;
|
|
|
|
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
2018-12-05 09:05:47 +01:00
|
|
|
|
}
|
2018-07-06 17:36:33 +02:00
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
public string Path
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
get => _path;
|
|
|
|
|
|
set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value;
|
|
|
|
|
|
}
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets all manifests, merged into a single manifest object.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public PackageManifest Manifest
|
|
|
|
|
|
=> _cache.GetCacheItem<PackageManifest>("Umbraco.Core.Manifest.ManifestParser::Manifests", () =>
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
var manifests = GetManifests();
|
|
|
|
|
|
return MergeManifests(manifests);
|
|
|
|
|
|
}, new TimeSpan(0, 4, 0));
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets all manifests.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private IEnumerable<PackageManifest> GetManifests()
|
|
|
|
|
|
{
|
|
|
|
|
|
var manifests = new List<PackageManifest>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var path in GetManifestFiles())
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
var text = File.ReadAllText(path);
|
|
|
|
|
|
text = TrimPreamble(text);
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
var manifest = ParseManifest(text);
|
|
|
|
|
|
manifests.Add(manifest);
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
2019-01-25 10:04:18 +01:00
|
|
|
|
catch (Exception e)
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
_logger.Error<ManifestParser>(e, "Failed to parse manifest at '{Path}', ignoring.", path);
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
return manifests;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Merges all manifests into one.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static PackageManifest MergeManifests(IEnumerable<PackageManifest> manifests)
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
var scripts = new HashSet<string>();
|
|
|
|
|
|
var stylesheets = new HashSet<string>();
|
|
|
|
|
|
var propertyEditors = new List<IDataEditor>();
|
|
|
|
|
|
var parameterEditors = new List<IDataEditor>();
|
|
|
|
|
|
var gridEditors = new List<GridEditor>();
|
|
|
|
|
|
var contentApps = new List<ManifestContentAppDefinition>();
|
|
|
|
|
|
var dashboards = new List<ManifestDashboardDefinition>();
|
|
|
|
|
|
var sections = new List<ManifestBackOfficeSection>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var manifest in manifests)
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
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);
|
|
|
|
|
|
if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps);
|
|
|
|
|
|
if (manifest.Dashboards != null) dashboards.AddRange(manifest.Dashboards);
|
|
|
|
|
|
if (manifest.Sections != null) sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias.ToLowerInvariant()));
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
2019-01-25 10:04:18 +01:00
|
|
|
|
|
|
|
|
|
|
return new PackageManifest
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
Scripts = scripts.ToArray(),
|
|
|
|
|
|
Stylesheets = stylesheets.ToArray(),
|
|
|
|
|
|
PropertyEditors = propertyEditors.ToArray(),
|
|
|
|
|
|
ParameterEditors = parameterEditors.ToArray(),
|
|
|
|
|
|
GridEditors = gridEditors.ToArray(),
|
|
|
|
|
|
ContentApps = contentApps.ToArray(),
|
|
|
|
|
|
Dashboards = dashboards.ToArray(),
|
|
|
|
|
|
Sections = sections.ToArray()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
// gets all manifest files (recursively)
|
|
|
|
|
|
private IEnumerable<string> GetManifestFiles()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Directory.Exists(_path) == false)
|
|
|
|
|
|
return new string[0];
|
|
|
|
|
|
return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories);
|
|
|
|
|
|
}
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
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);
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
return text;
|
|
|
|
|
|
}
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Parses a manifest.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal PackageManifest ParseManifest(string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
|
|
|
|
throw new ArgumentNullOrEmptyException(nameof(text));
|
|
|
|
|
|
|
|
|
|
|
|
var manifest = JsonConvert.DeserializeObject<PackageManifest>(text,
|
|
|
|
|
|
new DataEditorConverter(_logger),
|
|
|
|
|
|
new ValueValidatorConverter(_validators),
|
|
|
|
|
|
new DashboardAccessRuleConverter());
|
|
|
|
|
|
|
|
|
|
|
|
// 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]);
|
|
|
|
|
|
|
|
|
|
|
|
// add property editors that are also parameter editors, to the parameter editors list
|
|
|
|
|
|
// (the manifest format is kinda legacy)
|
|
|
|
|
|
var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList();
|
|
|
|
|
|
if (ppEditors.Count > 0)
|
|
|
|
|
|
manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
return manifest;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-25 10:04:18 +01:00
|
|
|
|
// purely for tests
|
|
|
|
|
|
internal IEnumerable<GridEditor> ParseGridEditors(string text)
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-25 10:04:18 +01:00
|
|
|
|
return JsonConvert.DeserializeObject<IEnumerable<GridEditor>>(text);
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|