Chris@0: root = $root; Chris@0: $this->moduleList = []; Chris@0: foreach ($module_list as $name => $module) { Chris@0: $this->moduleList[$name] = new Extension($this->root, $module['type'], $module['pathname'], $module['filename']); Chris@0: } Chris@0: $this->cacheBackend = $cache_backend; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function load($name) { Chris@0: if (isset($this->loadedFiles[$name])) { Chris@0: return TRUE; Chris@0: } Chris@0: Chris@0: if (isset($this->moduleList[$name])) { Chris@0: $this->moduleList[$name]->load(); Chris@0: $this->loadedFiles[$name] = TRUE; Chris@0: return TRUE; Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function loadAll() { Chris@0: if (!$this->loaded) { Chris@0: foreach ($this->moduleList as $name => $module) { Chris@0: $this->load($name); Chris@0: } Chris@0: $this->loaded = TRUE; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function reload() { Chris@0: $this->loaded = FALSE; Chris@0: $this->loadAll(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isLoaded() { Chris@0: return $this->loaded; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getModuleList() { Chris@0: return $this->moduleList; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getModule($name) { Chris@0: if (isset($this->moduleList[$name])) { Chris@0: return $this->moduleList[$name]; Chris@0: } Chris@17: throw new UnknownExtensionException(sprintf('The module %s does not exist.', $name)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setModuleList(array $module_list = []) { Chris@0: $this->moduleList = $module_list; Chris@0: // Reset the implementations, so a new call triggers a reloading of the Chris@0: // available hooks. Chris@0: $this->resetImplementations(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addModule($name, $path) { Chris@0: $this->add('module', $name, $path); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addProfile($name, $path) { Chris@0: $this->add('profile', $name, $path); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a module or profile to the list of currently active modules. Chris@0: * Chris@0: * @param string $type Chris@0: * The extension type; either 'module' or 'profile'. Chris@0: * @param string $name Chris@0: * The module name; e.g., 'node'. Chris@0: * @param string $path Chris@0: * The module path; e.g., 'core/modules/node'. Chris@0: */ Chris@0: protected function add($type, $name, $path) { Chris@0: $pathname = "$path/$name.info.yml"; Chris@0: $filename = file_exists($this->root . "/$path/$name.$type") ? "$name.$type" : NULL; Chris@0: $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename); Chris@0: $this->resetImplementations(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function buildModuleDependencies(array $modules) { Chris@0: foreach ($modules as $module) { Chris@0: $graph[$module->getName()]['edges'] = []; Chris@0: if (isset($module->info['dependencies']) && is_array($module->info['dependencies'])) { Chris@0: foreach ($module->info['dependencies'] as $dependency) { Chris@18: $dependency_data = Dependency::createFromString($dependency); Chris@18: $graph[$module->getName()]['edges'][$dependency_data->getName()] = $dependency_data; Chris@0: } Chris@0: } Chris@0: } Chris@0: $graph_object = new Graph($graph); Chris@0: $graph = $graph_object->searchAndSort(); Chris@0: foreach ($graph as $module_name => $data) { Chris@0: $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : []; Chris@0: $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : []; Chris@0: $modules[$module_name]->sort = $data['weight']; Chris@0: } Chris@0: return $modules; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function moduleExists($module) { Chris@0: return isset($this->moduleList[$module]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function loadAllIncludes($type, $name = NULL) { Chris@0: foreach ($this->moduleList as $module => $filename) { Chris@0: $this->loadInclude($module, $type, $name); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function loadInclude($module, $type, $name = NULL) { Chris@0: if ($type == 'install') { Chris@0: // Make sure the installation API is available Chris@0: include_once $this->root . '/core/includes/install.inc'; Chris@0: } Chris@0: Chris@0: $name = $name ?: $module; Chris@0: $key = $type . ':' . $module . ':' . $name; Chris@0: if (isset($this->includeFileKeys[$key])) { Chris@0: return $this->includeFileKeys[$key]; Chris@0: } Chris@0: if (isset($this->moduleList[$module])) { Chris@0: $file = $this->root . '/' . $this->moduleList[$module]->getPath() . "/$name.$type"; Chris@0: if (is_file($file)) { Chris@0: require_once $file; Chris@0: $this->includeFileKeys[$key] = $file; Chris@0: return $file; Chris@0: } Chris@0: else { Chris@0: $this->includeFileKeys[$key] = FALSE; Chris@0: } Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getHookInfo() { Chris@0: if (!isset($this->hookInfo)) { Chris@0: if ($cache = $this->cacheBackend->get('hook_info')) { Chris@0: $this->hookInfo = $cache->data; Chris@0: } Chris@0: else { Chris@0: $this->buildHookInfo(); Chris@0: $this->cacheBackend->set('hook_info', $this->hookInfo); Chris@0: } Chris@0: } Chris@0: return $this->hookInfo; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds hook_hook_info() information. Chris@0: * Chris@0: * @see \Drupal\Core\Extension\ModuleHandler::getHookInfo() Chris@0: */ Chris@0: protected function buildHookInfo() { Chris@0: $this->hookInfo = []; Chris@0: // Make sure that the modules are loaded before checking. Chris@0: $this->reload(); Chris@0: // $this->invokeAll() would cause an infinite recursion. Chris@0: foreach ($this->moduleList as $module => $filename) { Chris@0: $function = $module . '_hook_info'; Chris@0: if (function_exists($function)) { Chris@0: $result = $function(); Chris@0: if (isset($result) && is_array($result)) { Chris@0: $this->hookInfo = NestedArray::mergeDeep($this->hookInfo, $result); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getImplementations($hook) { Chris@0: $implementations = $this->getImplementationInfo($hook); Chris@0: return array_keys($implementations); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function writeCache() { Chris@0: if ($this->cacheNeedsWriting) { Chris@0: $this->cacheBackend->set('module_implements', $this->implementations); Chris@0: $this->cacheNeedsWriting = FALSE; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function resetImplementations() { Chris@0: $this->implementations = NULL; Chris@0: $this->hookInfo = NULL; Chris@0: $this->alterFunctions = NULL; Chris@0: // We maintain a persistent cache of hook implementations in addition to the Chris@0: // static cache to avoid looping through every module and every hook on each Chris@0: // request. Benchmarks show that the benefit of this caching outweighs the Chris@0: // additional database hit even when using the default database caching Chris@0: // backend and only a small number of modules are enabled. The cost of the Chris@0: // $this->cacheBackend->get() is more or less constant and reduced further Chris@0: // when non-database caching backends are used, so there will be more Chris@0: // significant gains when a large number of modules are installed or hooks Chris@0: // invoked, since this can quickly lead to Chris@0: // \Drupal::moduleHandler()->implementsHook() being called several thousand Chris@0: // times per request. Chris@0: $this->cacheBackend->set('module_implements', []); Chris@0: $this->cacheBackend->delete('hook_info'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function implementsHook($module, $hook) { Chris@0: $function = $module . '_' . $hook; Chris@0: if (function_exists($function)) { Chris@0: return TRUE; Chris@0: } Chris@0: // If the hook implementation does not exist, check whether it lives in an Chris@0: // optional include file registered via hook_hook_info(). Chris@0: $hook_info = $this->getHookInfo(); Chris@0: if (isset($hook_info[$hook]['group'])) { Chris@0: $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); Chris@0: if (function_exists($function)) { Chris@0: return TRUE; Chris@0: } Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invoke($module, $hook, array $args = []) { Chris@0: if (!$this->implementsHook($module, $hook)) { Chris@0: return; Chris@0: } Chris@0: $function = $module . '_' . $hook; Chris@0: return call_user_func_array($function, $args); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invokeAll($hook, array $args = []) { Chris@0: $return = []; Chris@0: $implementations = $this->getImplementations($hook); Chris@0: foreach ($implementations as $module) { Chris@0: $function = $module . '_' . $hook; Chris@0: $result = call_user_func_array($function, $args); Chris@0: if (isset($result) && is_array($result)) { Chris@0: $return = NestedArray::mergeDeep($return, $result); Chris@0: } Chris@0: elseif (isset($result)) { Chris@0: $return[] = $result; Chris@0: } Chris@0: } Chris@0: Chris@0: return $return; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@14: public function invokeDeprecated($description, $module, $hook, array $args = []) { Chris@14: $result = $this->invoke($module, $hook, $args); Chris@14: $this->triggerDeprecationError($description, $hook); Chris@14: return $result; Chris@14: } Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function invokeAllDeprecated($description, $hook, array $args = []) { Chris@14: $result = $this->invokeAll($hook, $args); Chris@14: $this->triggerDeprecationError($description, $hook); Chris@14: return $result; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Triggers an E_USER_DEPRECATED error if any module implements the hook. Chris@14: * Chris@14: * @param string $description Chris@14: * Helpful text describing what to do instead of implementing this hook. Chris@14: * @param string $hook Chris@14: * The name of the hook. Chris@14: */ Chris@14: private function triggerDeprecationError($description, $hook) { Chris@14: $modules = array_keys($this->getImplementationInfo($hook)); Chris@14: if (!empty($modules)) { Chris@14: $message = 'The deprecated hook hook_' . $hook . '() is implemented in these functions: '; Chris@14: $implementations = array_map(function ($module) use ($hook) { Chris@14: return $module . '_' . $hook . '()'; Chris@14: }, $modules); Chris@14: @trigger_error($message . implode(', ', $implementations) . '. ' . $description, E_USER_DEPRECATED); Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@0: public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { Chris@0: // Most of the time, $type is passed as a string, so for performance, Chris@0: // normalize it to that. When passed as an array, usually the first item in Chris@0: // the array is a generic type, and additional items in the array are more Chris@0: // specific variants of it, as in the case of array('form', 'form_FORM_ID'). Chris@0: if (is_array($type)) { Chris@0: $cid = implode(',', $type); Chris@0: $extra_types = $type; Chris@0: $type = array_shift($extra_types); Chris@0: // Allow if statements in this function to use the faster isset() rather Chris@0: // than !empty() both when $type is passed as a string, or as an array Chris@0: // with one item. Chris@0: if (empty($extra_types)) { Chris@0: unset($extra_types); Chris@0: } Chris@0: } Chris@0: else { Chris@0: $cid = $type; Chris@0: } Chris@0: Chris@0: // Some alter hooks are invoked many times per page request, so store the Chris@0: // list of functions to call, and on subsequent calls, iterate through them Chris@0: // quickly. Chris@0: if (!isset($this->alterFunctions[$cid])) { Chris@0: $this->alterFunctions[$cid] = []; Chris@0: $hook = $type . '_alter'; Chris@0: $modules = $this->getImplementations($hook); Chris@0: if (!isset($extra_types)) { Chris@0: // For the more common case of a single hook, we do not need to call Chris@0: // function_exists(), since $this->getImplementations() returns only Chris@0: // modules with implementations. Chris@0: foreach ($modules as $module) { Chris@0: $this->alterFunctions[$cid][] = $module . '_' . $hook; Chris@0: } Chris@0: } Chris@0: else { Chris@0: // For multiple hooks, we need $modules to contain every module that Chris@0: // implements at least one of them. Chris@0: $extra_modules = []; Chris@0: foreach ($extra_types as $extra_type) { Chris@0: $extra_modules = array_merge($extra_modules, $this->getImplementations($extra_type . '_alter')); Chris@0: } Chris@0: // If any modules implement one of the extra hooks that do not implement Chris@0: // the primary hook, we need to add them to the $modules array in their Chris@0: // appropriate order. $this->getImplementations() can only return Chris@0: // ordered implementations of a single hook. To get the ordered Chris@0: // implementations of multiple hooks, we mimic the Chris@0: // $this->getImplementations() logic of first ordering by Chris@0: // $this->getModuleList(), and then calling Chris@0: // $this->alter('module_implements'). Chris@0: if (array_diff($extra_modules, $modules)) { Chris@0: // Merge the arrays and order by getModuleList(). Chris@0: $modules = array_intersect(array_keys($this->moduleList), array_merge($modules, $extra_modules)); Chris@0: // Since $this->getImplementations() already took care of loading the Chris@0: // necessary include files, we can safely pass FALSE for the array Chris@0: // values. Chris@0: $implementations = array_fill_keys($modules, FALSE); Chris@0: // Let modules adjust the order solely based on the primary hook. This Chris@0: // ensures the same module order regardless of whether this if block Chris@0: // runs. Calling $this->alter() recursively in this way does not Chris@0: // result in an infinite loop, because this call is for a single Chris@0: // $type, so we won't end up in this code block again. Chris@0: $this->alter('module_implements', $implementations, $hook); Chris@0: $modules = array_keys($implementations); Chris@0: } Chris@0: foreach ($modules as $module) { Chris@0: // Since $modules is a merged array, for any given module, we do not Chris@0: // know whether it has any particular implementation, so we need a Chris@0: // function_exists(). Chris@0: $function = $module . '_' . $hook; Chris@0: if (function_exists($function)) { Chris@0: $this->alterFunctions[$cid][] = $function; Chris@0: } Chris@0: foreach ($extra_types as $extra_type) { Chris@0: $function = $module . '_' . $extra_type . '_alter'; Chris@0: if (function_exists($function)) { Chris@0: $this->alterFunctions[$cid][] = $function; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: foreach ($this->alterFunctions[$cid] as $function) { Chris@0: $function($data, $context1, $context2); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function alterDeprecated($description, $type, &$data, &$context1 = NULL, &$context2 = NULL) { Chris@14: // Invoke the alter hook. This has the side effect of populating Chris@14: // $this->alterFunctions. Chris@14: $this->alter($type, $data, $context1, $context2); Chris@14: // The $type parameter can be an array. alter() will deal with this Chris@14: // internally, but we have to extract the proper $cid in order to discover Chris@14: // implementations. Chris@14: $cid = $type; Chris@14: if (is_array($type)) { Chris@14: $cid = implode(',', $type); Chris@14: $extra_types = $type; Chris@14: $type = array_shift($extra_types); Chris@14: } Chris@14: if (!empty($this->alterFunctions[$cid])) { Chris@14: $message = 'The deprecated alter hook hook_' . $type . '_alter() is implemented in these functions: ' . implode(', ', $this->alterFunctions[$cid]) . '.'; Chris@14: @trigger_error($message . ' ' . $description, E_USER_DEPRECATED); Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@0: * Provides information about modules' implementations of a hook. Chris@0: * Chris@0: * @param string $hook Chris@0: * The name of the hook (e.g. "help" or "menu"). Chris@0: * Chris@0: * @return mixed[] Chris@0: * An array whose keys are the names of the modules which are implementing Chris@0: * this hook and whose values are either a string identifying a file in Chris@0: * which the implementation is to be found, or FALSE, if the implementation Chris@0: * is in the module file. Chris@0: */ Chris@0: protected function getImplementationInfo($hook) { Chris@0: if (!isset($this->implementations)) { Chris@0: $this->implementations = []; Chris@0: $this->verified = []; Chris@0: if ($cache = $this->cacheBackend->get('module_implements')) { Chris@0: $this->implementations = $cache->data; Chris@0: } Chris@0: } Chris@0: if (!isset($this->implementations[$hook])) { Chris@0: // The hook is not cached, so ensure that whether or not it has Chris@0: // implementations, the cache is updated at the end of the request. Chris@0: $this->cacheNeedsWriting = TRUE; Chris@0: // Discover implementations. Chris@0: $this->implementations[$hook] = $this->buildImplementationInfo($hook); Chris@0: // Implementations are always "verified" as part of the discovery. Chris@0: $this->verified[$hook] = TRUE; Chris@0: } Chris@0: elseif (!isset($this->verified[$hook])) { Chris@0: if (!$this->verifyImplementations($this->implementations[$hook], $hook)) { Chris@0: // One or more of the implementations did not exist and need to be Chris@0: // removed in the cache. Chris@0: $this->cacheNeedsWriting = TRUE; Chris@0: } Chris@0: $this->verified[$hook] = TRUE; Chris@0: } Chris@0: return $this->implementations[$hook]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds hook implementation information for a given hook name. Chris@0: * Chris@0: * @param string $hook Chris@0: * The name of the hook (e.g. "help" or "menu"). Chris@0: * Chris@0: * @return mixed[] Chris@0: * An array whose keys are the names of the modules which are implementing Chris@0: * this hook and whose values are either a string identifying a file in Chris@0: * which the implementation is to be found, or FALSE, if the implementation Chris@0: * is in the module file. Chris@0: * Chris@0: * @throws \RuntimeException Chris@0: * Exception thrown when an invalid implementation is added by Chris@0: * hook_module_implements_alter(). Chris@0: * Chris@0: * @see \Drupal\Core\Extension\ModuleHandler::getImplementationInfo() Chris@0: */ Chris@0: protected function buildImplementationInfo($hook) { Chris@0: $implementations = []; Chris@0: $hook_info = $this->getHookInfo(); Chris@0: foreach ($this->moduleList as $module => $extension) { Chris@0: $include_file = isset($hook_info[$hook]['group']) && $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); Chris@0: // Since $this->implementsHook() may needlessly try to load the include Chris@0: // file again, function_exists() is used directly here. Chris@0: if (function_exists($module . '_' . $hook)) { Chris@0: $implementations[$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; Chris@0: } Chris@0: } Chris@0: // Allow modules to change the weight of specific implementations, but avoid Chris@0: // an infinite loop. Chris@0: if ($hook != 'module_implements_alter') { Chris@0: // Remember the original implementations, before they are modified with Chris@0: // hook_module_implements_alter(). Chris@0: $implementations_before = $implementations; Chris@0: // Verify implementations that were added or modified. Chris@0: $this->alter('module_implements', $implementations, $hook); Chris@0: // Verify new or modified implementations. Chris@0: foreach (array_diff_assoc($implementations, $implementations_before) as $module => $group) { Chris@0: // If an implementation of hook_module_implements_alter() changed or Chris@0: // added a group, the respective file needs to be included. Chris@0: if ($group) { Chris@0: $this->loadInclude($module, 'inc', "$module.$group"); Chris@0: } Chris@0: // If a new implementation was added, verify that the function exists. Chris@0: if (!function_exists($module . '_' . $hook)) { Chris@0: throw new \RuntimeException("An invalid implementation {$module}_{$hook} was added by hook_module_implements_alter()"); Chris@0: } Chris@0: } Chris@0: } Chris@0: return $implementations; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Verifies an array of implementations loaded from the cache, by including Chris@0: * the lazy-loaded $module.$group.inc, and checking function_exists(). Chris@0: * Chris@0: * @param string[] $implementations Chris@0: * Implementation "group" by module name. Chris@0: * @param string $hook Chris@0: * The hook name. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE, if all implementations exist. Chris@0: * FALSE, if one or more implementations don't exist and need to be removed Chris@0: * from the cache. Chris@0: */ Chris@0: protected function verifyImplementations(&$implementations, $hook) { Chris@0: $all_valid = TRUE; Chris@0: foreach ($implementations as $module => $group) { Chris@0: // If this hook implementation is stored in a lazy-loaded file, include Chris@0: // that file first. Chris@0: if ($group) { Chris@0: $this->loadInclude($module, 'inc', "$module.$group"); Chris@0: } Chris@0: // It is possible that a module removed a hook implementation without Chris@0: // the implementations cache being rebuilt yet, so we check whether the Chris@0: // function exists on each request to avoid undefined function errors. Chris@0: // Since ModuleHandler::implementsHook() may needlessly try to Chris@0: // load the include file again, function_exists() is used directly here. Chris@0: if (!function_exists($module . '_' . $hook)) { Chris@0: // Clear out the stale implementation from the cache and force a cache Chris@0: // refresh to forget about no longer existing hook implementations. Chris@0: unset($implementations[$module]); Chris@0: // One of the implementations did not exist and needs to be removed in Chris@0: // the cache. Chris@0: $all_valid = FALSE; Chris@0: } Chris@0: } Chris@0: return $all_valid; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Parses a dependency for comparison by drupal_check_incompatibility(). Chris@0: * Chris@18: * @param string $dependency Chris@0: * A dependency string, which specifies a module dependency, and optionally Chris@0: * the project it comes from and versions that are supported. Supported Chris@0: * formats include: Chris@0: * - 'module' Chris@0: * - 'project:module' Chris@18: * - 'project:module (>=version, version)'. Chris@0: * Chris@18: * @return array Chris@0: * An associative array with three keys: Chris@0: * - 'name' includes the name of the thing to depend on (e.g. 'foo'). Chris@0: * - 'original_version' contains the original version string (which can be Chris@0: * used in the UI for reporting incompatibilities). Chris@0: * - 'versions' is a list of associative arrays, each containing the keys Chris@0: * 'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<', Chris@0: * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'. Chris@0: * Callers should pass this structure to drupal_check_incompatibility(). Chris@0: * Chris@18: * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0. Chris@18: * Use \Drupal\Core\Extension\Dependency::createFromString() instead. Chris@18: * Chris@18: * @see https://www.drupal.org/node/2756875 Chris@0: */ Chris@0: public static function parseDependency($dependency) { Chris@18: @trigger_error(__METHOD__ . ' is deprecated. Use \Drupal\Core\Extension\Dependency::createFromString() instead. See https://www.drupal.org/node/2756875', E_USER_DEPRECATED); Chris@18: $dependency = Dependency::createFromString($dependency); Chris@18: $result = [ Chris@18: 'name' => $dependency->getName(), Chris@18: 'project' => $dependency->getProject(), Chris@18: 'original_version' => $dependency['original_version'], Chris@18: 'versions' => $dependency['versions'], Chris@18: ]; Chris@18: return array_filter($result); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getModuleDirectories() { Chris@0: $dirs = []; Chris@0: foreach ($this->getModuleList() as $name => $module) { Chris@0: $dirs[$name] = $this->root . '/' . $module->getPath(); Chris@0: } Chris@0: return $dirs; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getName($module) { Chris@17: try { Chris@17: return \Drupal::service('extension.list.module')->getName($module); Chris@17: } Chris@17: catch (UnknownExtensionException $e) { Chris@18: @trigger_error('Calling ModuleHandler::getName() with an unknown module is deprecated in Drupal 8.7.0 and support for this will be removed in Drupal 9.0.0, check that the module exists before calling this method. See https://www.drupal.org/node/3024541.', E_USER_DEPRECATED); Chris@17: return $module; Chris@17: } Chris@0: } Chris@0: Chris@0: }