Additional Module and Theme directories (squashed commit)

This commit is contained in:
Baruch Nissenbaum
2015-10-24 20:05:21 +03:00
parent 51003bf582
commit 1584bf703b
15 changed files with 184 additions and 42 deletions

View File

@@ -6,6 +6,7 @@ using Moq;
using NUnit.Framework;
using Orchard.Caching;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions.Compilers;
using Orchard.Environment.Extensions.Loaders;
using Orchard.FileSystems.AppData;
@@ -151,8 +152,9 @@ namespace Orchard.Tests.Environment.Loaders {
IHostEnvironment hostEnvironment,
IAssemblyProbingFolder assemblyProbingFolder,
IDependenciesFolder dependenciesFolder,
IProjectFileParser projectFileParser)
: base(buildManager, virtualPathProvider, virtualPathMonitor, hostEnvironment, assemblyProbingFolder, dependenciesFolder, projectFileParser) {}
IProjectFileParser projectFileParser,
ExtensionLocations extensionLocations)
: base(buildManager, virtualPathProvider, virtualPathMonitor, hostEnvironment, assemblyProbingFolder, dependenciesFolder, projectFileParser, extensionLocations) {}
public IEnumerable<string> GetDependenciesAccessor(string projectPath) {
return GetDependencies(projectPath);

View File

@@ -15,6 +15,14 @@
<add key="webpages:Version" value="3.0.3"/>
<add key="log4net.Config" value="Config\log4net.config"/>
<add key="owin:AppStartup" value="Orchard.Owin.Startup, Orchard.Framework"/>
<!-- Optional additional comma-separated list of folders for Modules or Themes -->
<!-- Note: Themes that are not under ~/Themes/ should have web.config with appropriate access permissions. -->
<!--
<add key="Modules" value="~/Ibn_Modules,~/Customer1/Modules"/>
<add key="Themes" value="~/Themes/MoreThemes,~/Customer1/Themes" />
-->
</appSettings>
<system.web.webPages.razor>

View File

@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
namespace Orchard.Environment.Configuration {
public class ExtensionLocations : IDependency {
public string[] CoreLocations;
public string[] ModuleLocations;
public string[] ThemeLocations;
public string[] CommonLocations; // locations that should not be common and not related to the current tenant
public string[] ModuleAndThemeLocations;
public string[] ExtensionsVirtualPathPrefixes; // Modules+Themes (no core)
public ExtensionLocations() {
Init(new DefaultAppConfigurationAccessor());
}
// This optional constructor can be used to create an environment that takes AppConfigurations from IAppConfigurationAccessor instead of from the global ConfigurationManager.AppSettings
public ExtensionLocations(IAppConfigurationAccessor appConfigurationAccessor) {
Init(appConfigurationAccessor);
}
public virtual void Init(IAppConfigurationAccessor appConfigurationAccessor) {
CoreLocations = new string[] {"~/Core"};
ModuleLocations = GetConfigPaths(appConfigurationAccessor, "Modules", "~/Modules");
ThemeLocations = GetConfigPaths(appConfigurationAccessor, "Themes", "~/Themes" );
CommonLocations = GetConfigPaths(appConfigurationAccessor, "Common", "~/Media")
.Concat(ThemeLocations)
.Concat(ModuleLocations)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
ModuleAndThemeLocations = ModuleLocations
.Concat(ThemeLocations)
.Distinct(StringComparer.CurrentCultureIgnoreCase)
.ToArray();
ExtensionsVirtualPathPrefixes = ModuleAndThemeLocations
.Select(l=>l+"/")
.OrderBy(l=>l.Count(c=>c=='/'))
.Reverse()
.ToArray();
}
/// <summary>
/// Return module from path that is constructed as Location/Module/relative/path/in/module
/// Path prefixes is expected as list of Location/ (location+trailing "/")
///
/// Extension locations can contain '/' so they are matched with deeper path first
/// </summary>
/// <param name="virtualPath"></param>
/// <returns>the module - or null of not found</returns>
public static string ModuleMatch(string virtualPath, IEnumerable<string> pathPrefixes) {
foreach(string prefix in pathPrefixes) {
if(virtualPath.StartsWith(prefix)) {
int index = virtualPath.IndexOf('/', prefix.Length, virtualPath.Length - prefix.Length);
if (index <= 0)
continue;
var moduleName = virtualPath.Substring(prefix.Length, index - prefix.Length);
return (string.IsNullOrEmpty(moduleName) ? null : moduleName);
}
}
return null;
}
/// <summary>
/// Return module from path that is constructed as ExtensionLocation/Module/relative/path/in/module
///
/// Extension locations can contain '/' so they are matched with deeper path first
/// </summary>
/// <param name="virtualPath"></param>
/// <returns>the module - or null of not found</returns>
public string ExtensionsModuleMatch(string virtualPath) {
ModuleMatch(virtualPath, ExtensionsVirtualPathPrefixes);
return null;
}
/// <summary>
/// Return true if the virtual path starts with any of the prefixes
/// </summary>
public static bool PrefixMatch(string virtualPath, IEnumerable<string> pathPrefixes) {
return pathPrefixes.Any(p => virtualPath.StartsWith(p));
}
public bool ExtensionsPrefixMatch(string virtualPath) {
return PrefixMatch(virtualPath, ExtensionsVirtualPathPrefixes);
}
/// <summary>
/// Get list of comma separated paths from web.config appSettings
/// Also return the default path
/// </summary>
static string[] GetConfigPaths(IAppConfigurationAccessor appConfigurationAccessor, string key, string defaultPath) {
char[] delim = { ',' };
string configuration = appConfigurationAccessor.GetConfiguration(key) ?? "";
return configuration.Split(delim, StringSplitOptions.RemoveEmptyEntries).Concat(new string[] { defaultPath }).Select(s => s.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
private class DefaultAppConfigurationAccessor : IAppConfigurationAccessor {
public DefaultAppConfigurationAccessor() {}
public string GetConfiguration(string name) {
return ConfigurationManager.AppSettings[name];
}
}
}
}

View File

@@ -41,12 +41,14 @@ namespace Orchard.Environment.Extensions {
}
public void MonitorExtensionsWork(Action<IVolatileToken> monitor) {
var locations = _extensionManager.AvailableExtensions().Select(e => e.Location).Distinct(StringComparer.InvariantCultureIgnoreCase);
Logger.Information("Start monitoring extension files...");
// Monitor add/remove of any module/theme
Logger.Debug("Monitoring virtual path \"{0}\"", "~/Modules");
monitor(_virtualPathMonitor.WhenPathChanges("~/Modules"));
Logger.Debug("Monitoring virtual path \"{0}\"", "~/Themes");
monitor(_virtualPathMonitor.WhenPathChanges("~/Themes"));
foreach(string location in locations) {
Logger.Debug("Monitoring virtual path \"{0}\"", location);
monitor(_virtualPathMonitor.WhenPathChanges(location));
}
// Give loaders a chance to monitor any additional changes
var extensions = _extensionManager.AvailableExtensions().Where(d => DefaultExtensionTypes.IsModule(d.ExtensionType) || DefaultExtensionTypes.IsTheme(d.ExtensionType)).ToList();

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Orchard.Caching;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions.Compilers;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
@@ -13,7 +14,7 @@ using Orchard.Utility.Extensions;
namespace Orchard.Environment.Extensions.Loaders {
public class DynamicExtensionLoader : ExtensionLoaderBase {
public static readonly string[] ExtensionsVirtualPathPrefixes = { "~/Modules/", "~/Themes/" };
private readonly string[] _extensionsVirtualPathPrefixes; // { "~/Modules/", "~/Themes/" };
private readonly IBuildManager _buildManager;
private readonly IVirtualPathProvider _virtualPathProvider;
@@ -31,7 +32,8 @@ namespace Orchard.Environment.Extensions.Loaders {
IHostEnvironment hostEnvironment,
IAssemblyProbingFolder assemblyProbingFolder,
IDependenciesFolder dependenciesFolder,
IProjectFileParser projectFileParser)
IProjectFileParser projectFileParser,
ExtensionLocations extensionLocations)
: base(dependenciesFolder) {
_buildManager = buildManager;
@@ -43,6 +45,8 @@ namespace Orchard.Environment.Extensions.Loaders {
_dependenciesFolder = dependenciesFolder;
Logger = NullLogger.Instance;
_extensionsVirtualPathPrefixes = extensionLocations.ModuleAndThemeLocations;
}
public ILogger Logger { get; set; }
@@ -209,8 +213,8 @@ namespace Orchard.Environment.Extensions.Loaders {
}
private void AddDependencies(string projectPath, HashSet<string> currentSet) {
// Skip files from locations other than "~/Modules" and "~/Themes"
if (string.IsNullOrEmpty(PrefixMatch(projectPath, ExtensionsVirtualPathPrefixes))) {
// Skip files from locations other than "~/Modules" and "~/Themes" etc.
if (string.IsNullOrEmpty(PrefixMatch(projectPath, _extensionsVirtualPathPrefixes))) {
return;
}

View File

@@ -25,7 +25,8 @@ namespace Orchard.Environment.Extensions.Loaders {
if (Disabled)
return null;
if (descriptor.Location == "~/Themes") {
// Temporary - theme without own project should be under ~/themes
if (descriptor.Location.StartsWith("~/Themes",StringComparison.InvariantCultureIgnoreCase)) {
string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id,
descriptor.Id + ".csproj");
@@ -44,7 +45,7 @@ namespace Orchard.Environment.Extensions.Loaders {
return new ExtensionProbeEntry {
Descriptor = descriptor,
Loader = this,
VirtualPath = "~/Theme/" + descriptor.Id,
VirtualPath = descriptor.VirtualPath,
VirtualPathDependencies = Enumerable.Empty<string>(),
};
}

View File

@@ -12,6 +12,8 @@ namespace Orchard.Environment.Extensions.Models {
/// </summary>
public string Id { get; set; }
public string VirtualPath { get { return Location + "/" + Id; } }
/// <summary>
/// The extension type.
/// </summary>

View File

@@ -35,11 +35,18 @@ using Orchard.Mvc.ViewEngines.ThemeAwareness;
using Orchard.Services;
using Orchard.WebApi;
using Orchard.WebApi.Filters;
using System.Linq;
using System.Web.Configuration;
namespace Orchard.Environment {
public static class OrchardStarter {
public static IContainer CreateHostContainer(Action<ContainerBuilder> registrations) {
ExtensionLocations extensionLocations = new ExtensionLocations();
var builder = new ContainerBuilder();
// Application paths and parameters
builder.RegisterInstance(extensionLocations);
builder.RegisterModule(new CollectionOrderModule());
builder.RegisterModule(new LoggingModule());
builder.RegisterModule(new EventsModule());
@@ -79,7 +86,6 @@ namespace Orchard.Environment {
RegisterVolatileProvider<DefaultVirtualPathMonitor, IVirtualPathMonitor>(builder);
RegisterVolatileProvider<DefaultVirtualPathProvider, IVirtualPathProvider>(builder);
builder.RegisterType<DefaultOrchardHost>().As<IOrchardHost>().As<IEventHandler>()
.Named<IEventHandler>(typeof(IShellSettingsManagerEventHandler).Name)
.Named<IEventHandler>(typeof(IShellDescriptorManagerEventHandler).Name)
@@ -100,11 +106,11 @@ namespace Orchard.Environment {
{
builder.RegisterType<ExtensionHarvester>().As<IExtensionHarvester>().SingleInstance();
builder.RegisterType<ModuleFolders>().As<IExtensionFolders>().SingleInstance()
.WithParameter(new NamedParameter("paths", new[] { "~/Modules" }));
.WithParameter(new NamedParameter("paths", extensionLocations.ModuleLocations));
builder.RegisterType<CoreModuleFolders>().As<IExtensionFolders>().SingleInstance()
.WithParameter(new NamedParameter("paths", new[] { "~/Core" }));
.WithParameter(new NamedParameter("paths", extensionLocations.CoreLocations));
builder.RegisterType<ThemeFolders>().As<IExtensionFolders>().SingleInstance()
.WithParameter(new NamedParameter("paths", new[] { "~/Themes" }));
.WithParameter(new NamedParameter("paths", extensionLocations.ThemeLocations));
builder.RegisterType<CoreExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<ReferencedExtensionLoader>().As<IExtensionLoader>().SingleInstance();
@@ -174,7 +180,6 @@ namespace Orchard.Environment {
return container;
}
private static void RegisterVolatileProvider<TRegister, TService>(ContainerBuilder builder) where TService : IVolatileProvider {
builder.RegisterType<TRegister>()
.As<TService>()

View File

@@ -4,6 +4,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using Orchard.Environment.Configuration;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Folders;
using Orchard.Environment.Extensions.Loaders;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
@@ -15,10 +19,14 @@ namespace Orchard.FileSystems.Dependencies {
/// </summary>
public class DynamicModuleVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider {
private readonly IExtensionDependenciesManager _extensionDependenciesManager;
private string[] _extensionsVirtualPathPrefixes;
public DynamicModuleVirtualPathProvider(IExtensionDependenciesManager extensionDependenciesManager) {
public DynamicModuleVirtualPathProvider(IExtensionDependenciesManager extensionDependenciesManager, ExtensionLocations extensionLocations) {
_extensionDependenciesManager = extensionDependenciesManager;
Logger = NullLogger.Instance;
_extensionsVirtualPathPrefixes = extensionLocations.ExtensionsVirtualPathPrefixes;
}
public ILogger Logger { get; set; }
@@ -42,7 +50,7 @@ namespace Orchard.FileSystems.Dependencies {
}
private ActivatedExtensionDescriptor GetExtensionDescriptor(string virtualPath) {
var prefix = PrefixMatch(virtualPath, DynamicExtensionLoader.ExtensionsVirtualPathPrefixes);
var prefix = PrefixMatch(virtualPath, _extensionsVirtualPathPrefixes);
if (prefix == null)
return null;

View File

@@ -17,8 +17,8 @@ namespace Orchard.Localization.Services {
private readonly ISignals _signals;
const string CoreLocalizationFilePathFormat = "~/Core/App_Data/Localization/{0}/orchard.core.po";
const string ModulesLocalizationFilePathFormat = "~/Modules/{0}/App_Data/Localization/{1}/orchard.module.po";
const string ThemesLocalizationFilePathFormat = "~/Themes/{0}/App_Data/Localization/{1}/orchard.theme.po";
const string ModulesLocalizationFilePathFormat = "{0}/App_Data/Localization/{1}/orchard.module.po";
const string ThemesLocalizationFilePathFormat = "{0}/App_Data/Localization/{1}/orchard.theme.po";
const string RootLocalizationFilePathFormat = "~/App_Data/Localization/{0}/orchard.root.po";
const string TenantLocalizationFilePathFormat = "~/App_Data/Sites/{0}/Localization/{1}/orchard.po";
@@ -124,7 +124,7 @@ namespace Orchard.Localization.Services {
foreach (var module in _extensionManager.AvailableExtensions()) {
if (DefaultExtensionTypes.IsModule(module.ExtensionType)) {
string modulePath = string.Format(ModulesLocalizationFilePathFormat, module.Id, culture);
string modulePath = string.Format(ModulesLocalizationFilePathFormat, module.VirtualPath, culture);
text = _webSiteFolder.ReadFile(modulePath);
if (text != null) {
_localizationStreamParser.ParseLocalizationStream(text, translations, true);
@@ -139,7 +139,7 @@ namespace Orchard.Localization.Services {
foreach (var theme in _extensionManager.AvailableExtensions()) {
if (DefaultExtensionTypes.IsTheme(theme.ExtensionType)) {
string themePath = string.Format(ThemesLocalizationFilePathFormat, theme.Id, culture);
string themePath = string.Format(ThemesLocalizationFilePathFormat, theme.VirtualPath, culture);
text = _webSiteFolder.ReadFile(themePath);
if (text != null) {
_localizationStreamParser.ParseLocalizationStream(text, translations, true);

View File

@@ -8,6 +8,7 @@ namespace Orchard.Mvc.ViewEngines {
public class CreateModulesViewEngineParams {
public IEnumerable<string> VirtualPaths { get; set; }
public IEnumerable<string> ExtensionLocations { get; set; }
}
public interface IViewEngineProvider : ISingletonDependency {

View File

@@ -59,11 +59,8 @@ namespace Orchard.Mvc.ViewEngines.Razor {
}
public IViewEngine CreateModulesViewEngine(CreateModulesViewEngineParams parameters) {
var areaFormats = new[] {
"~/Core/{2}/Views/{1}/{0}.cshtml",
"~/Modules/{2}/Views/{1}/{0}.cshtml",
"~/Themes/{2}/Views/{1}/{0}.cshtml",
};
//TBD: It would probably be better to determined the area deterministically from the module of the controller, not by trial and error.
var areaFormats = parameters.ExtensionLocations.Select(location => location + "/{2}/Views/{1}/{0}.cshtml").ToArray();
//Logger.Debug("AreaFormats (module): \r\n\t-{0}", string.Join("\r\n\t-", areaFormats));

View File

@@ -113,6 +113,9 @@ namespace Orchard.Mvc.ViewEngines.Razor {
}
}
private string[] _commonLocations;
public string[] CommonLocations { get { return _commonLocations ?? (_commonLocations = WorkContext.Resolve<ExtensionLocations>().CommonLocations); } }
public void RegisterImageSet(string imageSet, string style = "", int size = 16) {
// hack to fake the style "alternate" for now so we don't have to change stylesheet names when this is hooked up
// todo: (heskew) deal in shapes so we have real alternates
@@ -199,16 +202,11 @@ namespace Orchard.Mvc.ViewEngines.Razor {
_tenantPrefix = WorkContext.Resolve<ShellSettings>().RequestUrlPrefix ?? "";
}
if (!String.IsNullOrEmpty(_tenantPrefix)) {
if (path.StartsWith("~/")
&& !path.StartsWith("~/Modules", StringComparison.OrdinalIgnoreCase)
&& !path.StartsWith("~/Themes", StringComparison.OrdinalIgnoreCase)
&& !path.StartsWith("~/Media", StringComparison.OrdinalIgnoreCase)
&& !path.StartsWith("~/Core", StringComparison.OrdinalIgnoreCase)) {
return base.Href("~/" + _tenantPrefix + path.Substring(String.IsNullOrWhiteSpace(_tenantPrefix) ? 2 : 1), pathParts);
}
if (!String.IsNullOrEmpty(_tenantPrefix)
&& path.StartsWith("~/")
&& !CommonLocations.Any(gpp=>path.StartsWith(gpp, StringComparison.OrdinalIgnoreCase))
) {
return base.Href("~/" + _tenantPrefix + path.Substring(2), pathParts);
}
return base.Href(path, pathParts);

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.Environment.Configuration;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
@@ -93,12 +94,17 @@ namespace Orchard.Mvc.ViewEngines.ThemeAwareness {
.Reverse() // reverse from (C <= B <= A) to (A => B => C)
.Where(fd => DefaultExtensionTypes.IsModule(fd.Extension.ExtensionType));
var allLocations = enabledModules.Concat(enabledModules)
.Select(fd => fd.Extension.Location + "/" + fd.Extension.Id)
.Distinct(StringComparer.OrdinalIgnoreCase)
var moduleVirtualPaths = enabledModules
.Select(fd => fd.Extension.VirtualPath)
.Distinct(StringComparer.OrdinalIgnoreCase) // is Distinct guaranty to keep order?
.ToList();
var moduleParams = new CreateModulesViewEngineParams { VirtualPaths = allLocations };
var moduleLocations = enabledModules
.Select(fd => fd.Extension.Location)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var moduleParams = new CreateModulesViewEngineParams { VirtualPaths = moduleVirtualPaths, ExtensionLocations = moduleLocations };
engines = engines.Concat(_viewEngineProviders.Select(vep => vep.CreateModulesViewEngine(moduleParams)));
return new ViewEngineCollectionWrapper(engines);

View File

@@ -150,6 +150,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="BackgroundHttpContextFactory.cs" />
<Compile Include="Environment\Configuration\ExtensionLocations.cs" />
<Compile Include="DisplayManagement\IPositioned.cs" />
<Compile Include="DisplayManagement\PositionWrapper.cs" />
<Compile Include="Localization\Services\ILocalizationStreamParser.cs" />