mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Implement new module sorting pattern
This commit is contained in:
parent
695e0483aa
commit
2b266276c2
28
_config/modules.yml
Normal file
28
_config/modules.yml
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
Name: coremodulesorter
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\Core\Manifest\PrioritySorter.modulesorter:
|
||||
class: SilverStripe\Core\Manifest\PrioritySorter
|
||||
properties:
|
||||
RestKey: $other_modules
|
||||
---
|
||||
Name: modules-other
|
||||
---
|
||||
SilverStripe\Core\Manifest\ModuleManifest:
|
||||
module_priority:
|
||||
- $other_modules
|
||||
---
|
||||
Name: modules-project
|
||||
Before: '*'
|
||||
---
|
||||
SilverStripe\Core\Manifest\ModuleManifest:
|
||||
module_priority:
|
||||
- $project
|
||||
---
|
||||
Name: modules-framework
|
||||
After: modules-other
|
||||
---
|
||||
SilverStripe\Core\Manifest\ModuleManifest:
|
||||
module_priority:
|
||||
- silverstripe\framework
|
@ -43,20 +43,59 @@ The manifest is created whenever you flush your SilverStripe cache by appending
|
||||
example by visiting `http://yoursite.com/?flush=1`. When your include the `flush=1` flag, the manifest class will search
|
||||
your entire project for the appropriate `.ss` files located in `template` directory and save that information for later.
|
||||
|
||||
It will each and prioritize templates in the following priority:
|
||||
|
||||
1. mysite (or other name given to site folder)
|
||||
2. module-specific themes (e.g. themes/simple_blog)
|
||||
3. themes (e.g. themes/simple)
|
||||
4. modules (e.g. blog)
|
||||
5. framework
|
||||
|
||||
<div class="warning">
|
||||
Whenever you add or remove template files, rebuild the manifest by visiting `http://yoursite.com/?flush=1`. You can
|
||||
flush the cache from any page, (.com/home?flush=1, .com/admin?flush=1, etc.). Flushing the cache can be slow, so you
|
||||
only need to do it when you're developing new templates.
|
||||
</div>
|
||||
|
||||
## Template Priority
|
||||
|
||||
The order in which templates are selected from themes can be explicitly declared
|
||||
through configuration. To specify the order you want, make a list of the module
|
||||
names under `SilverStripe\Core\Manifest\ModuleManifest.module_priority` in a
|
||||
configuration YAML file.
|
||||
|
||||
|
||||
*some-module/_config.yml*
|
||||
```yml
|
||||
SilverStripe\Core\Manifest\ModuleManifest:
|
||||
module_priority:
|
||||
- 'example/module-one'
|
||||
- 'example/module-two'
|
||||
- '$other_modules'
|
||||
- 'example/module-three'
|
||||
```
|
||||
|
||||
The placeholder `$other_modules` is used to mark where all of the modules not specified
|
||||
in the list should appear. (In alphabetical order of their containing directory names).
|
||||
|
||||
In this example, the module named `example/module-one` has the highest level of precedence,
|
||||
followed by `example/module-two`. The module `example/module-three` is guaranteed the lowest
|
||||
level of precedence.
|
||||
|
||||
### Defining a "project"
|
||||
|
||||
It is a good idea to define one of your modules as the `project`. Commonly, this is the
|
||||
`mysite/` module, but there is nothing compulsory about that module name. The "project"
|
||||
module can be specified as a variable in the `module_priorities` list, as well.
|
||||
|
||||
*some-module/_config.yml*
|
||||
```yml
|
||||
SilverStripe\Core\Manifest\ModuleManifest:
|
||||
project: 'myapp'
|
||||
module_priority:
|
||||
- '$project'
|
||||
- '$other_modules'
|
||||
```
|
||||
|
||||
### About module "names"
|
||||
|
||||
Module names are derived their local `composer.json` files using the following precedence:
|
||||
* The value of the `name` attribute in `composer.json`
|
||||
* The value of `extras.installer_name` in `composer.json`
|
||||
* The basename of the directory that contains the module
|
||||
|
||||
## Nested Layouts through `$Layout`
|
||||
|
||||
SilverStripe has basic support for nested layouts through a fixed template variable named `$Layout`. It's used for
|
||||
|
@ -50,7 +50,7 @@ you should call `->activate()` on the kernel instance you would like to unnest t
|
||||
|
||||
# Application
|
||||
|
||||
An application represents a basic excution controller for the top level application entry point.
|
||||
An application represents a basic execution controller for the top level application entry point.
|
||||
The role of the application is to:
|
||||
|
||||
- Control bootstrapping of a provided kernel instance
|
||||
|
@ -26,6 +26,7 @@ use SilverStripe\Logging\ErrorHandler;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
* Simple Kernel container
|
||||
@ -116,7 +117,7 @@ class CoreKernel implements Kernel
|
||||
$themeResourceLoader = ThemeResourceLoader::inst();
|
||||
$themeResourceLoader->addSet('$default', new ThemeManifest(
|
||||
$basePath,
|
||||
project(),
|
||||
null, // project is defined in config, and this argument is deprecated
|
||||
$manifestCacheFactory
|
||||
));
|
||||
$this->setThemeResourceLoader($themeResourceLoader);
|
||||
@ -196,8 +197,15 @@ class CoreKernel implements Kernel
|
||||
*/
|
||||
protected function bootConfigs()
|
||||
{
|
||||
global $project;
|
||||
$projectBefore = $project;
|
||||
$config = ModuleManifest::config();
|
||||
// After loading all other app manifests, include _config.php files
|
||||
$this->getModuleLoader()->getManifest()->activateConfig();
|
||||
if ($project && $project !== $projectBefore) {
|
||||
Deprecation::notice('5.0', '$project global is deprecated');
|
||||
$config->set('project', $project);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -498,10 +506,15 @@ class CoreKernel implements Kernel
|
||||
$config->setFlush(true);
|
||||
}
|
||||
}
|
||||
// tell modules to sort, now that config is available
|
||||
$this->getModuleLoader()->getManifest()->sort();
|
||||
|
||||
// Find default templates
|
||||
$defaultSet = $this->getThemeResourceLoader()->getSet('$default');
|
||||
if ($defaultSet instanceof ThemeManifest) {
|
||||
$defaultSet->setProject(
|
||||
ModuleManifest::config()->get('project')
|
||||
);
|
||||
$defaultSet->init($this->getIncludeTests(), $flush);
|
||||
}
|
||||
}
|
||||
|
@ -323,27 +323,8 @@ class ClassManifest
|
||||
*/
|
||||
public function getOwnerModule($class)
|
||||
{
|
||||
$path = realpath($this->getItemPath($class));
|
||||
if (!$path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Module $rootModule */
|
||||
$rootModule = null;
|
||||
|
||||
// Find based on loaded modules
|
||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||
foreach ($modules as $module) {
|
||||
// Leave root module as fallback
|
||||
if (empty($module->getRelativePath())) {
|
||||
$rootModule = $module;
|
||||
} elseif (stripos($path, realpath($module->getPath())) === 0) {
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to top level module
|
||||
return $rootModule;
|
||||
$path = $this->getItemPath($class);
|
||||
return ModuleLoader::inst()->getManifest()->getModuleByPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,12 +5,18 @@ namespace SilverStripe\Core\Manifest;
|
||||
use LogicException;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
/**
|
||||
* A utility class which builds a manifest of configuration items
|
||||
*/
|
||||
class ModuleManifest
|
||||
{
|
||||
use Configurable;
|
||||
|
||||
const PROJECT_KEY = '$project';
|
||||
|
||||
/**
|
||||
* The base path used when building the manifest
|
||||
*
|
||||
@ -42,7 +48,7 @@ class ModuleManifest
|
||||
*
|
||||
* @var Module[]
|
||||
*/
|
||||
protected $modules = array();
|
||||
protected $modules = [];
|
||||
|
||||
/**
|
||||
* Adds a path as a module
|
||||
@ -105,8 +111,8 @@ class ModuleManifest
|
||||
// build cache from factory
|
||||
if ($this->cacheFactory) {
|
||||
$this->cache = $this->cacheFactory->create(
|
||||
CacheInterface::class.'.modulemanifest',
|
||||
[ 'namespace' => 'modulemanifest' . ($includeTests ? '_tests' : '') ]
|
||||
CacheInterface::class . '.modulemanifest',
|
||||
['namespace' => 'modulemanifest' . ($includeTests ? '_tests' : '')]
|
||||
);
|
||||
}
|
||||
|
||||
@ -124,7 +130,10 @@ class ModuleManifest
|
||||
*/
|
||||
public function activateConfig()
|
||||
{
|
||||
foreach ($this->getModules() as $module) {
|
||||
$modules = $this->getModules();
|
||||
// Work in reverse priority, so the higher priority modules get later execution
|
||||
/** @var Module $module */
|
||||
foreach (array_reverse($modules) as $module) {
|
||||
$module->activate();
|
||||
}
|
||||
}
|
||||
@ -145,20 +154,20 @@ class ModuleManifest
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'min_depth' => 0,
|
||||
'name_regex' => '/(^|[\/\\\\])_config.php$/',
|
||||
'ignore_tests' => !$includeTests,
|
||||
'name_regex' => '/(^|[\/\\\\])_config.php$/',
|
||||
'ignore_tests' => !$includeTests,
|
||||
'file_callback' => array($this, 'addSourceConfigFile'),
|
||||
// Cannot be max_depth: 1 due to "/framework/admin/_config.php"
|
||||
'max_depth' => 2
|
||||
'max_depth' => 2
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'name_regex' => '/\.ya?ml$/',
|
||||
'ignore_tests' => !$includeTests,
|
||||
'name_regex' => '/\.ya?ml$/',
|
||||
'ignore_tests' => !$includeTests,
|
||||
'file_callback' => array($this, 'addYAMLConfigFile'),
|
||||
'max_depth' => 2
|
||||
'max_depth' => 2
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
@ -225,4 +234,66 @@ class ModuleManifest
|
||||
{
|
||||
return $this->modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort modules sorted by priority
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sort()
|
||||
{
|
||||
$order = static::config()->uninherited('module_priority');
|
||||
$project = static::config()->get('project');
|
||||
/* @var PrioritySorter $sorter */
|
||||
$sorter = Injector::inst()->createWithArgs(
|
||||
PrioritySorter::class . '.modulesorter',
|
||||
[
|
||||
$this->modules,
|
||||
$order ?: []
|
||||
]
|
||||
);
|
||||
|
||||
if ($project) {
|
||||
$sorter->setVariable(self::PROJECT_KEY, $project);
|
||||
}
|
||||
|
||||
$this->modules = $sorter->getSortedList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module that contains the given path
|
||||
*
|
||||
* @param string $path Full filesystem path to the given file
|
||||
* @return Module The module, or null if not a path in any module
|
||||
*/
|
||||
public function getModuleByPath($path)
|
||||
{
|
||||
// Ensure path exists
|
||||
$path = realpath($path);
|
||||
if (!$path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Module $rootModule */
|
||||
$rootModule = null;
|
||||
|
||||
// Find based on loaded modules
|
||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||
foreach ($modules as $module) {
|
||||
// Check if path is in module
|
||||
if (stripos($path, realpath($module->getPath())) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is the root module, keep looking in case there is a more specific module later
|
||||
if (empty($module->getRelativePath())) {
|
||||
$rootModule = $module;
|
||||
} else {
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to top level module
|
||||
return $rootModule;
|
||||
}
|
||||
}
|
||||
|
212
src/Core/Manifest/PrioritySorter.php
Normal file
212
src/Core/Manifest/PrioritySorter.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
|
||||
/**
|
||||
* Sorts an associative array of items given a list of priorities,
|
||||
* where priorities are the keys of the items in the order they are desired.
|
||||
* Allows user-defined variables, and a "rest" key to symbolise all remaining items.
|
||||
* Example:
|
||||
*
|
||||
* $myItems = [
|
||||
* 'product-one' => new Product(...),
|
||||
* 'product-two' => new Product(...),
|
||||
* 'product-three' => new Product(...),
|
||||
* 'product-four' => new Product(...),
|
||||
* ];
|
||||
*
|
||||
* $priorities = [
|
||||
* '$featured',
|
||||
* 'product-two',
|
||||
* '...rest',
|
||||
* ];
|
||||
*
|
||||
* $sorter = new PrioritySorter($items, $priorities);
|
||||
* $sorter->setVariable('$featured', 'product-three');
|
||||
* $sorter->getSortedList();
|
||||
*
|
||||
* [
|
||||
* 'product-three' => [object] Product,
|
||||
* 'product-two' => [object] Product,
|
||||
* 'product-one' => [object] Product,
|
||||
* 'product-four' => [object] Product
|
||||
* ]
|
||||
*
|
||||
*/
|
||||
class PrioritySorter
|
||||
{
|
||||
use Injectable;
|
||||
|
||||
/**
|
||||
* The key that is used to denote all remaining items that have not
|
||||
* been specified in priorities
|
||||
* @var string
|
||||
*/
|
||||
protected $restKey = '...rest';
|
||||
|
||||
/**
|
||||
* A map of variables to their values
|
||||
* @var array
|
||||
*/
|
||||
protected $variables = [];
|
||||
|
||||
/**
|
||||
* An associative array of items, whose keys can be used in the $priorities list
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* An indexed array of keys in the $items list, reflecting the desired sort
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $priorities;
|
||||
|
||||
/**
|
||||
* The keys of the $items array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $names;
|
||||
|
||||
/**
|
||||
* PrioritySorter constructor.
|
||||
* @param array $items
|
||||
* @param array $priorities
|
||||
*/
|
||||
public function __construct(array $items = [], array $priorities = [])
|
||||
{
|
||||
$this->setItems($items);
|
||||
$this->priorities = $priorities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the items and returns a new version of $this->items
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSortedList()
|
||||
{
|
||||
$this->addVariables();
|
||||
|
||||
// Find all items that don't have their order specified by the config system
|
||||
$unspecified = array_diff($this->names, $this->priorities);
|
||||
|
||||
if (!empty($unspecified)) {
|
||||
$this->includeRest($unspecified);
|
||||
}
|
||||
|
||||
$sortedList = [];
|
||||
foreach ($this->priorities as $itemName) {
|
||||
if (isset($this->items[$itemName])) {
|
||||
$sortedList[$itemName] = $this->items[$itemName];
|
||||
}
|
||||
}
|
||||
|
||||
return $sortedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priorities for the items
|
||||
*
|
||||
* @param array $priorities An array of keys used in $this->items
|
||||
* @return $this
|
||||
*/
|
||||
public function setPriorities(array $priorities)
|
||||
{
|
||||
$this->priorities = $priorities;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of all items
|
||||
*
|
||||
* @param array $items
|
||||
* @return $this
|
||||
*/
|
||||
public function setItems(array $items)
|
||||
{
|
||||
$this->items = $items;
|
||||
$this->names = array_keys($items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a variable for replacination, e.g. addVariable->('$project', 'myproject')
|
||||
*
|
||||
* @param string $name
|
||||
* @param $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setVariable($name, $value)
|
||||
{
|
||||
$this->variables[$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key used for "all other items"
|
||||
*
|
||||
* @param $key
|
||||
* @return $this
|
||||
*/
|
||||
public function setRestKey($key)
|
||||
{
|
||||
$this->restKey = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If variables are defined, interpolate their values
|
||||
*/
|
||||
protected function addVariables()
|
||||
{
|
||||
// Remove variables from the list
|
||||
$varValues = array_values($this->variables);
|
||||
$this->names = array_filter($this->names, function ($name) use ($varValues) {
|
||||
return !in_array($name, $varValues);
|
||||
});
|
||||
|
||||
// Replace variables with their values
|
||||
$this->priorities = array_map(function ($name) {
|
||||
return $this->resolveValue($name);
|
||||
}, $this->priorities);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the "rest" key exists in the order array,
|
||||
* replace it by the unspecified items
|
||||
*/
|
||||
protected function includeRest(array $list)
|
||||
{
|
||||
$otherItemsIndex = false;
|
||||
if ($this->restKey) {
|
||||
$otherItemsIndex = array_search($this->restKey, $this->priorities);
|
||||
}
|
||||
if ($otherItemsIndex !== false) {
|
||||
array_splice($this->priorities, $otherItemsIndex, 1, $list);
|
||||
} else {
|
||||
// Otherwise just jam them on the end
|
||||
$this->priorities = array_merge($this->priorities, $list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure variables get converted to their values
|
||||
*
|
||||
* @param $name
|
||||
* @return mixed
|
||||
*/
|
||||
protected function resolveValue($name)
|
||||
{
|
||||
return isset($this->variables[$name]) ? $this->variables[$name] : $name;
|
||||
}
|
||||
}
|
@ -108,13 +108,7 @@ class Deprecation
|
||||
|
||||
$callingfile = realpath($backtrace[1]['file']);
|
||||
|
||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||
foreach ($modules as $module) {
|
||||
if (strpos($callingfile, realpath($module->getPath())) === 0) {
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return ModuleLoader::inst()->getManifest()->getModuleByPath($callingfile);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,9 @@ namespace SilverStripe\Forms\HTMLEditor;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Manifest\Module;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\View\Requirements;
|
||||
use SilverStripe\View\SSViewer;
|
||||
@ -191,13 +193,23 @@ class TinyMCEConfig extends HTMLEditorConfig
|
||||
* - themes
|
||||
* - skins
|
||||
*
|
||||
* If left blank defaults to [admin dir]/tinyme
|
||||
* Supports vendor/module:path
|
||||
*
|
||||
* @config
|
||||
* @var string
|
||||
*/
|
||||
private static $base_dir = null;
|
||||
|
||||
/**
|
||||
* Extra editor.css file paths.
|
||||
*
|
||||
* Supports vendor/module:path syntax
|
||||
*
|
||||
* @config
|
||||
* @var array
|
||||
*/
|
||||
private static $editor_css = [];
|
||||
|
||||
/**
|
||||
* TinyMCE JS settings
|
||||
*
|
||||
@ -555,7 +567,7 @@ class TinyMCEConfig extends HTMLEditorConfig
|
||||
$settings['document_base_url'] = Director::absoluteBaseURL();
|
||||
|
||||
// https://www.tinymce.com/docs/api/class/tinymce.editormanager/#baseURL
|
||||
$tinyMCEBaseURL = $this->getAdminModule()->getResourceURL('thirdparty/tinymce');
|
||||
$tinyMCEBaseURL = Controller::join_links(Director::baseURL(), $this->getTinyMCEPath());
|
||||
$settings['baseURL'] = $tinyMCEBaseURL;
|
||||
|
||||
// map all plugins to absolute urls for loading
|
||||
@ -615,13 +627,15 @@ class TinyMCEConfig extends HTMLEditorConfig
|
||||
$editor = array();
|
||||
|
||||
// Add standard editor.css
|
||||
$editor[] = $this->getAdminModule()->getResourceURL('client/dist/styles/editor.css');
|
||||
foreach ($this->config()->get('editor_css') as $editorCSS) {
|
||||
$editor[] = Director::absoluteURL($this->resolvePath($editorCSS));
|
||||
}
|
||||
|
||||
// Themed editor.css
|
||||
$themes = $this->config()->get('user_themes') ?: SSViewer::get_themes();
|
||||
$themedEditor = ThemeResourceLoader::inst()->findThemedCSS('editor', $themes);
|
||||
if ($themedEditor) {
|
||||
$editor[] = Director::absoluteURL($themedEditor, Director::BASE);
|
||||
$editor[] = Director::absoluteURL($themedEditor);
|
||||
}
|
||||
|
||||
return $editor;
|
||||
@ -685,32 +699,46 @@ class TinyMCEConfig extends HTMLEditorConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getTinyMCEPath()
|
||||
{
|
||||
$configDir = static::config()->get('base_dir');
|
||||
if ($configDir) {
|
||||
return $configDir;
|
||||
}
|
||||
|
||||
if ($admin = $this->getAdminModule()) {
|
||||
return $admin->getRelativeResourcePath('thirdparty/tinymce');
|
||||
return $this->resolvePath($configDir);
|
||||
}
|
||||
|
||||
throw new Exception(sprintf(
|
||||
'If the silverstripe/admin module is not installed,
|
||||
you must set the TinyMCE path in %s.base_dir',
|
||||
'If the silverstripe/admin module is not installed you must set the TinyMCE path in %s.base_dir',
|
||||
__CLASS__
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \SilverStripe\Core\Manifest\Module
|
||||
* @return Module
|
||||
* @deprecated 4.0..5.0
|
||||
*/
|
||||
protected function getAdminModule()
|
||||
{
|
||||
Deprecation::notice('5.0', 'Set base_dir or editor_css config instead');
|
||||
return ModuleLoader::getModule('silverstripe/admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand resource path to a relative filesystem path
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function resolvePath($path)
|
||||
{
|
||||
if (preg_match('#(?<module>[^/]+/[^/]+)\s*:\s*(?<path>[^:]+)#', $path, $results)) {
|
||||
$module = ModuleLoader::getModule($results['module']);
|
||||
if ($module) {
|
||||
return $module->getRelativeResourcePath($results['path']);
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace SilverStripe\View;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Manifest\ManifestFileFinder;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
|
||||
/**
|
||||
* A class which builds a manifest of all themes (which is really just a directory called "templates")
|
||||
@ -62,7 +63,7 @@ class ThemeManifest implements ThemeList
|
||||
* @param string $project Path to application code
|
||||
* @param CacheFactory $cacheFactory Cache factory to generate backend cache with
|
||||
*/
|
||||
public function __construct($base, $project, CacheFactory $cacheFactory = null)
|
||||
public function __construct($base, $project = null, CacheFactory $cacheFactory = null)
|
||||
{
|
||||
$this->base = $base;
|
||||
$this->project = $project;
|
||||
@ -116,6 +117,9 @@ class ThemeManifest implements ThemeList
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \string[]
|
||||
*/
|
||||
public function getThemes()
|
||||
{
|
||||
return $this->themes;
|
||||
@ -137,7 +141,11 @@ class ThemeManifest implements ThemeList
|
||||
));
|
||||
|
||||
$this->themes = [];
|
||||
$finder->find($this->base);
|
||||
|
||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||
foreach ($modules as $module) {
|
||||
$finder->find($module->getPath());
|
||||
}
|
||||
|
||||
if ($this->cache) {
|
||||
$this->cache->set($this->cacheKey, $this->themes);
|
||||
@ -156,19 +164,20 @@ class ThemeManifest implements ThemeList
|
||||
if ($basename !== self::TEMPLATES_DIR) {
|
||||
return;
|
||||
}
|
||||
$dir = trim(substr(dirname($pathname), strlen($this->base)), '/\\');
|
||||
$this->themes[] = "/".$dir;
|
||||
}
|
||||
|
||||
// We only want part of the full path, so split into directories
|
||||
$parts = explode('/', $pathname);
|
||||
// Take the end (the part relative to base), except the very last directory
|
||||
$themeParts = array_slice($parts, -$depth, $depth-1);
|
||||
// Then join again
|
||||
$path = '/'.implode('/', $themeParts);
|
||||
/**
|
||||
* Sets the project
|
||||
*
|
||||
* @param string $project
|
||||
* @return $this
|
||||
*/
|
||||
public function setProject($project)
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
// If this is in the project, add to beginning of list. Else add to end.
|
||||
if ($themeParts && $themeParts[0] == $this->project) {
|
||||
array_unshift($this->themes, $path);
|
||||
} else {
|
||||
array_push($this->themes, $path);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,11 @@ class ThemeResourceLoader
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* The base path of the application
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
@ -201,7 +206,6 @@ class ThemeResourceLoader
|
||||
|
||||
$tail = array_pop($parts);
|
||||
$head = implode('/', $parts);
|
||||
|
||||
$themePaths = $this->getThemePaths($themes);
|
||||
foreach ($themePaths as $themePath) {
|
||||
// Join path
|
||||
|
@ -5,10 +5,12 @@ namespace SilverStripe\i18n\Data;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\Core\Resettable;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
* Data sources for localisation strings. I.e. yml files stored
|
||||
@ -34,43 +36,21 @@ class Sources implements Resettable
|
||||
*/
|
||||
public function getSortedModules()
|
||||
{
|
||||
// Get list of module => path pairs, and then just the names
|
||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||
$moduleNames = array_keys($modules);
|
||||
|
||||
// Remove the "project" module from the list - we'll add it back specially later if needed
|
||||
global $project;
|
||||
if (($idx = array_search($project, $moduleNames)) !== false) {
|
||||
array_splice($moduleNames, $idx, 1);
|
||||
$i18nOrder = Sources::config()->uninherited('module_priority');
|
||||
$sortedModules = [];
|
||||
if ($i18nOrder) {
|
||||
Deprecation::notice('5.0', sprintf(
|
||||
'%s.module_priority is deprecated. Use %s.module_priority instead.',
|
||||
__CLASS__,
|
||||
ModuleManifest::class
|
||||
));
|
||||
}
|
||||
|
||||
// Get the order from the config system (lowest to highest)
|
||||
$order = Sources::config()->uninherited('module_priority');
|
||||
foreach (ModuleLoader::inst()->getManifest()->getModules() as $module) {
|
||||
$sortedModules[$module->getName()] = $module->getPath();
|
||||
};
|
||||
|
||||
// Find all modules that don't have their order specified by the config system
|
||||
$unspecified = array_diff($moduleNames, $order);
|
||||
|
||||
// If the placeholder "other_modules" exists in the order array, replace it by the unspecified modules
|
||||
if (($idx = array_search('other_modules', $order)) !== false) {
|
||||
array_splice($order, $idx, 1, $unspecified);
|
||||
} else {
|
||||
// Otherwise just jam them on the front
|
||||
array_splice($order, 0, 0, $unspecified);
|
||||
}
|
||||
|
||||
// Put the project at end (highest priority)
|
||||
if (!in_array($project, $order)) {
|
||||
$order[] = $project;
|
||||
}
|
||||
|
||||
$sortedModulePaths = array();
|
||||
foreach ($order as $module) {
|
||||
if (isset($modules[$module])) {
|
||||
$sortedModulePaths[$module] = $modules[$module]->getPath();
|
||||
}
|
||||
}
|
||||
$sortedModulePaths = array_reverse($sortedModulePaths, true);
|
||||
return $sortedModulePaths;
|
||||
return $sortedModules;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
@ -35,12 +36,7 @@ function singleton($className)
|
||||
|
||||
function project()
|
||||
{
|
||||
global $project;
|
||||
// Set default project
|
||||
if (empty($project) && file_exists(BASE_PATH . '/mysite')) {
|
||||
$project = 'mysite';
|
||||
}
|
||||
return $project;
|
||||
return ModuleManifest::config()->get('project');
|
||||
}
|
||||
|
||||
/**
|
||||
|
97
tests/php/Core/Manifest/PrioritySorterTest.php
Normal file
97
tests/php/Core/Manifest/PrioritySorterTest.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Core\Manifest\PrioritySorter;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class PrioritySorterTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* @var PrioritySorter
|
||||
*/
|
||||
protected $sorter;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$modules = [
|
||||
'module/one' => 'I am module one',
|
||||
'module/two' => 'I am module two',
|
||||
'module/three' => 'I am module three',
|
||||
'module/four' => 'I am module four',
|
||||
'module/five' => 'I am module five',
|
||||
];
|
||||
$this->sorter = new PrioritySorter($modules);
|
||||
}
|
||||
|
||||
public function testModuleSortingWithNoVarsAndNoRest()
|
||||
{
|
||||
$this->sorter->setPriorities([
|
||||
'module/three',
|
||||
'module/one',
|
||||
'module/two',
|
||||
]);
|
||||
|
||||
$result = $this->sorter->getSortedList();
|
||||
$keys = array_keys($result);
|
||||
$this->assertEquals('module/three', $keys[0]);
|
||||
$this->assertEquals('module/one', $keys[1]);
|
||||
$this->assertEquals('module/two', $keys[2]);
|
||||
$this->assertEquals('module/four', $keys[3]);
|
||||
$this->assertEquals('module/five', $keys[4]);
|
||||
}
|
||||
|
||||
public function testModuleSortingWithVarsAndNoRest()
|
||||
{
|
||||
$this->sorter->setPriorities([
|
||||
'module/three',
|
||||
'$project',
|
||||
])
|
||||
->setVariable('$project', 'module/one');
|
||||
|
||||
$result = $this->sorter->getSortedList();
|
||||
$keys = array_keys($result);
|
||||
$this->assertEquals('module/three', $keys[0]);
|
||||
$this->assertEquals('module/one', $keys[1]);
|
||||
$this->assertEquals('module/two', $keys[2]);
|
||||
$this->assertEquals('module/four', $keys[3]);
|
||||
$this->assertEquals('module/five', $keys[4]);
|
||||
}
|
||||
|
||||
public function testModuleSortingWithNoVarsAndWithRest()
|
||||
{
|
||||
$this->sorter->setPriorities([
|
||||
'module/two',
|
||||
'$other_modules',
|
||||
'module/four',
|
||||
])
|
||||
->setRestKey('$other_modules');
|
||||
$result = $this->sorter->getSortedList();
|
||||
$keys = array_keys($result);
|
||||
$this->assertEquals('module/two', $keys[0]);
|
||||
$this->assertEquals('module/one', $keys[1]);
|
||||
$this->assertEquals('module/three', $keys[2]);
|
||||
$this->assertEquals('module/five', $keys[3]);
|
||||
$this->assertEquals('module/four', $keys[4]);
|
||||
}
|
||||
|
||||
public function testModuleSortingWithVarsAndWithRest()
|
||||
{
|
||||
$this->sorter->setPriorities([
|
||||
'module/two',
|
||||
'other_modules',
|
||||
'$project',
|
||||
])
|
||||
->setVariable('$project', 'module/four')
|
||||
->setRestKey('other_modules');
|
||||
|
||||
$result = $this->sorter->getSortedList();
|
||||
$keys = array_keys($result);
|
||||
$this->assertEquals('module/two', $keys[0]);
|
||||
$this->assertEquals('module/one', $keys[1]);
|
||||
$this->assertEquals('module/three', $keys[2]);
|
||||
$this->assertEquals('module/five', $keys[3]);
|
||||
$this->assertEquals('module/four', $keys[4]);
|
||||
}
|
||||
}
|
@ -2,9 +2,11 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
|
||||
/**
|
||||
* Tests for the {@link TemplateLoader} class.
|
||||
@ -35,14 +37,29 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
|
||||
// Fake project root
|
||||
$this->base = dirname(__FILE__) . '/fixtures/templatemanifest';
|
||||
ModuleManifest::config()->set('module_priority', ['$project', '$other_modules']);
|
||||
ModuleManifest::config()->set('project', 'myproject');
|
||||
|
||||
$moduleManifest = new ModuleManifest($this->base);
|
||||
$moduleManifest->init();
|
||||
$moduleManifest->sort();
|
||||
ModuleLoader::inst()->pushManifest($moduleManifest);
|
||||
|
||||
// New ThemeManifest for that root
|
||||
$this->manifest = new ThemeManifest($this->base, 'myproject');
|
||||
$this->manifest = new ThemeManifest($this->base);
|
||||
$this->manifest->setProject('myproject');
|
||||
$this->manifest->init();
|
||||
// New Loader for that root
|
||||
$this->loader = new ThemeResourceLoader($this->base);
|
||||
$this->loader->addSet('$default', $this->manifest);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
ModuleLoader::inst()->popManifest();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that 'main' and 'Layout' templates are loaded from module
|
||||
*/
|
||||
|
@ -115,7 +115,9 @@ class DeprecationTest extends SapphireTest
|
||||
|
||||
protected function callThatOriginatesFromFramework()
|
||||
{
|
||||
$this->assertEquals('silverstripe/framework', TestDeprecation::get_module()->getName());
|
||||
$module = TestDeprecation::get_module();
|
||||
$this->assertNotNull($module);
|
||||
$this->assertEquals('silverstripe/framework', $module->getName());
|
||||
$this->assertNull(Deprecation::notice('2.0', 'Deprecation test passed'));
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ class HTMLEditorConfigTest extends SapphireTest
|
||||
// Plugin specified with standard location
|
||||
$this->assertContains('plugin4', array_keys($plugins));
|
||||
$this->assertEquals(
|
||||
'/subdir/silverstripe-admin/thirdparty/tinymce/plugins/plugin4/plugin.min.js',
|
||||
'/subdir/test/thirdparty/tinymce/plugins/plugin4/plugin.min.js',
|
||||
$plugins['plugin4']
|
||||
);
|
||||
|
||||
@ -109,12 +109,15 @@ class HTMLEditorConfigTest extends SapphireTest
|
||||
|
||||
public function testPluginCompression()
|
||||
{
|
||||
// This test requires the real tiny_mce_gzip.php file
|
||||
$module = ModuleLoader::inst()->getManifest()->getModule('silverstripe/admin');
|
||||
if (!$module) {
|
||||
$this->markTestSkipped('No silverstripe/admin module loaded');
|
||||
}
|
||||
TinyMCEConfig::config()->remove('base_dir');
|
||||
TinyMCEConfig::config()->set('base_dir', 'silverstripe/admin:thirdparty/tinymce');
|
||||
Config::modify()->set(Director::class, 'alternate_base_url', 'http://mysite.com/subdir');
|
||||
|
||||
// Build new config
|
||||
$c = new TinyMCEConfig();
|
||||
$c->setTheme('modern');
|
||||
$c->setOption('language', 'es');
|
||||
|
Loading…
Reference in New Issue
Block a user