diff --git a/_config/modules.yml b/_config/modules.yml new file mode 100644 index 000000000..1c5f6fa6e --- /dev/null +++ b/_config/modules.yml @@ -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 diff --git a/docs/en/02_Developer_Guides/01_Templates/05_Template_Inheritance.md b/docs/en/02_Developer_Guides/01_Templates/05_Template_Inheritance.md index e8cc58a8d..54508672d 100644 --- a/docs/en/02_Developer_Guides/01_Templates/05_Template_Inheritance.md +++ b/docs/en/02_Developer_Guides/01_Templates/05_Template_Inheritance.md @@ -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 -
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.
+## 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 diff --git a/docs/en/02_Developer_Guides/16_Execution_Pipeline/03_App_Object_and_Kernel.md b/docs/en/02_Developer_Guides/16_Execution_Pipeline/03_App_Object_and_Kernel.md index 311b0164d..baf2cb2dc 100644 --- a/docs/en/02_Developer_Guides/16_Execution_Pipeline/03_App_Object_and_Kernel.md +++ b/docs/en/02_Developer_Guides/16_Execution_Pipeline/03_App_Object_and_Kernel.md @@ -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 diff --git a/src/Core/CoreKernel.php b/src/Core/CoreKernel.php index 140d248af..5db708329 100644 --- a/src/Core/CoreKernel.php +++ b/src/Core/CoreKernel.php @@ -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); } } diff --git a/src/Core/Manifest/ClassManifest.php b/src/Core/Manifest/ClassManifest.php index 6fd04c03b..2a48d4ee5 100644 --- a/src/Core/Manifest/ClassManifest.php +++ b/src/Core/Manifest/ClassManifest.php @@ -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); } /** diff --git a/src/Core/Manifest/ModuleManifest.php b/src/Core/Manifest/ModuleManifest.php index 04abc183e..c208868a5 100644 --- a/src/Core/Manifest/ModuleManifest.php +++ b/src/Core/Manifest/ModuleManifest.php @@ -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; + } } diff --git a/src/Core/Manifest/PrioritySorter.php b/src/Core/Manifest/PrioritySorter.php new file mode 100644 index 000000000..8ac39aee6 --- /dev/null +++ b/src/Core/Manifest/PrioritySorter.php @@ -0,0 +1,212 @@ + 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; + } +} diff --git a/src/Dev/Deprecation.php b/src/Dev/Deprecation.php index 0c3264944..fbb350c7c 100644 --- a/src/Dev/Deprecation.php +++ b/src/Dev/Deprecation.php @@ -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); } /** diff --git a/src/Forms/HTMLEditor/TinyMCEConfig.php b/src/Forms/HTMLEditor/TinyMCEConfig.php index 0b8f7c118..174cfa240 100644 --- a/src/Forms/HTMLEditor/TinyMCEConfig.php +++ b/src/Forms/HTMLEditor/TinyMCEConfig.php @@ -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('#(?[^/]+/[^/]+)\s*:\s*(?[^:]+)#', $path, $results)) { + $module = ModuleLoader::getModule($results['module']); + if ($module) { + return $module->getRelativeResourcePath($results['path']); + } + } + return $path; + } } diff --git a/src/View/ThemeManifest.php b/src/View/ThemeManifest.php index a2fa87195..7c782a424 100644 --- a/src/View/ThemeManifest.php +++ b/src/View/ThemeManifest.php @@ -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; } } diff --git a/src/View/ThemeResourceLoader.php b/src/View/ThemeResourceLoader.php index fe3ecf48c..95c6f0b55 100644 --- a/src/View/ThemeResourceLoader.php +++ b/src/View/ThemeResourceLoader.php @@ -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 diff --git a/src/i18n/Data/Sources.php b/src/i18n/Data/Sources.php index ce269df13..b64d112c2 100644 --- a/src/i18n/Data/Sources.php +++ b/src/i18n/Data/Sources.php @@ -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; } /** diff --git a/src/includes/functions.php b/src/includes/functions.php index dabab3298..80d87ad95 100644 --- a/src/includes/functions.php +++ b/src/includes/functions.php @@ -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'); } /** diff --git a/tests/php/Core/Manifest/PrioritySorterTest.php b/tests/php/Core/Manifest/PrioritySorterTest.php new file mode 100644 index 000000000..8c1f2c345 --- /dev/null +++ b/tests/php/Core/Manifest/PrioritySorterTest.php @@ -0,0 +1,97 @@ + '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]); + } +} diff --git a/tests/php/Core/Manifest/ThemeResourceLoaderTest.php b/tests/php/Core/Manifest/ThemeResourceLoaderTest.php index 41ddff23c..9eb1aba38 100644 --- a/tests/php/Core/Manifest/ThemeResourceLoaderTest.php +++ b/tests/php/Core/Manifest/ThemeResourceLoaderTest.php @@ -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 */ diff --git a/tests/php/Dev/DeprecationTest.php b/tests/php/Dev/DeprecationTest.php index f6430ebc9..e10037691 100644 --- a/tests/php/Dev/DeprecationTest.php +++ b/tests/php/Dev/DeprecationTest.php @@ -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')); } } diff --git a/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php b/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php index b4ea219e4..ebdde9b06 100644 --- a/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php +++ b/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php @@ -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');