Skip to content

Commit

Permalink
V11: Using IFileProvider to access assets added from packages (#13141)
Browse files Browse the repository at this point in the history
* Creating a FileProviderFactory for getting the package.manifest and grid.editors.config.js files through a file provider

* Collecting the package.manifest-s from different sources

* Searching different sources for grid.editors.config.js

* Using an IFileProvider to collect all tours

* Refactoring IconService.cs

* Typo

* Optimizations when looping through the file system

* Moving WebRootFileProviderFactory to Umbraco.Web.Common proj

* Removes double registering

* pluginLangFileSources includes the localPluginFileSources

* Comments

* Remove linq from foreach

* Change workflow for grid.editors.config.js so we check first physical file, then RCL, then Embedded

* Clean up

* Check if config dir exists

* Discover nested package.manifest files

* Fix IFileInfo.PhysicalPath check

* Revert 712810e as that way files in content root are preferred over those in web root

* Adding comments

* Refactoring

* Remove PhysicalPath check

* Fix registration of WebRootFileProviderFactory
  • Loading branch information
elit0451 authored Nov 2, 2022
1 parent dc9d415 commit 897cf4c
Show file tree
Hide file tree
Showing 13 changed files with 391 additions and 142 deletions.
25 changes: 23 additions & 2 deletions src/Umbraco.Core/Configuration/Grid/GridConfig.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Web.Common.DependencyInjection;

namespace Umbraco.Cms.Core.Configuration.Grid;

Expand All @@ -13,9 +16,27 @@ public GridConfig(
IManifestParser manifestParser,
IJsonSerializer jsonSerializer,
IHostingEnvironment hostingEnvironment,
ILoggerFactory loggerFactory)
ILoggerFactory loggerFactory,
IGridEditorsConfigFileProviderFactory gridEditorsConfigFileProviderFactory)
=> EditorsConfig =
new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, loggerFactory.CreateLogger<GridEditorsConfig>());
new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, loggerFactory.CreateLogger<GridEditorsConfig>(), gridEditorsConfigFileProviderFactory);

[Obsolete("Use other ctor - Will be removed in Umbraco 13")]
public GridConfig(
AppCaches appCaches,
IManifestParser manifestParser,
IJsonSerializer jsonSerializer,
IHostingEnvironment hostingEnvironment,
ILoggerFactory loggerFactory)
: this(
appCaches,
manifestParser,
jsonSerializer,
hostingEnvironment,
loggerFactory,
StaticServiceProvider.Instance.GetRequiredService<IGridEditorsConfigFileProviderFactory>())
{
}

public IGridEditorsConfig EditorsConfig { get; }
}
86 changes: 68 additions & 18 deletions src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;

namespace Umbraco.Cms.Core.Configuration.Grid;

Expand All @@ -17,20 +20,40 @@ internal class GridEditorsConfig : IGridEditorsConfig

private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger<GridEditorsConfig> _logger;
private readonly IGridEditorsConfigFileProviderFactory _gridEditorsConfigFileProviderFactory;
private readonly IManifestParser _manifestParser;

public GridEditorsConfig(
AppCaches appCaches,
IHostingEnvironment hostingEnvironment,
IManifestParser manifestParser,
IJsonSerializer jsonSerializer,
ILogger<GridEditorsConfig> logger)
ILogger<GridEditorsConfig> logger,
IGridEditorsConfigFileProviderFactory gridEditorsConfigFileProviderFactory)
{
_appCaches = appCaches;
_hostingEnvironment = hostingEnvironment;
_manifestParser = manifestParser;
_jsonSerializer = jsonSerializer;
_logger = logger;
_gridEditorsConfigFileProviderFactory = gridEditorsConfigFileProviderFactory;
}

[Obsolete("Use other ctor - Will be removed in Umbraco 13")]
public GridEditorsConfig(
AppCaches appCaches,
IHostingEnvironment hostingEnvironment,
IManifestParser manifestParser,
IJsonSerializer jsonSerializer,
ILogger<GridEditorsConfig> logger)
: this(
appCaches,
hostingEnvironment,
manifestParser,
jsonSerializer,
logger,
StaticServiceProvider.Instance.GetRequiredService<IGridEditorsConfigFileProviderFactory>())
{
}

public IEnumerable<IGridEditorConfig> Editors
Expand All @@ -39,13 +62,37 @@ public IEnumerable<IGridEditorConfig> Editors
{
List<IGridEditorConfig> GetResult()
{
var configFolder =
new DirectoryInfo(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config));
IFileInfo? gridConfig = null;
var editors = new List<IGridEditorConfig>();
var gridConfig = Path.Combine(configFolder.FullName, "grid.editors.config.js");
if (File.Exists(gridConfig))
var configPath = Constants.SystemDirectories.Config.TrimStart(Constants.CharArrays.Tilde);

// Get physical file if it exists
var configPhysicalDirPath = _hostingEnvironment.MapPathContentRoot(configPath);

if (Directory.Exists(configPhysicalDirPath) == true)
{
var physicalFileProvider = new PhysicalFileProvider(configPhysicalDirPath);
gridConfig = GetConfigFile(physicalFileProvider, string.Empty);
}

// If there is no physical file, check in RCLs
if (gridConfig is null)
{
IFileProvider? compositeFileProvider = _gridEditorsConfigFileProviderFactory.Create();

if (compositeFileProvider is null)
{
throw new ArgumentNullException(nameof(compositeFileProvider));
}

gridConfig = GetConfigFile(compositeFileProvider, configPath);
}

if (gridConfig is not null)
{
var sourceString = File.ReadAllText(gridConfig);
using Stream stream = gridConfig.CreateReadStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
var sourceString = reader.ReadToEnd();

try
{
Expand All @@ -63,19 +110,16 @@ List<IGridEditorConfig> GetResult()
// Read default from embedded file
else
{
Assembly assembly = GetType().Assembly;
Stream? resourceStream = assembly.GetManifestResourceStream(
"Umbraco.Cms.Core.EmbeddedResources.Grid.grid.editors.config.js");
IFileProvider configFileProvider = new EmbeddedFileProvider(GetType().Assembly, "Umbraco.Cms.Core.EmbeddedResources.Grid");
IFileInfo embeddedConfig = configFileProvider.GetFileInfo("grid.editors.config.js");

if (resourceStream is not null)
{
using var reader = new StreamReader(resourceStream, Encoding.UTF8);
var sourceString = reader.ReadToEnd();
editors.AddRange(_jsonSerializer.Deserialize<IEnumerable<GridEditor>>(sourceString)!);
}
using Stream stream = embeddedConfig.CreateReadStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
var sourceString = reader.ReadToEnd();
editors.AddRange(_jsonSerializer.Deserialize<IEnumerable<GridEditor>>(sourceString)!);
}

// add manifest editors, skip duplicates
// Add manifest editors, skip duplicates
foreach (GridEditor gridEditor in _manifestParser.CombinedManifest.GridEditors)
{
if (editors.Contains(gridEditor) == false)
Expand All @@ -95,4 +139,10 @@ List<IGridEditorConfig> GetResult()
return result!;
}
}

private static IFileInfo? GetConfigFile(IFileProvider fileProvider, string path)
{
IFileInfo fileInfo = fileProvider.GetFileInfo($"{path}/grid.editors.config.js");
return fileInfo.Exists ? fileInfo : null;
}
}
10 changes: 10 additions & 0 deletions src/Umbraco.Core/IO/IGridEditorsConfigFileProviderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.Extensions.FileProviders;

namespace Umbraco.Cms.Core.IO;

/// <summary>
/// Factory for creating <see cref="IFileProvider" /> instances for providing the grid.editors.config.js file.
/// </summary>
public interface IGridEditorsConfigFileProviderFactory : IFileProviderFactory
{
}
10 changes: 10 additions & 0 deletions src/Umbraco.Core/IO/IManifestFileProviderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.Extensions.FileProviders;

namespace Umbraco.Cms.Core.IO;

/// <summary>
/// Factory for creating <see cref="IFileProvider" /> instances for providing the package.manifest file.
/// </summary>
public interface IManifestFileProviderFactory : IFileProviderFactory
{
}
92 changes: 82 additions & 10 deletions src/Umbraco.Infrastructure/Manifest/ManifestParser.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;

namespace Umbraco.Cms.Core.Manifest;
Expand All @@ -21,6 +25,7 @@ public class ManifestParser : IManifestParser

private readonly IAppPolicyCache _cache;
private readonly IDataValueEditorFactory _dataValueEditorFactory;
private readonly IManifestFileProviderFactory _manifestFileProviderFactory;
private readonly ManifestFilterCollection _filters;
private readonly IHostingEnvironment _hostingEnvironment;

Expand All @@ -46,7 +51,8 @@ public ManifestParser(
IJsonSerializer jsonSerializer,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
IDataValueEditorFactory dataValueEditorFactory)
IDataValueEditorFactory dataValueEditorFactory,
IManifestFileProviderFactory manifestFileProviderFactory)
{
if (appCaches == null)
{
Expand All @@ -64,6 +70,34 @@ public ManifestParser(
_localizedTextService = localizedTextService;
_shortStringHelper = shortStringHelper;
_dataValueEditorFactory = dataValueEditorFactory;
_manifestFileProviderFactory = manifestFileProviderFactory;
}

[Obsolete("Use other ctor - Will be removed in Umbraco 13")]
public ManifestParser(
AppCaches appCaches,
ManifestValueValidatorCollection validators,
ManifestFilterCollection filters,
ILogger<ManifestParser> logger,
IIOHelper ioHelper,
IHostingEnvironment hostingEnvironment,
IJsonSerializer jsonSerializer,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
IDataValueEditorFactory dataValueEditorFactory)
: this(
appCaches,
validators,
filters,
logger,
ioHelper,
hostingEnvironment,
jsonSerializer,
localizedTextService,
shortStringHelper,
dataValueEditorFactory,
StaticServiceProvider.Instance.GetRequiredService<IManifestFileProviderFactory>())
{
}

public string AppPluginsPath
Expand All @@ -89,25 +123,33 @@ public CompositePackageManifest CombinedManifest
public IEnumerable<PackageManifest> GetManifests()
{
var manifests = new List<PackageManifest>();
IFileProvider? manifestFileProvider = _manifestFileProviderFactory.Create();

if (manifestFileProvider is null)
{
throw new ArgumentNullException(nameof(manifestFileProvider));
}

foreach (var path in GetManifestFiles())
foreach (IFileInfo file in GetManifestFiles(manifestFileProvider, Constants.SystemDirectories.AppPlugins))
{
try
{
var text = File.ReadAllText(path);
using Stream stream = file.CreateReadStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
var text = reader.ReadToEnd();
text = TrimPreamble(text);
if (string.IsNullOrWhiteSpace(text))
{
continue;
}

PackageManifest manifest = ParseManifest(text);
manifest.Source = path;
manifest.Source = file.PhysicalPath!; // We assure that the PhysicalPath is not null in GetManifestFiles()
manifests.Add(manifest);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to parse manifest at '{Path}', ignoring.", path);
_logger.LogError(e, "Failed to parse manifest at '{Path}', ignoring.", file.PhysicalPath);
}
}

Expand Down Expand Up @@ -242,14 +284,44 @@ private static string TrimPreamble(string text)
return text;
}

// gets all manifest files (recursively)
private IEnumerable<string> GetManifestFiles()
// Gets all manifest files
private static IEnumerable<IFileInfo> GetManifestFiles(IFileProvider fileProvider, string path)
{
if (Directory.Exists(_path) == false)
var manifestFiles = new List<IFileInfo>();
IEnumerable<IFileInfo> pluginFolders = fileProvider.GetDirectoryContents(path);

foreach (IFileInfo pluginFolder in pluginFolders)
{
return Array.Empty<string>();
if (!pluginFolder.IsDirectory)
{
continue;
}

manifestFiles.AddRange(GetNestedManifestFiles(fileProvider, $"{path}/{pluginFolder.Name}"));
}

return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories);
return manifestFiles;
}

// Helper method to get all nested package.manifest files (recursively)
private static IEnumerable<IFileInfo> GetNestedManifestFiles(IFileProvider fileProvider, string path)
{
foreach (IFileInfo file in fileProvider.GetDirectoryContents(path))
{
if (file.IsDirectory)
{
var virtualPath = WebPath.Combine(path, file.Name);

// Recursively find nested package.manifest files
foreach (IFileInfo nested in GetNestedManifestFiles(fileProvider, virtualPath))
{
yield return nested;
}
}
else if (file.Name.InvariantEquals("package.manifest") && !string.IsNullOrEmpty(file.PhysicalPath))
{
yield return file;
}
}
}
}
Loading

0 comments on commit 897cf4c

Please sign in to comment.