diff --git a/core/core.services.yml b/core/core.services.yml index efa4b34..2ae0391 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -476,11 +476,13 @@ services: arguments: ['@app.root', '@module_handler', '@kernel', '@router.builder'] lazy: true extension.list.module: - class: Drupal\Core\Extension\ModuleExtensionList - arguments: ['@app.root', 'module', '@cache.default', '@info_parser', '@module_handler', '@config.factory', '@extension.list.profile'] + class: Drupal\Core\Extension\Hub\ExtensionHubInterface + factory: Drupal\Core\Extension\Hub\ExtensionHub::createForModules + arguments: ['@app.root', '@info_parser', '@module_handler', '@config.factory', '@extension.list.profile', '@cache.default'] extension.list.profile: - class: Drupal\Core\Extension\ProfileExtensionList - arguments: ['@app.root', 'profile', '@cache.default', '@info_parser', '@module_handler'] + class: Drupal\Core\Extension\Hub\ExtensionHubInterface + factory: Drupal\Core\Extension\Hub\ExtensionHub::createForProfiles + arguments: ['@app.root', '@info_parser', '@module_handler', '@cache.default'] content_uninstall_validator: class: Drupal\Core\Entity\ContentUninstallValidator tags: diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 5f5a19a..76c1be7 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -206,12 +206,12 @@ function drupal_get_filename($type, $name, $filename = NULL) { $extension_list = \Drupal::service($service_id); if (isset($filename)) { // Manually add the info file path of an extension. - $extension_list->setFilename($name, $filename); + $extension_list->nameSetFilename($name, $filename); return; } else { try { - return $extension_list->getFilename($name); + return $extension_list->nameGetFilename($name); } catch (\InvalidArgumentException $e) { // Catch the exception and trigger error to maintain existing behavior. diff --git a/core/includes/install.inc b/core/includes/install.inc index 50108fa..f35d0f2 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -618,7 +618,7 @@ function drupal_install_system($install_state) { // Before having installed the system module and being able to do a module // rebuild, prime the ModuleExtensionList static cache with the module's // exact location. - \Drupal::service('extension.list.module')->setFilename('system', 'core/modules/system/system.info.yml'); + \Drupal::service('extension.list.module')->nameSetFilename('system', 'core/modules/system/system.info.yml'); // Install base system configuration. \Drupal::service('config.installer')->installDefaultConfig('core', 'core'); diff --git a/core/lib/Drupal/Core/Extension/Extension.php b/core/lib/Drupal/Core/Extension/Extension.php index 0238714..2c3cf81 100644 --- a/core/lib/Drupal/Core/Extension/Extension.php +++ b/core/lib/Drupal/Core/Extension/Extension.php @@ -45,6 +45,13 @@ class Extension implements \Serializable { protected $root; /** + * Parsed contents of the *.info.yml file. Stays null until initialized. + * + * @var array|null + */ + public $info; + + /** * Constructs a new Extension object. * * @param string $root diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php index cdee878..cdbd521 100644 --- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php +++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php @@ -18,7 +18,7 @@ * to your settings.php. * */ -class ExtensionDiscovery { +class ExtensionDiscovery implements ExtensionDiscoveryInterface { /** * Origin directory weight: Core. diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscoveryInterface.php b/core/lib/Drupal/Core/Extension/ExtensionDiscoveryInterface.php new file mode 100644 index 0000000..24af3c8 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ExtensionDiscoveryInterface.php @@ -0,0 +1,58 @@ +scan('module'); + * @endcode + * + * The following directories will be searched (in the order stated): + * - the core directory; i.e., /core + * - the installation profile directory; e.g., /core/profiles/standard + * - the legacy site-wide directory; i.e., /sites/all + * - the site-wide directory; i.e., / + * - the site-specific directory; e.g., /sites/example.com + * + * To also find test modules, add + * @code + * $settings['extension_discovery_scan_tests'] = TRUE; + * @encode + * to your settings.php. + * + * The information is returned in an associative array, keyed by the extension + * name (without .info.yml extension). Extensions found later in the search + * will take precedence over extensions found earlier - unless they are not + * compatible with the current version of Drupal core. + * + * @param string $type + * The extension type to search for. One of 'profile', 'module', 'theme', or + * 'theme_engine'. + * @param bool $include_tests + * (optional) Whether to explicitly include or exclude test extensions. By + * default, test extensions are only discovered when in a test environment. + * + * @return \Drupal\Core\Extension\Extension[] + * An associative array of Extension objects, keyed by extension name. + */ + public function scan($type, $include_tests = NULL); +} diff --git a/core/lib/Drupal/Core/Extension/ExtensionList.php b/core/lib/Drupal/Core/Extension/ExtensionList.php deleted file mode 100644 index 925d319..0000000 --- a/core/lib/Drupal/Core/Extension/ExtensionList.php +++ /dev/null @@ -1,467 +0,0 @@ -root = $root; - $this->type = $type; - $this->cache = $cache; - $this->infoParser = $info_parser; - $this->moduleHandler = $module_handler; - $this->extensionDiscovery = $this->getExtensionDiscovery(); - } - - /** - * Returns the extension discovery. - * - * @return \Drupal\Core\Extension\ExtensionDiscovery - */ - protected function getExtensionDiscovery() { - return new ExtensionDiscovery($this->root); - } - - /** - * Resets the stored extension list. - */ - public function reset() { - $this->extensions = NULL; - $this->cache->delete($this->getListCacheId()); - $this->extensionInfo = NULL; - $this->cache->delete($this->getInfoCacheId()); - $this->fileNames = NULL; - $this->cache->delete($this->getFilenameCacheId()); - $this->addedFileNames = NULL; - return $this; - } - - /** - * Returns the extension list cache ID. - * - * @return string - */ - protected function getListCacheId() { - return 'core.extension_listing.' . $this->type; - } - - /** - * Returns the extension info cache ID. - * - * @return string - */ - protected function getInfoCacheId() { - return "system.{$this->type}.info"; - } - - /** - * Returns the extension filenames cache ID. - * - * @return string - */ - protected function getFilenameCacheId() { - return "system.{$this->type}.files"; - } - - /** - * Determines if an extension exists in the filesystem. - * - * @param string $name - * The machine name of the extension. - * - * @return bool - * True if the extension exists (whether installed or not) and false if not. - */ - public function extensionExists($name) { - $extensions = $this->listExtensions(); - return isset($extensions[$name]); - } - - /** - * Returns the human readable name of the extension. - * - * @param string $machine_name - * The extension name. - * - * @return string - * The human readable name of the extension. - * - * @throws \InvalidArgumentException - * If there is no extension with the supplied machine name. - */ - public function getName($machine_name) { - return $this->getExtension($machine_name)->info['name']; - } - - /** - * Returns a single extension. - * - * @param string $name - * The extension name. - * - * @return \Drupal\Core\Extension\Extension - * - * @throws \InvalidArgumentException - * If there is no extension with the supplied name. - */ - public function getExtension($name) { - $extensions = $this->listExtensions(); - if (isset($extensions[$name])) { - return $extensions[$name]; - } - - throw new \InvalidArgumentException("The {$this->type} $name does not exist."); - } - - /** - * Returns all available extensions. - * - * @return \Drupal\Core\Extension\Extension[] - */ - public function listExtensions() { - if (isset($this->extensions)) { - return $this->extensions; - } - if ($cache = $this->cache->get($this->getListCacheId())) { - $this->extensions = $cache->data; - return $this->extensions; - } - $extensions = $this->doListExtensions(); - $this->cache->set($this->getListCacheId(), $extensions); - $this->extensions = $extensions; - return $this->extensions; - } - - /** - * Scans the available extensions. - * - * Overriding this method gives other code the chance to add additional - * extensions to this raw listing. - * - * @return \Drupal\Core\Extension\Extension[] - */ - protected function doScanExtensions() { - return $this->extensionDiscovery->scan($this->type); - } - - /** - * Build the actual list of extensions before caching it. - * - * @return \Drupal\Core\Extension\Extension[] - */ - protected function doListExtensions() { - // Find extensions. - $extensions = $this->doScanExtensions(); - - // Read info files for each extension. - foreach ($extensions as $name => $extension) { - // Look for the info file. - $extension->info = $this->infoParser->parse($extension->getPathname()); - - // Add the info file modification time, so it becomes available for - // contributed extensions to use for ordering extension lists. - $extension->info['mtime'] = $extension->getMTime(); - - // Merge in defaults and save. - $extensions[$name]->info = $extension->info + $this->defaults; - - // Invoke hook_system_info_alter() to give installed modules a chance to - // modify the data in the .info.yml files if necessary. - $this->moduleHandler->alter('system_info', $extensions[$name]->info, $extensions[$name], $this->type); - } - - return $extensions; - } - - /** - * Returns information about a specified extension. - * - * This function returns the contents of the .info.yml file for the specified - * installed extension. - * - * @param string $name - * The name of an extension whose information shall be returned. If - * $name does not exist or is not enabled, an empty array will be returned. - * - * @return mixed[] - * An associative array of extension information. - * - * @throws \InvalidArgumentException - * If there is no extension with the supplied name. - */ - public function getInfo($name) { - // Ensure that $this->extensionInfo is primed. - $this->getAllInfo(); - if (isset($this->extensionInfo[$name])) { - return $this->extensionInfo[$name]; - } - throw new \InvalidArgumentException("The {$this->type} $name does not exist."); - } - - /** - * Returns an array of information about enabled modules or themes. - * - * This function returns the contents of the .info.yml file for each installed - * extension. - * - * @return array[] - * An associative array of extension information keyed by name. If no - * records are available, an empty array is returned. - */ - public function getAllInfo() { - if (!isset($this->extensionInfo)) { - $cache_key = "system.{$this->type}.info"; - if ($cache = $this->cache->get($cache_key)) { - $info = $cache->data; - } - else { - $info = $this->recalculateInfo(); - $this->cache->set($cache_key, $info); - } - $this->extensionInfo = $info; - } - return $this->extensionInfo; - } - - /** - * Generates the information from .info.yml files for extensions of this type. - * - * The information is placed in cache with the "system.{extension_type}.info" - * key. - * - * @return array[] - * An array of arrays of .info.yml entries keyed by the extension name. - */ - protected function recalculateInfo() { - $info = []; - foreach ($this->listExtensions() as $name => $extension) { - $info[$name] = $extension->info; - } - return $info; - } - - /** - * Returns a list of extension folder names keyed by extension name. - * - * @return string[] - */ - public function getFilenames() { - if (!isset($this->fileNames)) { - $cache_id = $this->getFilenameCacheId(); - if ($cache = $this->cache->get($cache_id)) { - $file_names = $cache->data; - } - else { - $file_names = $this->recalculateFilenames(); - $this->cache->set($cache_id, $file_names); - } - $this->fileNames = $file_names; - } - return $this->fileNames; - } - - /** - * Generates a sorted list of .info.yml file locations for all extensions. - * - * The information is placed in cache with the "system.{extension_type}.files" - * key. - * - * @return string[] - * An array of .info.yml file locations keyed by the extension name. - */ - protected function recalculateFilenames() { - $file_names = []; - $extensions = $this->listExtensions(); - ksort($extensions); - foreach ($extensions as $name => $extension) { - $file_names[$name] = $extension->getPathname(); - } - return $file_names; - } - - /** - * Sets the filename for an extension. - * - * This method is used in the Drupal bootstrapping phase, when the extension - * system is not fully initialized, to manually set locations of modules and - * profiles needed to complete bootstrapping. - * - * It is not recommended to call this method except in those rare cases. - * - * @param string $extension_name - * The name of the extension for which the filename is requested. - * @param string $filename - * The filename of the extension which is to be set explicitly rather - * than by consulting the dynamic extension listing. - */ - public function setFilename($extension_name, $filename) { - $this->addedFileNames[$extension_name] = $filename; - } - - /** - * Gets the filename for a system resource. - * - * The filename, whether provided, cached, or retrieved from the database, is - * only returned if the file exists. - * - * This function plays a key role in allowing Drupal's extensions (modules, - * themes, profiles, theme_engines, etc.) to be located in different places - * depending on a site's configuration. For example, a module 'foo' may - * legally be located in any of these three places: - * - * core/modules/foo/foo.info.yml - * modules/foo/foo.info.yml - * sites/all/modules/foo/foo.info.yml - * sites/example.com/modules/foo/foo.info.yml - * - * while a theme 'bar' may be located in any of similar places: - * - * core/themes/bar/bar.info.yml - * themes/bar/bar.info.yml - * sites/all/themes/bar/bar.info.yml - * sites/example.com/themes/bar/bar.info.yml - * - * Calling ExtensionList::getFilename('foo') will give you one of the above, - * depending on where the extension is located and what type it is. - * - * @param string $extension_name - * The name of the extension for which the filename is requested. - * - * @return string - * The filename of the requested extension's .info.yml file. - * - * @throws \InvalidArgumentException - * If there is no extension with the supplied name. - */ - public function getFilename($extension_name) { - // Ensure that $this->fileNames is primed. - $this->getFilenames(); - if (isset($this->addedFileNames[$extension_name])) { - return $this->addedFileNames[$extension_name]; - } - elseif (isset($this->fileNames[$extension_name])) { - return $this->fileNames[$extension_name]; - } - throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist."); - } - - - /** - * Gets the path to an extension of a specific type (module, theme, etc.). - * - * @param string $extension_name - * The name of the extension for which the path is requested. - * - * @return string - * The drupal-root-relative path to the specified extension. - * - * @throws \InvalidArgumentException - * If there is no extension with the supplied name. - */ - public function getPath($extension_name) { - return dirname($this->getFilename($extension_name)); - } - -} diff --git a/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameList.php b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameList.php new file mode 100644 index 0000000..1d537ca --- /dev/null +++ b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameList.php @@ -0,0 +1,40 @@ +extensionList = $extension_list; + } + + /** + * {@inheritdoc} + */ + public function reset() { + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFilenames() { + $file_names = []; + $extensions = $this->extensionList->listExtensions(); + ksort($extensions); + foreach ($extensions as $name => $extension) { + $file_names[$name] = $extension->getPathname(); + } + return $file_names; + } +} diff --git a/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListBuffer.php b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListBuffer.php new file mode 100644 index 0000000..ab5887b --- /dev/null +++ b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListBuffer.php @@ -0,0 +1,41 @@ +decorated = $decorated; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->fileNames = NULL; + $this->decorated->reset(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFilenames() { + return $this->fileNames !== NULL + ? $this->fileNames + : $this->fileNames = $this->decorated->getFilenames(); + } +} diff --git a/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListCache.php b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListCache.php new file mode 100644 index 0000000..b1da4c3 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListCache.php @@ -0,0 +1,59 @@ +decorated = $decorated; + $this->cache = $cache; + $this->cacheId = $cache_id; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->cache->delete($this->cacheId); + $this->decorated->reset(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFilenames() { + if ($cache = $this->cache->get($this->cacheId)) { + return $cache->data; + } + // Resolve cache miss. + $file_names = $this->decorated->getFilenames(); + $this->cache->set($this->cacheId, $file_names); + return $file_names; + } +} diff --git a/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListInterface.php b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListInterface.php new file mode 100644 index 0000000..b2ac3a4 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/FilenameList/ExtensionFilenameListInterface.php @@ -0,0 +1,21 @@ +type = $type; + $this->source = $source; + $this->infoList = $info_list; + $this->fileList = $file_list; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->source->reset(); + $this->infoList->reset(); + $this->fileList->reset(); + $this->addedFileNames = NULL; + return $this; + } + + /** + * {@inheritdoc} + */ + public function extensionExists($name) { + $extensions = $this->listExtensions(); + return isset($extensions[$name]); + } + + /** + * {@inheritdoc} + */ + public function nameGetLabel($machine_name) { + return $this->nameGetExtension($machine_name)->info['name']; + } + + /** + * {@inheritdoc} + */ + public function nameGetExtension($name) { + $extensions = $this->listExtensions(); + if (isset($extensions[$name])) { + return $extensions[$name]; + } + + throw new \InvalidArgumentException("The {$this->type} $name does not exist."); + } + + /** + * {@inheritdoc} + */ + public function listExtensions() { + return $this->source->listExtensions(); + } + + /** + * {@inheritdoc} + */ + public function nameGetInfo($extension_name) { + // Ensure that $this->extensionInfo is primed. + $allInfo = $this->getAllInfo(); + if (isset($allInfo[$extension_name])) { + return $allInfo[$extension_name]; + } + throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist."); + } + + /** + * {@inheritdoc} + */ + public function getAllInfo() { + return $this->infoList->getAllInfo(); + } + + /** + * {@inheritdoc} + */ + public function getFilenames() { + return $this->fileList->getFilenames(); + } + + /** + * {@inheritdoc} + */ + public function nameSetFilename($extension_name, $filename) { + $this->addedFileNames[$extension_name] = $filename; + } + + /** + * {@inheritdoc} + */ + public function nameGetFilename($extension_name) { + if (isset($this->addedFileNames[$extension_name])) { + return $this->addedFileNames[$extension_name]; + } + $filenames = $this->getFilenames(); + if (isset($filenames[$extension_name])) { + return $filenames[$extension_name]; + } + throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist."); + } + + /** + * {@inheritdoc} + */ + public function nameGetPath($extension_name) { + return dirname($this->nameGetFilename($extension_name)); + } +} diff --git a/core/lib/Drupal/Core/Extension/Hub/ExtensionHubInterface.php b/core/lib/Drupal/Core/Extension/Hub/ExtensionHubInterface.php new file mode 100644 index 0000000..5001aac --- /dev/null +++ b/core/lib/Drupal/Core/Extension/Hub/ExtensionHubInterface.php @@ -0,0 +1,144 @@ +extensionList = $extension_list; + } + + /** + * {@inheritdoc} + */ + public function reset() { + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAllInfo() { + $info = []; + foreach ($this->extensionList->listExtensions() as $name => $extension) { + $info[$name] = $extension->info; + } + return $info; + } +} diff --git a/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListBuffer.php b/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListBuffer.php new file mode 100644 index 0000000..27245d3 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListBuffer.php @@ -0,0 +1,41 @@ +decorated = $decorated; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->extensionInfo = NULL; + $this->decorated->reset(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAllInfo() { + return $this->extensionInfo !== NULL + ? $this->extensionInfo + : $this->extensionInfo = $this->decorated->getAllInfo(); + } +} diff --git a/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListCache.php b/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListCache.php new file mode 100644 index 0000000..d8458c6 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListCache.php @@ -0,0 +1,56 @@ +decorated = $decorated; + $this->cache = $cache; + $this->cache_id = $cache_id; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->cache->delete($this->cache_id); + $this->decorated->reset(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAllInfo() { + if ($cache = $this->cache->get($this->cache_id)) { + return $cache->data; + } + // Resolve cache miss. + $info = $this->decorated->getAllInfo(); + $this->cache->set($this->cache_id, $info); + return $info; + } +} diff --git a/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListInterface.php b/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListInterface.php new file mode 100644 index 0000000..0c57d93 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/InfoList/ExtensionInfoListInterface.php @@ -0,0 +1,26 @@ +info. + */ + public function __construct( + $type, + InfoParserInterface $info_parser, + ModuleHandlerInterface $module_handler, + ExtensionDiscovery $extension_discovery, + array $info_defaults + ) { + $this->type = $type; + $this->infoParser = $info_parser; + $this->moduleHandler = $module_handler; + $this->extensionDiscovery = $extension_discovery; + $this->defaults = $info_defaults; + } + + /** + * {@inheritdoc} + */ + public function reset() { + // Nothing to do. + } + + /** + * Scans the available extensions. + * + * Overriding this method gives other code the chance to add additional + * extensions to this raw listing. + * + * @return \Drupal\Core\Extension\Extension[] + */ + protected function doScanExtensions() { + return $this->extensionDiscovery->scan($this->type); + } + + /** + * Returns all available extensions. + * + * @return \Drupal\Core\Extension\Extension[] + * + * @throws \Drupal\Core\Extension\InfoParserException + * If one of the .info.yml is broken or incomplete. + */ + public function listExtensions() { + // Find extensions. + $extensions = $this->doScanExtensions(); + + // Read info files for each extension. + foreach ($extensions as $name => $extension) { + // @todo Clone the extension object, to prevent side effects? + + // Look for the info file. + $extension->info = $this->infoParser->parse($extension->getPathname()); + + // Add the info file modification time, so it becomes available for + // contributed extensions to use for ordering extension lists. + /** @noinspection PhpUndefinedMethodInspection */ + $extension->info['mtime'] = $extension->getMTime(); + + // Merge in defaults and save. + $extension->info += $this->defaults; + + // Invoke hook_system_info_alter() to give installed modules a chance to + // modify the data in the .info.yml files if necessary. + $this->moduleHandler->alter('system_info', $extensions[$name]->info, $extensions[$name], $this->type); + } + + return $extensions; + } +} diff --git a/core/lib/Drupal/Core/Extension/List_/ExtensionListBuffer.php b/core/lib/Drupal/Core/Extension/List_/ExtensionListBuffer.php new file mode 100644 index 0000000..1132ee9 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/List_/ExtensionListBuffer.php @@ -0,0 +1,43 @@ +decorated = $decorated; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->decorated->reset(); + $this->extensions = NULL; + return $this; + } + + /** + * Returns all available extensions, with $extension->info filled in. + * + * @return \Drupal\Core\Extension\Extension[] + */ + public function listExtensions() { + return NULL !== $this->extensions + ? $this->extensions + : $this->extensions = $this->decorated->listExtensions(); + } +} diff --git a/core/lib/Drupal/Core/Extension/List_/ExtensionListCache.php b/core/lib/Drupal/Core/Extension/List_/ExtensionListCache.php new file mode 100644 index 0000000..7e10e9c --- /dev/null +++ b/core/lib/Drupal/Core/Extension/List_/ExtensionListCache.php @@ -0,0 +1,58 @@ +cache = $cache; + $this->decorated = $decorated; + $this->cache_id = $cache_id; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->cache->delete($this->cache_id); + return $this; + } + + /** + * {@inheritdoc} + */ + public function listExtensions() { + if ($cache = $this->cache->get($this->cache_id)) { + return $cache->data; + } + $extensions = $this->decorated->listExtensions(); + $this->cache->set($this->cache_id, $extensions); + return $extensions; + } +} diff --git a/core/lib/Drupal/Core/Extension/List_/ExtensionListInterface.php b/core/lib/Drupal/Core/Extension/List_/ExtensionListInterface.php new file mode 100644 index 0000000..df866c1 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/List_/ExtensionListInterface.php @@ -0,0 +1,21 @@ +info filled in. + * + * @return \Drupal\Core\Extension\Extension[] + */ + public function listExtensions(); + +} diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/List_/ModuleDiscoveryExtensionList.php similarity index 63% rename from core/lib/Drupal/Core/Extension/ModuleExtensionList.php rename to core/lib/Drupal/Core/Extension/List_/ModuleDiscoveryExtensionList.php index 4becd70..b37ce36 100644 --- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php +++ b/core/lib/Drupal/Core/Extension/List_/ModuleDiscoveryExtensionList.php @@ -1,63 +1,81 @@ [], - 'description' => '', - 'package' => 'Other', - 'version' => NULL, - 'php' => DRUPAL_MINIMUM_PHP, - ]; + private $configFactory; /** - * The config factory. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface + * @var \Drupal\Core\Extension\List_\ExtensionListInterface */ - protected $configFactory; + private $profileList; /** - * The profile list needed by this module list. + * @param \Drupal\Core\Extension\InfoParserInterface $info_parser + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * @param \Drupal\Core\Extension\List_\ExtensionListInterface $profile_list * - * @var \Drupal\Core\Extension\ExtensionList + * @return \Drupal\Core\Extension\List_\ModuleDiscoveryExtensionList */ - protected $profileList; + static function create( + InfoParserInterface $info_parser, + ModuleHandlerInterface $module_handler, + ExtensionDiscovery $extension_discovery, + ConfigFactoryInterface $config_factory, + ExtensionListInterface $profile_list + ) { + return new self( + 'module', + $info_parser, + $module_handler, + $extension_discovery, + [ + 'dependencies' => [], + 'description' => '', + 'package' => 'Other', + 'version' => NULL, + 'php' => DRUPAL_MINIMUM_PHP, + ], + $config_factory, + $profile_list); + } /** - * Constructs a new ModuleExtensionList instance. - * - * @param string $root - * The app root. * @param string $type - * The extension type. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache - * The cache. * @param \Drupal\Core\Extension\InfoParserInterface $info_parser - * The info parser. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler. + * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery + * @param array $info_defaults * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The config factory. - * @param \Drupal\Core\Extension\ExtensionList $profile_list - * The site profile listing. + * @param \Drupal\Core\Extension\List_\ExtensionListInterface $profile_list */ - public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, ExtensionList $profile_list) { - parent::__construct($root, $type, $cache, $info_parser, $module_handler); + function __construct( + $type, + InfoParserInterface $info_parser, + ModuleHandlerInterface $module_handler, + ExtensionDiscovery $extension_discovery, + array $info_defaults, + ConfigFactoryInterface $config_factory, + ExtensionListInterface $profile_list + ) { + parent::__construct($type, $info_parser, $module_handler, $extension_discovery, $info_defaults); $this->configFactory = $config_factory; $this->profileList = $profile_list; @@ -85,7 +103,7 @@ protected function doScanExtensions() { /** * {@inheritdoc} */ - protected function doListExtensions() { + public function listExtensions() { // Find installation profiles. This needs to happen before performing a // module scan as the module scan needs to know what the active profile is. $profiles = $this->profileList->listExtensions(); @@ -99,7 +117,7 @@ protected function doListExtensions() { } // Find modules. - $extensions = parent::doListExtensions(); + $extensions = parent::listExtensions(); // It is possible that a module was marked as required by // hook_system_info_alter() and modules that it depends on are not required. foreach ($extensions as $extension) { @@ -128,8 +146,11 @@ protected function doListExtensions() { // Add status, weight, and schema version. $installed_modules = $this->configFactory->get('core.extension')->get('module') ?: []; foreach ($extensions as $name => $module) { + /** @noinspection PhpUndefinedFieldInspection */ $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0; + /** @noinspection PhpUndefinedFieldInspection */ $module->status = (int) isset($installed_modules[$name]); + /** @noinspection PhpUndefinedFieldInspection */ $module->schema_version = SCHEMA_UNINSTALLED; } $extensions = $this->moduleHandler->buildModuleDependencies($extensions); @@ -151,6 +172,7 @@ protected function ensureRequiredDependencies(Extension $module, array $modules $dependency_name = ModuleHandler::parseDependency($dependency)['name']; if (!isset($modules[$dependency_name]->info['required'])) { $modules[$dependency_name]->info['required'] = TRUE; + // @todo Use an injected (lazy/proxy) translation service. $modules[$dependency_name]->info['explanation'] = $this->t('Dependency of required module @module', array('@module' => $module->info['name'])); // Ensure any dependencies it has are required. $this->ensureRequiredDependencies($modules[$dependency_name], $modules); diff --git a/core/lib/Drupal/Core/Extension/List_/ProfileDiscoveryExtensionList.php b/core/lib/Drupal/Core/Extension/List_/ProfileDiscoveryExtensionList.php new file mode 100644 index 0000000..06d903d --- /dev/null +++ b/core/lib/Drupal/Core/Extension/List_/ProfileDiscoveryExtensionList.php @@ -0,0 +1,37 @@ + [], + 'description' => '', + 'package' => 'Other', + 'version' => NULL, + 'php' => DRUPAL_MINIMUM_PHP, + ]); + } + +} diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index c41a650..5316166 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -717,7 +717,7 @@ public function getModuleDirectories() { * {@inheritdoc} */ public function getName($module) { - return \Drupal::service('extension.list.module')->getName($module); + return \Drupal::service('extension.list.module')->nameGetLabel($module); } } diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index 1cc2d35..b04a22c 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -308,7 +308,7 @@ public function getModuleDirectories(); * in if no matching module is found. * * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. - * Use \Drupal::service('extension.list.module')->getName() instead. + * Use \Drupal::service('extension.list.module')->nameGetLabel() instead. */ public function getName($module); diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index 73064ef..e2ba5ab 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -166,7 +166,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) { $module_filenames[$name] = $current_module_filenames[$name]; } else { - $module_path = \Drupal::service('extension.list.module')->getPath($name); + $module_path = \Drupal::service('extension.list.module')->nameGetPath($name); $pathname = "$module_path/$name.info.yml"; $filename = file_exists($module_path . "/$name.module") ? "$name.module" : NULL; $module_filenames[$name] = new Extension($this->root, 'module', $pathname, $filename); diff --git a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php deleted file mode 100644 index 256f416..0000000 --- a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php +++ /dev/null @@ -1,21 +0,0 @@ - [], - 'description' => '', - 'package' => 'Other', - 'version' => NULL, - 'php' => DRUPAL_MINIMUM_PHP, - ]; - -} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 436321d..b286e37 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -916,7 +916,7 @@ function system_check_directory($form_element, FormStateInterface $form_state) { function system_get_info($type, $name = NULL) { if ($type == 'module') { if (isset($name)) { - return \Drupal::service('extension.list.module')->getInfo($name); + return \Drupal::service('extension.list.module')->nameGetInfo($name); } else { return \Drupal::service('extension.list.module')->getAllInfo(); diff --git a/core/tests/Drupal/Tests/Core/Extension/ExtensionHubTest.php b/core/tests/Drupal/Tests/Core/Extension/ExtensionHubTest.php new file mode 100644 index 0000000..198996a --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Extension/ExtensionHubTest.php @@ -0,0 +1,237 @@ +setupEmptyTestExtensionHub(); + + $test_extension_hub->nameGetLabel('test_name'); + } + + /** + * @covers ::nameGetLabel + */ + public function testGetName() { + $test_extension_hub = $this->setupTestExtensionHub(); + + static::assertEquals('test name', $test_extension_hub->nameGetLabel('test_name')); + } + + /** + * @covers ::nameGetExtension + * @expectedException \InvalidArgumentException + */ + public function testGetExtensionWithNonExistingExtension() { + $test_extension_hub = $this->setupEmptyTestExtensionHub(); + + $test_extension_hub->nameGetExtension('test_name'); + } + + /** + * @covers ::nameGetExtension + */ + public function testGetExtension() { + $test_extension_hub = $this->setupTestExtensionHub(); + + $extension = $test_extension_hub->nameGetExtension('test_name'); + static::assertInstanceOf(Extension::class, $extension); + static::assertEquals('test_name', $extension->getName()); + } + + /** + * @covers ::listExtensions + */ + public function testListExtensions() { + $test_extension_list = $this->setupTestExtensionHub(); + + $extensions = $test_extension_list->listExtensions(); + static::assertCount(1, $extensions); + static::assertEquals('test_name', $extensions['test_name']->getName()); + } + + /** + * @covers ::nameGetInfo + * @covers ::getAllInfo + */ + public function testGetInfo() { + $test_extension_list = $this->setupTestExtensionHub(); + + $info = $test_extension_list->nameGetInfo('test_name'); + static::assertEquals([ + 'type' => 'test_extension', + 'core' => '8.x', + 'name' => 'test name', + 'mtime' => 123456789, + ], $info); + } + + /** + * @covers ::getAllInfo + */ + public function testGetAllInfo() { + $test_extension_list = $this->setupTestExtensionHub(); + + $infos = $test_extension_list->getAllInfo(); + static::assertEquals(['test_name' => [ + 'type' => 'test_extension', + 'core' => '8.x', + 'name' => 'test name', + 'mtime' => 123456789, + ]], $infos); + } + + /** + * @covers ::getFilenames + */ + public function testGetFilenames() { + $test_extension_list = $this->setupTestExtensionHub(); + + $filenames = $test_extension_list->getFilenames(); + static::assertEquals([ + 'test_name' => 'vfs://drupal_root/example/test_name/test_name.info.yml', + ], $filenames); + } + + /** + * @covers ::nameGetFilename + */ + public function testGetFilename() { + $test_extension_list = $this->setupTestExtensionHub(); + + $filename = $test_extension_list->nameGetFilename('test_name'); + static::assertEquals('vfs://drupal_root/example/test_name/test_name.info.yml', $filename); + } + + + /** + * @covers ::nameSetFilename + * @covers ::nameGetFilename + */ + public function testSetFilename() { + $test_extension_list = $this->setupTestExtensionHub(); + + $test_extension_list->nameSetFilename('test_name', 'vfs://drupal_root/example2/test_name/test_name.info.yml'); + static::assertEquals('vfs://drupal_root/example2/test_name/test_name.info.yml', $test_extension_list->nameGetFilename('test_name')); + } + + /** + * @covers ::nameGetPath + */ + public function testGetPath() { + $test_extension_list = $this->setupTestExtensionHub(); + + $path = $test_extension_list->nameGetPath('test_name'); + static::assertEquals('vfs://drupal_root/example/test_name', $path); + } + + /** + * @return \Drupal\Core\Extension\Hub\ExtensionHubInterface + */ + protected function setupEmptyTestExtensionHub() { + list($cache, $info_parser, $module_handler) = $this->getMocks(); + + $extension_discovery = $this->prophesize(ExtensionDiscovery::class); + $extension_discovery->scan('test_extension')->willReturn([]); + + return ExtensionHub::create( + 'test_extension', + TestExtensionList::create( + 'test_extension', + $info_parser->reveal(), + $module_handler->reveal(), + $extension_discovery->reveal()), + $cache->reveal()); + } + + /** + * @return \Drupal\Core\Extension\Hub\ExtensionHubInterface + */ + protected function setupTestExtensionHub() { + vfsStream::setup('drupal_root'); + vfsStream::create([ + 'example' => [ + 'test_name' => [ + 'test_name.info.yml' => Yaml::encode([ + 'name' => 'test name', + 'type' => 'test_extension', + 'core' => '8.x', + ]), + ], + ], + ]); + touch('vfs://drupal_root/example/test_name/test_name.info.yml', 123456789); + + list($cache, $info_parser, $module_handler) = $this->getMocks(); + $info_parser->parse(Argument::any())->will(function($args) { + return Yaml::decode(file_get_contents($args[0])); + }); + + $extension_discovery = $this->prophesize(ExtensionDiscovery::class); + $extension_discovery->scan('test_extension')->willReturn(['test_name' => new Extension($this->root, 'test_extension', 'vfs://drupal_root/example/test_name/test_name.info.yml')]); + + return ExtensionHub::create( + 'test_extension', + TestExtensionList::create( + 'test_extension', + $info_parser->reveal(), + $module_handler->reveal(), + $extension_discovery->reveal()), + $cache->reveal()); + } + + protected function getMocks() { + $cache = $this->prophesize(CacheBackendInterface::class); + $info_parser = $this->prophesize(InfoParserInterface::class); + $module_handler = $this->prophesize(ModuleHandlerInterface::class); + return [$cache, $info_parser, $module_handler]; + } + +} + +class TestExtensionList extends DiscoveryExtensionListBase { + + /** + * @param string $type + * @param \Drupal\Core\Extension\InfoParserInterface $info_parser + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery + * + * @return \Drupal\Core\Extension\List_\ExtensionListInterface + */ + static function create( + $type, + InfoParserInterface $info_parser, + ModuleHandlerInterface $module_handler, + ExtensionDiscovery $extension_discovery + ) { + return new self( + $type, + $info_parser, + $module_handler, + $extension_discovery, + []); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Extension/ExtensionListTest.php b/core/tests/Drupal/Tests/Core/Extension/ExtensionListTest.php deleted file mode 100644 index 3ddf93b..0000000 --- a/core/tests/Drupal/Tests/Core/Extension/ExtensionListTest.php +++ /dev/null @@ -1,204 +0,0 @@ -getMocks(); - $test_extension_list = new TestExtension($this->root, 'test_extension', $cache->reveal(), $info_parser->reveal(), $module_handler->reveal()); - - $extension_discovery = $this->prophesize(ExtensionDiscovery::class); - $extension_discovery->scan('test_extension')->willReturn([]); - $test_extension_list->setExtensionDiscovery($extension_discovery->reveal()); - - $test_extension_list->getName('test_name'); - } - - /** - * @covers ::getName - */ - public function testGetName() { - $test_extension_list = $this->setupTestExtensionList(); - - $this->assertEquals('test name', $test_extension_list->getName('test_name')); - } - - /** - * @covers ::getExtension - * @expectedException \InvalidArgumentException - */ - public function testGetExtensionWithNonExistingExtension() { - list($cache, $info_parser, $module_handler) = $this->getMocks(); - $test_extension_list = new TestExtension($this->root, 'test_extension', $cache->reveal(), $info_parser->reveal(), $module_handler->reveal()); - - $extension_discovery = $this->prophesize(ExtensionDiscovery::class); - $extension_discovery->scan('test_extension')->willReturn([]); - $test_extension_list->setExtensionDiscovery($extension_discovery->reveal()); - - $test_extension_list->getExtension('test_name'); - } - - /** - * @covers ::getExtension - */ - public function testGetExtension() { - $test_extension_list = $this->setupTestExtensionList(); - - $extension = $test_extension_list->getExtension('test_name'); - $this->assertInstanceOf(Extension::class, $extension); - $this->assertEquals('test_name', $extension->getName()); - } - - /** - * @covers ::listExtensions - */ - public function testListExtensions() { - $test_extension_list = $this->setupTestExtensionList(); - - $extensions = $test_extension_list->listExtensions(); - $this->assertCount(1, $extensions); - $this->assertEquals('test_name', $extensions['test_name']->getName()); - } - - /** - * @covers ::getInfo - * @covers ::getAllInfo - */ - public function testGetInfo() { - $test_extension_list = $this->setupTestExtensionList(); - - $info = $test_extension_list->getInfo('test_name'); - $this->assertEquals([ - 'type' => 'test_extension', - 'core' => '8.x', - 'name' => 'test name', - 'mtime' => 123456789, - ], $info); - } - - /** - * @covers ::getAllInfo - */ - public function testGetAllInfo() { - $test_extension_list = $this->setupTestExtensionList(); - - $infos = $test_extension_list->getAllInfo(); - $this->assertEquals(['test_name' => [ - 'type' => 'test_extension', - 'core' => '8.x', - 'name' => 'test name', - 'mtime' => 123456789, - ]], $infos); - } - - /** - * @covers ::getFilenames - */ - public function testGetFilenames() { - $test_extension_list = $this->setupTestExtensionList(); - - $filenames = $test_extension_list->getFilenames(); - $this->assertEquals([ - 'test_name' => 'vfs://drupal_root/example/test_name/test_name.info.yml', - ], $filenames); - } - - /** - * @covers ::getFilename - */ - public function testGetFilename() { - $test_extension_list = $this->setupTestExtensionList(); - - $filename = $test_extension_list->getFilename('test_name'); - $this->assertEquals('vfs://drupal_root/example/test_name/test_name.info.yml', $filename); - } - - - /** - * @covers ::setFilename - * @covers ::getFilename - */ - public function testSetFilename() { - $test_extension_list = $this->setupTestExtensionList(); - - $test_extension_list->setFilename('test_name', 'vfs://drupal_root/example2/test_name/test_name.info.yml'); - $this->assertEquals('vfs://drupal_root/example2/test_name/test_name.info.yml', $test_extension_list->getFilename('test_name')); - } - - /** - * @covers ::getPath - */ - public function testGetPath() { - $test_extension_list = $this->setupTestExtensionList(); - - $path = $test_extension_list->getPath('test_name'); - $this->assertEquals('vfs://drupal_root/example/test_name', $path); - } - - /** - * @return \Drupal\Tests\Core\Extension\TestExtension - */ - protected function setupTestExtensionList() { - vfsStream::setup('drupal_root'); - vfsStream::create([ - 'example' => [ - 'test_name' => [ - 'test_name.info.yml' => Yaml::encode([ - 'name' => 'test name', - 'type' => 'test_extension', - 'core' => '8.x', - ]), - ], - ], - ]); - touch('vfs://drupal_root/example/test_name/test_name.info.yml', 123456789); - - list($cache, $info_parser, $module_handler) = $this->getMocks(); - $info_parser->parse(Argument::any())->will(function($args) { - return Yaml::decode(file_get_contents($args[0])); - }); - - $test_extension_list = new TestExtension('vfs://drupal_root', 'test_extension', $cache->reveal(), $info_parser->reveal(), $module_handler->reveal()); - - $extension_discovery = $this->prophesize(ExtensionDiscovery::class); - $extension_discovery->scan('test_extension')->willReturn(['test_name' => new Extension($this->root, 'test_extension', 'vfs://drupal_root/example/test_name/test_name.info.yml')]); - $test_extension_list->setExtensionDiscovery($extension_discovery->reveal()); - return $test_extension_list; - } - - protected function getMocks() { - $cache = $this->prophesize(CacheBackendInterface::class); - $info_parser = $this->prophesize(InfoParserInterface::class); - $module_handler = $this->prophesize(ModuleHandlerInterface::class); - return [$cache, $info_parser, $module_handler]; - } - -} - -class TestExtension extends ExtensionList { - - public function setExtensionDiscovery(ExtensionDiscovery $extension_discovery) { - $this->extensionDiscovery = $extension_discovery; - } - -}