API Implement new module sorting pattern

This commit is contained in:
Aaron Carlino 2017-07-13 10:27:27 +12:00 committed by Damian Mooyman
parent 695e0483aa
commit 2b266276c2
17 changed files with 594 additions and 120 deletions

28
_config/modules.yml Normal file
View 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

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
/**

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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);
}
/**

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
/**

View File

@ -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');
}
/**

View 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]);
}
}

View File

@ -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
*/

View File

@ -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'));
}
}

View File

@ -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');