Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
76.67% |
23 / 30 |
CRAP | |
68.54% |
146 / 213 |
| ModuleHandler | |
0.00% |
0 / 1 |
|
76.67% |
23 / 30 |
440.63 | |
68.54% |
146 / 213 |
| __construct | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
| load | |
100.00% |
1 / 1 |
3 | |
100.00% |
7 / 7 |
|||
| loadAll | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
| reload | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| isLoaded | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| getModuleList | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| getModule | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
| setModuleList | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| addModule | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| addProfile | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| add | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
| buildModuleDependencies | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 13 |
|||
| moduleExists | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| loadAllIncludes | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
| loadInclude | |
100.00% |
1 / 1 |
5 | |
100.00% |
9 / 9 |
|||
| getHookInfo | |
100.00% |
1 / 1 |
3 | |
100.00% |
6 / 6 |
|||
| buildHookInfo | |
100.00% |
1 / 1 |
5 | |
100.00% |
9 / 9 |
|||
| getImplementations | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| writeCache | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
| resetImplementations | |
100.00% |
1 / 1 |
1 | |
100.00% |
6 / 6 |
|||
| implementsHook | |
100.00% |
1 / 1 |
4 | |
100.00% |
9 / 9 |
|||
| invoke | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
| invokeAll | |
0.00% |
0 / 1 |
5.03 | |
90.00% |
9 / 10 |
|||
| alter | |
0.00% |
0 / 1 |
182 | |
0.00% |
0 / 33 |
|||
| getImplementationInfo | |
100.00% |
1 / 1 |
6 | |
100.00% |
14 / 14 |
|||
| buildImplementationInfo | |
0.00% |
0 / 1 |
10.54 | |
73.33% |
11 / 15 |
|||
| verifyImplementations | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
| parseDependency | |
100.00% |
1 / 1 |
11 | |
100.00% |
23 / 23 |
|||
| getModuleDirectories | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
| getName | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 2 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Extension\ModuleHandler. | |
| */ | |
| namespace Drupal\Core\Extension; | |
| use Drupal\Component\Graph\Graph; | |
| use Drupal\Component\Utility\NestedArray; | |
| use Drupal\Core\Cache\CacheBackendInterface; | |
| /** | |
| * Class that manages modules in a Drupal installation. | |
| */ | |
| class ModuleHandler implements ModuleHandlerInterface { | |
| /** | |
| * List of loaded files. | |
| * | |
| * @var array | |
| * An associative array whose keys are file paths of loaded files, relative | |
| * to the application's root directory. | |
| */ | |
| protected $loadedFiles; | |
| /** | |
| * List of installed modules. | |
| * | |
| * @var \Drupal\Core\Extension\Extension[] | |
| */ | |
| protected $moduleList; | |
| /** | |
| * Boolean indicating whether modules have been loaded. | |
| * | |
| * @var bool | |
| */ | |
| protected $loaded = FALSE; | |
| /** | |
| * List of hook implementations keyed by hook name. | |
| * | |
| * @var array | |
| */ | |
| protected $implementations; | |
| /** | |
| * List of hooks where the implementations have been "verified". | |
| * | |
| * @var true[] | |
| * Associative array where keys are hook names. | |
| */ | |
| protected $verified; | |
| /** | |
| * Information returned by hook_hook_info() implementations. | |
| * | |
| * @var array | |
| */ | |
| protected $hookInfo; | |
| /** | |
| * Cache backend for storing module hook implementation information. | |
| * | |
| * @var \Drupal\Core\Cache\CacheBackendInterface | |
| */ | |
| protected $cacheBackend; | |
| /** | |
| * Whether the cache needs to be written. | |
| * | |
| * @var bool | |
| */ | |
| protected $cacheNeedsWriting = FALSE; | |
| /** | |
| * List of alter hook implementations keyed by hook name(s). | |
| * | |
| * @var array | |
| */ | |
| protected $alterFunctions; | |
| /** | |
| * The app root. | |
| * | |
| * @var string | |
| */ | |
| protected $root; | |
| /** | |
| * Constructs a ModuleHandler object. | |
| * | |
| * @param string $root | |
| * The app root. | |
| * @param array $module_list | |
| * An associative array whose keys are the names of installed modules and | |
| * whose values are Extension class parameters. This is normally the | |
| * %container.modules% parameter being set up by DrupalKernel. | |
| * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend | |
| * Cache backend for storing module hook implementation information. | |
| * | |
| * @see \Drupal\Core\DrupalKernel | |
| * @see \Drupal\Core\CoreServiceProvider | |
| */ | |
| public function __construct($root, array $module_list = array(), CacheBackendInterface $cache_backend) { | |
| $this->root = $root; | |
| $this->moduleList = array(); | |
| foreach ($module_list as $name => $module) { | |
| $this->moduleList[$name] = new Extension($this->root, $module['type'], $module['pathname'], $module['filename']); | |
| } | |
| $this->cacheBackend = $cache_backend; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function load($name) { | |
| if (isset($this->loadedFiles[$name])) { | |
| return TRUE; | |
| } | |
| if (isset($this->moduleList[$name])) { | |
| $this->moduleList[$name]->load(); | |
| $this->loadedFiles[$name] = TRUE; | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function loadAll() { | |
| if (!$this->loaded) { | |
| foreach ($this->moduleList as $name => $module) { | |
| $this->load($name); | |
| } | |
| $this->loaded = TRUE; | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function reload() { | |
| $this->loaded = FALSE; | |
| $this->loadAll(); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function isLoaded() { | |
| return $this->loaded; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getModuleList() { | |
| return $this->moduleList; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getModule($name) { | |
| if (isset($this->moduleList[$name])) { | |
| return $this->moduleList[$name]; | |
| } | |
| throw new \InvalidArgumentException(sprintf('The module %s does not exist.', $name)); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function setModuleList(array $module_list = array()) { | |
| $this->moduleList = $module_list; | |
| // Reset the implementations, so a new call triggers a reloading of the | |
| // available hooks. | |
| $this->resetImplementations(); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function addModule($name, $path) { | |
| $this->add('module', $name, $path); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function addProfile($name, $path) { | |
| $this->add('profile', $name, $path); | |
| } | |
| /** | |
| * Adds a module or profile to the list of currently active modules. | |
| * | |
| * @param string $type | |
| * The extension type; either 'module' or 'profile'. | |
| * @param string $name | |
| * The module name; e.g., 'node'. | |
| * @param string $path | |
| * The module path; e.g., 'core/modules/node'. | |
| */ | |
| protected function add($type, $name, $path) { | |
| $pathname = "$path/$name.info.yml"; | |
| $filename = file_exists($this->root . "/$path/$name.$type") ? "$name.$type" : NULL; | |
| $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename); | |
| $this->resetImplementations(); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function buildModuleDependencies(array $modules) { | |
| foreach ($modules as $module) { | |
| $graph[$module->getName()]['edges'] = array(); | |
| if (isset($module->info['dependencies']) && is_array($module->info['dependencies'])) { | |
| foreach ($module->info['dependencies'] as $dependency) { | |
| $dependency_data = static::parseDependency($dependency); | |
| $graph[$module->getName()]['edges'][$dependency_data['name']] = $dependency_data; | |
| } | |
| } | |
| } | |
| $graph_object = new Graph($graph); | |
| $graph = $graph_object->searchAndSort(); | |
| foreach ($graph as $module_name => $data) { | |
| $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array(); | |
| $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : array(); | |
| $modules[$module_name]->sort = $data['weight']; | |
| } | |
| return $modules; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function moduleExists($module) { | |
| return isset($this->moduleList[$module]); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function loadAllIncludes($type, $name = NULL) { | |
| foreach ($this->moduleList as $module => $filename) { | |
| $this->loadInclude($module, $type, $name); | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function loadInclude($module, $type, $name = NULL) { | |
| if ($type == 'install') { | |
| // Make sure the installation API is available | |
| include_once $this->root . '/core/includes/install.inc'; | |
| } | |
| $name = $name ?: $module; | |
| if (isset($this->moduleList[$module])) { | |
| $file = $this->root . '/' . $this->moduleList[$module]->getPath() . "/$name.$type"; | |
| if (is_file($file)) { | |
| require_once $file; | |
| return $file; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getHookInfo() { | |
| if (!isset($this->hookInfo)) { | |
| if ($cache = $this->cacheBackend->get('hook_info')) { | |
| $this->hookInfo = $cache->data; | |
| } | |
| else { | |
| $this->buildHookInfo(); | |
| $this->cacheBackend->set('hook_info', $this->hookInfo); | |
| } | |
| } | |
| return $this->hookInfo; | |
| } | |
| /** | |
| * Builds hook_hook_info() information. | |
| * | |
| * @see \Drupal\Core\Extension\ModuleHandler::getHookInfo() | |
| */ | |
| protected function buildHookInfo() { | |
| $this->hookInfo = array(); | |
| // Make sure that the modules are loaded before checking. | |
| $this->reload(); | |
| // $this->invokeAll() would cause an infinite recursion. | |
| foreach ($this->moduleList as $module => $filename) { | |
| $function = $module . '_hook_info'; | |
| if (function_exists($function)) { | |
| $result = $function(); | |
| if (isset($result) && is_array($result)) { | |
| $this->hookInfo = NestedArray::mergeDeep($this->hookInfo, $result); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getImplementations($hook) { | |
| $implementations = $this->getImplementationInfo($hook); | |
| return array_keys($implementations); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function writeCache() { | |
| if ($this->cacheNeedsWriting) { | |
| $this->cacheBackend->set('module_implements', $this->implementations); | |
| $this->cacheNeedsWriting = FALSE; | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function resetImplementations() { | |
| $this->implementations = NULL; | |
| $this->hookInfo = NULL; | |
| $this->alterFunctions = NULL; | |
| // We maintain a persistent cache of hook implementations in addition to the | |
| // static cache to avoid looping through every module and every hook on each | |
| // request. Benchmarks show that the benefit of this caching outweighs the | |
| // additional database hit even when using the default database caching | |
| // backend and only a small number of modules are enabled. The cost of the | |
| // $this->cacheBackend->get() is more or less constant and reduced further | |
| // when non-database caching backends are used, so there will be more | |
| // significant gains when a large number of modules are installed or hooks | |
| // invoked, since this can quickly lead to | |
| // \Drupal::moduleHandler()->implementsHook() being called several thousand | |
| // times per request. | |
| $this->cacheBackend->set('module_implements', array()); | |
| $this->cacheBackend->delete('hook_info'); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function implementsHook($module, $hook) { | |
| $function = $module . '_' . $hook; | |
| if (function_exists($function)) { | |
| return TRUE; | |
| } | |
| // If the hook implementation does not exist, check whether it lives in an | |
| // optional include file registered via hook_hook_info(). | |
| $hook_info = $this->getHookInfo(); | |
| if (isset($hook_info[$hook]['group'])) { | |
| $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); | |
| if (function_exists($function)) { | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function invoke($module, $hook, array $args = array()) { | |
| if (!$this->implementsHook($module, $hook)) { | |
| return; | |
| } | |
| $function = $module . '_' . $hook; | |
| return call_user_func_array($function, $args); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function invokeAll($hook, array $args = array()) { | |
| $return = array(); | |
| $implementations = $this->getImplementations($hook); | |
| foreach ($implementations as $module) { | |
| $function = $module . '_' . $hook; | |
| $result = call_user_func_array($function, $args); | |
| if (isset($result) && is_array($result)) { | |
| $return = NestedArray::mergeDeep($return, $result); | |
| } | |
| elseif (isset($result)) { | |
| $return[] = $result; | |
| } | |
| } | |
| return $return; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { | |
| // Most of the time, $type is passed as a string, so for performance, | |
| // normalize it to that. When passed as an array, usually the first item in | |
| // the array is a generic type, and additional items in the array are more | |
| // specific variants of it, as in the case of array('form', 'form_FORM_ID'). | |
| if (is_array($type)) { | |
| $cid = implode(',', $type); | |
| $extra_types = $type; | |
| $type = array_shift($extra_types); | |
| // Allow if statements in this function to use the faster isset() rather | |
| // than !empty() both when $type is passed as a string, or as an array | |
| // with one item. | |
| if (empty($extra_types)) { | |
| unset($extra_types); | |
| } | |
| } | |
| else { | |
| $cid = $type; | |
| } | |
| // Some alter hooks are invoked many times per page request, so store the | |
| // list of functions to call, and on subsequent calls, iterate through them | |
| // quickly. | |
| if (!isset($this->alterFunctions[$cid])) { | |
| $this->alterFunctions[$cid] = array(); | |
| $hook = $type . '_alter'; | |
| $modules = $this->getImplementations($hook); | |
| if (!isset($extra_types)) { | |
| // For the more common case of a single hook, we do not need to call | |
| // function_exists(), since $this->getImplementations() returns only | |
| // modules with implementations. | |
| foreach ($modules as $module) { | |
| $this->alterFunctions[$cid][] = $module . '_' . $hook; | |
| } | |
| } | |
| else { | |
| // For multiple hooks, we need $modules to contain every module that | |
| // implements at least one of them. | |
| $extra_modules = array(); | |
| foreach ($extra_types as $extra_type) { | |
| $extra_modules = array_merge($extra_modules, $this->getImplementations($extra_type . '_alter')); | |
| } | |
| // If any modules implement one of the extra hooks that do not implement | |
| // the primary hook, we need to add them to the $modules array in their | |
| // appropriate order. $this->getImplementations() can only return | |
| // ordered implementations of a single hook. To get the ordered | |
| // implementations of multiple hooks, we mimic the | |
| // $this->getImplementations() logic of first ordering by | |
| // $this->getModuleList(), and then calling | |
| // $this->alter('module_implements'). | |
| if (array_diff($extra_modules, $modules)) { | |
| // Merge the arrays and order by getModuleList(). | |
| $modules = array_intersect(array_keys($this->moduleList), array_merge($modules, $extra_modules)); | |
| // Since $this->getImplementations() already took care of loading the | |
| // necessary include files, we can safely pass FALSE for the array | |
| // values. | |
| $implementations = array_fill_keys($modules, FALSE); | |
| // Let modules adjust the order solely based on the primary hook. This | |
| // ensures the same module order regardless of whether this if block | |
| // runs. Calling $this->alter() recursively in this way does not | |
| // result in an infinite loop, because this call is for a single | |
| // $type, so we won't end up in this code block again. | |
| $this->alter('module_implements', $implementations, $hook); | |
| $modules = array_keys($implementations); | |
| } | |
| foreach ($modules as $module) { | |
| // Since $modules is a merged array, for any given module, we do not | |
| // know whether it has any particular implementation, so we need a | |
| // function_exists(). | |
| $function = $module . '_' . $hook; | |
| if (function_exists($function)) { | |
| $this->alterFunctions[$cid][] = $function; | |
| } | |
| foreach ($extra_types as $extra_type) { | |
| $function = $module . '_' . $extra_type . '_alter'; | |
| if (function_exists($function)) { | |
| $this->alterFunctions[$cid][] = $function; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| foreach ($this->alterFunctions[$cid] as $function) { | |
| $function($data, $context1, $context2); | |
| } | |
| } | |
| /** | |
| * Provides information about modules' implementations of a hook. | |
| * | |
| * @param string $hook | |
| * The name of the hook (e.g. "help" or "menu"). | |
| * | |
| * @return mixed[] | |
| * An array whose keys are the names of the modules which are implementing | |
| * this hook and whose values are either a string identifying a file in | |
| * which the implementation is to be found, or FALSE, if the implementation | |
| * is in the module file. | |
| */ | |
| protected function getImplementationInfo($hook) { | |
| if (!isset($this->implementations)) { | |
| $this->implementations = array(); | |
| $this->verified = array(); | |
| if ($cache = $this->cacheBackend->get('module_implements')) { | |
| $this->implementations = $cache->data; | |
| } | |
| } | |
| if (!isset($this->implementations[$hook])) { | |
| // The hook is not cached, so ensure that whether or not it has | |
| // implementations, the cache is updated at the end of the request. | |
| $this->cacheNeedsWriting = TRUE; | |
| // Discover implementations. | |
| $this->implementations[$hook] = $this->buildImplementationInfo($hook); | |
| // Implementations are always "verified" as part of the discovery. | |
| $this->verified[$hook] = TRUE; | |
| } | |
| elseif (!isset($this->verified[$hook])) { | |
| if (!$this->verifyImplementations($this->implementations[$hook], $hook)) { | |
| // One or more of the implementations did not exist and need to be | |
| // removed in the cache. | |
| $this->cacheNeedsWriting = TRUE; | |
| } | |
| $this->verified[$hook] = TRUE; | |
| } | |
| return $this->implementations[$hook]; | |
| } | |
| /** | |
| * Builds hook implementation information for a given hook name. | |
| * | |
| * @param string $hook | |
| * The name of the hook (e.g. "help" or "menu"). | |
| * | |
| * @return mixed[] | |
| * An array whose keys are the names of the modules which are implementing | |
| * this hook and whose values are either a string identifying a file in | |
| * which the implementation is to be found, or FALSE, if the implementation | |
| * is in the module file. | |
| * | |
| * @throws \RuntimeException | |
| * Exception thrown when an invalid implementation is added by | |
| * hook_module_implements_alter(). | |
| * | |
| * @see \Drupal\Core\Extension\ModuleHandler::getImplementationInfo() | |
| */ | |
| protected function buildImplementationInfo($hook) { | |
| $implementations = array(); | |
| $hook_info = $this->getHookInfo(); | |
| foreach ($this->moduleList as $module => $extension) { | |
| $include_file = isset($hook_info[$hook]['group']) && $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); | |
| // Since $this->implementsHook() may needlessly try to load the include | |
| // file again, function_exists() is used directly here. | |
| if (function_exists($module . '_' . $hook)) { | |
| $implementations[$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; | |
| } | |
| } | |
| // Allow modules to change the weight of specific implementations, but avoid | |
| // an infinite loop. | |
| if ($hook != 'module_implements_alter') { | |
| // Remember the original implementations, before they are modified with | |
| // hook_module_implements_alter(). | |
| $implementations_before = $implementations; | |
| // Verify implementations that were added or modified. | |
| $this->alter('module_implements', $implementations, $hook); | |
| // Verify new or modified implementations. | |
| foreach (array_diff_assoc($implementations, $implementations_before) as $module => $group) { | |
| // If drupal_alter('module_implements') changed or added a $group, the | |
| // respective file needs to be included. | |
| if ($group) { | |
| $this->loadInclude($module, 'inc', "$module.$group"); | |
| } | |
| // If a new implementation was added, verify that the function exists. | |
| if (!function_exists($module . '_' . $hook)) { | |
| throw new \RuntimeException("An invalid implementation {$module}_{$hook} was added by hook_module_implements_alter()"); | |
| } | |
| } | |
| } | |
| return $implementations; | |
| } | |
| /** | |
| * Verifies an array of implementations loaded from the cache, by including | |
| * the lazy-loaded $module.$group.inc, and checking function_exists(). | |
| * | |
| * @param string[] $implementations | |
| * Implementation "group" by module name. | |
| * @param string $hook | |
| * The hook name. | |
| * | |
| * @return bool | |
| * TRUE, if all implementations exist. | |
| * FALSE, if one or more implementations don't exist and need to be removed | |
| * from the cache. | |
| */ | |
| protected function verifyImplementations(&$implementations, $hook) { | |
| $all_valid = TRUE; | |
| foreach ($implementations as $module => $group) { | |
| // If this hook implementation is stored in a lazy-loaded file, include | |
| // that file first. | |
| if ($group) { | |
| $this->loadInclude($module, 'inc', "$module.$group"); | |
| } | |
| // It is possible that a module removed a hook implementation without | |
| // the implementations cache being rebuilt yet, so we check whether the | |
| // function exists on each request to avoid undefined function errors. | |
| // Since ModuleHandler::implementsHook() may needlessly try to | |
| // load the include file again, function_exists() is used directly here. | |
| if (!function_exists($module . '_' . $hook)) { | |
| // Clear out the stale implementation from the cache and force a cache | |
| // refresh to forget about no longer existing hook implementations. | |
| unset($implementations[$module]); | |
| // One of the implementations did not exist and needs to be removed in | |
| // the cache. | |
| $all_valid = FALSE; | |
| } | |
| } | |
| return $all_valid; | |
| } | |
| /** | |
| * Parses a dependency for comparison by drupal_check_incompatibility(). | |
| * | |
| * @param $dependency | |
| * A dependency string, which specifies a module dependency, and optionally | |
| * the project it comes from and versions that are supported. Supported | |
| * formats include: | |
| * - 'module' | |
| * - 'project:module' | |
| * - 'project:module (>=version, version)' | |
| * | |
| * @return | |
| * An associative array with three keys: | |
| * - 'name' includes the name of the thing to depend on (e.g. 'foo'). | |
| * - 'original_version' contains the original version string (which can be | |
| * used in the UI for reporting incompatibilities). | |
| * - 'versions' is a list of associative arrays, each containing the keys | |
| * 'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<', | |
| * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'. | |
| * Callers should pass this structure to drupal_check_incompatibility(). | |
| * | |
| * @see drupal_check_incompatibility() | |
| */ | |
| public static function parseDependency($dependency) { | |
| $value = array(); | |
| // Split out the optional project name. | |
| if (strpos($dependency, ':') !== FALSE) { | |
| list($project_name, $dependency) = explode(':', $dependency); | |
| $value['project'] = $project_name; | |
| } | |
| // We use named subpatterns and support every op that version_compare | |
| // supports. Also, op is optional and defaults to equals. | |
| $p_op = '(?<operation>!=|==|=|<|<=|>|>=|<>)?'; | |
| // Core version is always optional: 8.x-2.x and 2.x is treated the same. | |
| $p_core = '(?:' . preg_quote(\Drupal::CORE_COMPATIBILITY) . '-)?'; | |
| $p_major = '(?<major>\d+)'; | |
| // By setting the minor version to x, branches can be matched. | |
| $p_minor = '(?<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)'; | |
| $parts = explode('(', $dependency, 2); | |
| $value['name'] = trim($parts[0]); | |
| if (isset($parts[1])) { | |
| $value['original_version'] = ' (' . $parts[1]; | |
| foreach (explode(',', $parts[1]) as $version) { | |
| if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) { | |
| $op = !empty($matches['operation']) ? $matches['operation'] : '='; | |
| if ($matches['minor'] == 'x') { | |
| // Drupal considers "2.x" to mean any version that begins with | |
| // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(), | |
| // on the other hand, treats "x" as a string; so to | |
| // version_compare(), "2.x" is considered less than 2.0. This | |
| // means that >=2.x and <2.x are handled by version_compare() | |
| // as we need, but > and <= are not. | |
| if ($op == '>' || $op == '<=') { | |
| $matches['major']++; | |
| } | |
| // Equivalence can be checked by adding two restrictions. | |
| if ($op == '=' || $op == '==') { | |
| $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x'); | |
| $op = '>='; | |
| } | |
| } | |
| $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']); | |
| } | |
| } | |
| } | |
| return $value; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getModuleDirectories() { | |
| $dirs = array(); | |
| foreach ($this->getModuleList() as $name => $module) { | |
| $dirs[$name] = $this->root . '/' . $module->getPath(); | |
| } | |
| return $dirs; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getName($module) { | |
| $info = system_get_info('module', $module); | |
| return isset($info['name']) ? $info['name'] : $module; | |
| } | |
| } |