diff --git a/core/core.services.yml b/core/core.services.yml index 40c6a8a6b4..d7a26aa861 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -501,6 +501,12 @@ services: - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator } 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', '@state', '@config.factory', '@extension.list.profile', '%install_profile%', '%container.modules%'] + extension.list.profile: + class: Drupal\Core\Extension\ProfileExtensionList + arguments: ['@app.root', 'profile', '@cache.default', '@info_parser', '@module_handler', '@state', '%install_profile%'] content_uninstall_validator: class: Drupal\Core\Entity\ContentUninstallValidator tags: diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 293f25ae9d..b8e7b1a9a0 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -5,6 +5,7 @@ * Functions that need to be loaded on every Drupal request. */ +use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\SafeMarkup; @@ -207,11 +208,26 @@ function drupal_get_filename($type, $name, $filename = NULL) { return 'core/core.info.yml'; } - // Profiles are converted into modules in system_rebuild_module_data(). - // @todo Remove false-exposure of profiles as modules. - if ($type == 'profile') { - $type = 'module'; + if ($type === 'module' || $type === 'profile') { + $service_id = 'extension.list.' . $type; + /** @var \Drupal\Core\Extension\ExtensionList $extension_list */ + $extension_list = \Drupal::service($service_id); + if (isset($filename)) { + // Manually add the info file path of an extension. + $extension_list->setPathname($name, $filename); + return; + } + else { + try { + return $extension_list->getPathname($name); + } + catch (\InvalidArgumentException $e) { + // Catch the exception and trigger error to maintain existing behavior. + trigger_error(new FormattableMarkup('The following @type is missing from the file system: @name', ['@type' => $type, '@name' => $name]), E_USER_WARNING); + } + } } + if (!isset($files[$type])) { $files[$type] = []; } @@ -220,15 +236,6 @@ function drupal_get_filename($type, $name, $filename = NULL) { $files[$type][$name] = $filename; } elseif (!isset($files[$type][$name])) { - // If the pathname of the requested extension is not known, try to retrieve - // the list of extension pathnames from various providers, checking faster - // providers first. - // Retrieve the current module list (derived from the service container). - if ($type == 'module' && \Drupal::hasService('module_handler')) { - foreach (\Drupal::moduleHandler()->getModuleList() as $module_name => $module) { - $files[$type][$module_name] = $module->getPathname(); - } - } // If still unknown, retrieve the file list prepared in state by // system_rebuild_module_data() and // \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData(). diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index cfc0497bbc..41cc43697c 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -302,12 +302,6 @@ function install_begin_request($class_loader, &$install_state) { // Allow command line scripts to override server variables used by Drupal. require_once __DIR__ . '/bootstrap.inc'; - // Before having installed the system module and being able to do a module - // rebuild, prime the drupal_get_filename() static cache with the module's - // exact location. - // @todo Remove as part of https://www.drupal.org/node/2186491 - drupal_get_filename('module', 'system', 'core/modules/system/system.info.yml'); - // If the hash salt leaks, it becomes possible to forge a valid testing user // agent, install a new copy of Drupal, and take over the original site. // The user agent header is used to pass a database prefix in the request when @@ -445,6 +439,12 @@ function install_begin_request($class_loader, &$install_state) { } } + // Before having installed the system module and being able to do a module + // rebuild, prime the drupal_get_filename() static cache with the system + // module's location. + // @todo Remove as part of https://www.drupal.org/node/2186491 + drupal_get_filename('module', 'system', 'core/modules/system/system.info.yml'); + // Use the language from the profile configuration, if available, to override // the language previously set in the parameters. if (isset($install_state['profile_info']['distribution']['langcode'])) { @@ -1063,7 +1063,7 @@ function install_base_system(&$install_state) { file_ensure_htaccess(); // Prime the drupal_get_filename() static cache with the user module's - // exact location. + // location. // @todo Remove as part of https://www.drupal.org/node/2186491 drupal_get_filename('module', 'user', 'core/modules/user/user.info.yml'); diff --git a/core/includes/install.inc b/core/includes/install.inc index c5b93b58a6..afa092f439 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -618,6 +618,13 @@ function drupal_install_system($install_state) { $kernel->rebuildContainer(FALSE); $kernel->prepareLegacyRequest($request); + // 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. + // @todo Try to install system as any other module, see + // https://www.drupal.org/node/2719315. + \Drupal::service('extension.list.module')->setPathname('system', 'core/modules/system/system.info.yml'); + // Install base system configuration. \Drupal::service('config.installer')->installDefaultConfig('core', 'core'); diff --git a/core/includes/module.inc b/core/includes/module.inc index e3f446c660..63b3abc5ef 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -57,7 +57,7 @@ function system_list($type) { */ function system_list_reset() { drupal_static_reset('system_list'); - drupal_static_reset('system_rebuild_module_data'); + \Drupal::service('extension.list.module')->reset(); \Drupal::cache('bootstrap')->delete('system_list'); } diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 3f53d04a3b..44e653faad 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -840,7 +840,10 @@ protected function initializeContainer() { if (isset($this->container)) { // Save the id of the currently logged in user. if ($this->container->initialized('current_user')) { - $current_user_id = $this->container->get('current_user')->id(); + /** @var \Drupal\Core\Session\AccountProxyInterface $current_user */ + $current_user = $this->container->get('current_user'); + // Ensure to not accidentally initialize the user. + $current_user_id = $current_user->hasAccount() ? $current_user->id() : 0; } // If there is a session, close and save it. diff --git a/core/lib/Drupal/Core/Extension/ExtensionList.php b/core/lib/Drupal/Core/Extension/ExtensionList.php new file mode 100644 index 0000000000..b0b3460f21 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ExtensionList.php @@ -0,0 +1,555 @@ +root = $root; + $this->type = $type; + $this->cache = $cache; + $this->infoParser = $info_parser; + $this->moduleHandler = $module_handler; + $this->state = $state; + $this->installProfile = $install_profile; + } + + /** + * 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->pathNames = NULL; + + try { + $this->state->delete($this->getPathnamesCacheId()); + } + catch (\Exception $e) { + // Ignore exceptions caused by a non existing {key_value} table in the + // early installer. + } + + $this->cache->delete($this->getPathnamesCacheId()); + // We explicitly don't reset the addedFileNames as it is sort of a static + // cache. + // @todo In the long run it would be great to add the reset, but the early + // installer fails due to that. + return $this; + } + + /** + * Returns the extension list cache ID. + * + * @return string + * The list cache ID. + */ + protected function getListCacheId() { + return 'core.extension.list.' . $this->type; + } + + /** + * Returns the extension info cache ID. + * + * @return string + * The info cache ID. + */ + protected function getInfoCacheId() { + return "system.{$this->type}.info"; + } + + /** + * Returns the extension filenames cache ID. + * + * @return string + * The filename cache ID. + */ + protected function getPathnamesCacheId() { + return "system.{$this->type}.files"; + } + + /** + * Determines if an extension exists in the filesystem. + * + * @param string $extension_name + * The extension machine name of the extension. + * + * @return bool + * TRUE if the extension exists (regardless installed or not) and FALSE if + * not. + */ + public function extensionExists($extension_name) { + $extensions = $this->listExtensions(); + return isset($extensions[$extension_name]); + } + + /** + * Returns the human-readable name of the extension. + * + * @param string $extension_name + * The extension machine name. + * + * @return string + * The human-readable name of the extension. + * + * @throws \InvalidArgumentException + * If there is no extension with the supplied extension human name. + */ + public function getName($extension_name) { + return $this->getExtension($extension_name)->info['name']; + } + + /** + * Returns a single extension. + * + * @param string $extension_name + * The extension machine name. + * + * @return \Drupal\Core\Extension\Extension + * Processed extension object for the given extension machine name. + * + * @throws \InvalidArgumentException + * If there is no extension with the supplied name. + */ + public function getExtension($extension_name) { + $extensions = $this->listExtensions(); + if (isset($extensions[$extension_name])) { + return $extensions[$extension_name]; + } + + throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist."); + } + + /** + * Returns all available extensions. + * + * @return \Drupal\Core\Extension\Extension[] + * Processed extension objects, keyed by extension machine name. + */ + public function listExtensions() { + if ($this->extensions !== NULL) { + 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[] + * Unprocessed extension objects, keyed by extension machine name. + */ + protected function doScanExtensions() { + return $this->getExtensionDiscovery()->scan($this->type); + } + + /** + * Builds the list of extensions. + * + * @return \Drupal\Core\Extension\Extension[] + * Processed extension objects, keyed by extension machine name. + * + * @throws \Drupal\Core\Extension\InfoParserException + * If one of the .info.yml files is incomplete, or causes a parsing error. + */ + protected function doListExtensions() { + // Find extensions. + $extensions = $this->doScanExtensions(); + + // Read info files for each extension. + foreach ($extensions as $extension_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 extension type-specific defaults. + $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', $extension->info, $extension, $this->type); + } + + return $extensions; + } + + /** + * Returns information about a specified extension. + * + * This function returns the contents of the .info.yml file for the specified + * extension. + * + * @param string $extension_name + * The name of an extension whose information shall be returned. + * + * @return mixed[] + * An associative array of extension information. + * + * @throws \InvalidArgumentException + * If there is no extension with the supplied name. + */ + public function getExtensionInfo($extension_name) { + $all_info = $this->getAllInstalledInfo(); + if (isset($all_info[$extension_name])) { + return $all_info[$extension_name]; + } + throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist or is not installed."); + } + + /** + * Returns an array of info files information of available extensions. + * + * This function returns the processed contents (with added defaults) of the + * .info.yml files. + * + * @return array[] + * An associative array of extension information arrays, keyed by extension + * name. + */ + public function getAllAvailableInfo() { + if ($this->extensionInfo === NULL) { + $cache_id = $this->getInfoCacheId(); + if ($cache = $this->cache->get($cache_id)) { + $info = $cache->data; + } + else { + $info = $this->recalculateInfo(); + $this->cache->set($cache_id, $info); + } + $this->extensionInfo = $info; + } + + return $this->extensionInfo; + } + + /** + * Returns installed extension machine names. + * + * @return string[] + * The extension machine names of all installed extensions of this type. + */ + abstract protected function getInstalledExtensionsNames(); + + /** + * Returns an array of info files information of installed extensions. + * + * This function returns the processed contents (with added defaults) of the + * .info.yml files. + * + * @return array[] + * An associative array of extension information arrays, keyed by extension + * name. + */ + public function getAllInstalledInfo() { + return array_intersect_key($this->getAllAvailableInfo(), array_flip($this->getInstalledExtensionsNames())); + } + + /** + * Generates the information from .info.yml files for extensions of this type. + * + * @return array[] + * An array of arrays of .info.yml entries keyed by the extension machine name. + */ + protected function recalculateInfo() { + return array_map(function (Extension $extension) { + return $extension->info; + }, $this->listExtensions()); + } + + /** + * Returns a list of extension file paths keyed by extension machine name. + * + * @return string[] + */ + public function getPathnames() { + if ($this->pathNames === NULL) { + $cache_id = $this->getPathnamesCacheId(); + if ($cache = $this->cache->get($cache_id)) { + $path_names = $cache->data; + } + // We use $file_names below. + elseif (!$path_names = $this->state->get($cache_id)) { + $path_names = $this->recalculatePathnames(); + // Store filenames to allow static::getFilename() to retrieve them + // without having to rebuild or scan the filesystem. + $this->state->set($cache_id, $path_names); + $this->cache->set($cache_id, $path_names); + } + $this->pathNames = $path_names; + } + return $this->pathNames; + } + + /** + * Generates a sorted list of .info.yml file locations for all extensions. + * + * @return string[] + * An array of .info.yml file locations keyed by the extension machine name. + */ + protected function recalculatePathnames() { + $extensions = $this->listExtensions(); + ksort($extensions); + + return array_map(function (Extension $extension) { + return $extension->getPathname(); + }, $extensions); + } + + /** + * Sets the pathname 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 $pathname + * The pathname of the extension which is to be set explicitly rather + * than by consulting the dynamic extension listing. + * + * @internal + * + * @see ::getFilename + */ + public function setPathname($extension_name, $pathname) { + $this->addedPathNames[$extension_name] = $pathname; + + // In the early installer the container is rebuilt multiple times. Therefore + // we have to keep the added filenames across those rebuilds. This is not a + // final design, but rather just a workaround resolved at some point, + // hopefully. + if (!empty($GLOBALS['install_state'])) { + static::$staticAddedPathNames[$extension_name] = $pathname; + } + } + + /** + * Gets the filename for an extension. + * + * 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 four 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 the following four 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 + * + * An installation profile maybe be located in any of the following places: + * + * - core/profiles/baz/baz.info.yml + * - /profiles/baz/baz.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 getPathname($extension_name) { + if (isset($this->addedPathNames[$extension_name])) { + return $this->addedPathNames[$extension_name]; + } + elseif (isset($this->pathNames[$extension_name])) { + return $this->pathNames[$extension_name]; + } + elseif (isset(static::$staticAddedPathNames[$extension_name])) { + return static::$staticAddedPathNames[$extension_name]; + } + elseif (($path_names = $this->getPathnames()) && isset($path_names[$extension_name])) { + return $path_names[$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->getPathname($extension_name)); + } + +} diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php new file mode 100644 index 0000000000..5fc30aa2a6 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php @@ -0,0 +1,230 @@ + [], + 'description' => '', + 'package' => 'Other', + 'version' => NULL, + 'php' => DRUPAL_MINIMUM_PHP, + ]; + + /** + * The config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The profile list needed by this module list. + * + * @var \Drupal\Core\Extension\ExtensionList + */ + protected $profileList; + + /** + * 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\State\StateInterface $state + * The state. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. + * @param \Drupal\Core\Extension\ExtensionList $profile_list + * The site profile listing. + * @param string $install_profile + * The install profile used by the site. + * @param array[] $container_modules_info + * (optional) The module locations coming from the compiled container. + */ + public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, ConfigFactoryInterface $config_factory, ExtensionList $profile_list, $install_profile, array $container_modules_info = []) { + parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $install_profile); + + $this->configFactory = $config_factory; + $this->profileList = $profile_list; + + // Use the information from the container. This is an optimization. + foreach ($container_modules_info as $module_name => $info) { + $this->setPathname($module_name, $info['pathname']); + } + } + + /** + * {@inheritdoc} + */ + protected function getExtensionDiscovery() { + $discovery = parent::getExtensionDiscovery(); + + if ($active_profile = $this->getActiveProfile()) { + $discovery->setProfileDirectories($this->getProfileDirectories($discovery)); + } + + return $discovery; + } + + /** + * Finds all installation profile paths. + * + * @param \Drupal\Core\Extension\ExtensionDiscovery $discovery + * The extension discovery. + * + * @return string[] + * Paths to all installation profiles. + */ + protected function getProfileDirectories(ExtensionDiscovery $discovery) { + $discovery->setProfileDirectories([]); + $all_profiles = $discovery->scan('profile'); + $active_profile = $all_profiles[$this->installProfile]; + $profiles = array_intersect_key($all_profiles, $this->configFactory->get('core.extension')->get('module') ?: [$active_profile->getName() => 0]); + + // If a module is within a profile directory but specifies another + // profile for testing, it needs to be found in the parent profile. + $settings = $this->configFactory->get('simpletest.settings')->get(); + $parent_profile = !empty($settings['parent_profile']) ? $settings['parent_profile'] : NULL; + + if ($parent_profile && !isset($profiles[$parent_profile])) { + // In case both profile directories contain the same extension, the + // actual profile always has precedence. + $profiles = [$parent_profile => $all_profiles[$parent_profile]] + $profiles; + } + + $profile_directories = array_map(function (Extension $profile) { + return $profile->getPath(); + }, $profiles); + return $profile_directories; + } + + /** + * Gets the processed active profile object, or null. + * + * @return \Drupal\Core\Extension\Extension|null + * The active profile, if there is one. + */ + protected function getActiveProfile() { + $profiles = $this->profileList->listExtensions(); + if ($this->installProfile && isset($profiles[$this->installProfile])) { + return $profiles[$this->installProfile]; + } + return NULL; + + } + + /** + * {@inheritdoc} + */ + protected function doScanExtensions() { + $extensions = parent::doScanExtensions(); + + $profile_extension_discovery = new ExtensionDiscovery($this->root); + $profiles = $profile_extension_discovery->scan('profile'); + // Modify the active profile object that was previously added to the module + // list. + if ($this->installProfile && isset($profiles[$this->installProfile])) { + $extensions[$this->installProfile] = $profiles[$this->installProfile]; + } + + return $extensions; + } + + /** + * {@inheritdoc} + */ + protected function doListExtensions() { + // Find modules. + $extensions = parent::doListExtensions(); + // 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) { + $this->ensureRequiredDependencies($extension, $extensions); + } + + // Add status, weight, and schema version. + $installed_modules = $this->configFactory->get('core.extension')->get('module') ?: []; + foreach ($extensions as $name => $module) { + $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0; + $module->status = (int) isset($installed_modules[$name]); + $module->schema_version = SCHEMA_UNINSTALLED; + } + $extensions = $this->moduleHandler->buildModuleDependencies($extensions); + + if ($this->installProfile && $extensions[$this->installProfile]) { + $active_profile = $extensions[$this->installProfile]; + + // Installation profile hooks are always executed last. + $active_profile->weight = 1000; + + // Installation profiles are hidden by default, unless explicitly + // specified otherwise in the .info.yml file. + if (!isset($active_profile->info['hidden'])) { + $active_profile->info['hidden'] = TRUE; + } + + // The installation profile is required. + $active_profile->info['required'] = TRUE; + // Add a default distribution name if the profile did not provide one. + // @see install_profile_info() + // @see drupal_install_profile_distribution_name() + if (!isset($active_profile->info['distribution']['name'])) { + $active_profile->info['distribution']['name'] = 'Drupal'; + } + } + + return $extensions; + } + + /** + * {@inheritdoc} + */ + protected function getInstalledExtensionsNames() { + return array_keys($this->moduleHandler->getModuleList()); + } + + /** + * Marks dependencies of required modules as 'required', recursively. + * + * @param \Drupal\Core\Extension\Extension $module + * The module extension object. + * @param \Drupal\Core\Extension\Extension[] $modules + * Extension objects for all available modules. + */ + protected function ensureRequiredDependencies(Extension $module, array $modules = []) { + if (!empty($module->info['required'])) { + foreach ($module->info['dependencies'] as $dependency) { + $dependency_name = ModuleHandler::parseDependency($dependency)['name']; + if (!isset($modules[$dependency_name]->info['required'])) { + $modules[$dependency_name]->info['required'] = TRUE; + $modules[$dependency_name]->info['explanation'] = $this->t('Dependency of required module @module', ['@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/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index cfbaa61a8e..a7d8a9bffc 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -717,8 +717,7 @@ public function getModuleDirectories() { * {@inheritdoc} */ public function getName($module) { - $info = system_get_info('module', $module); - return isset($info['name']) ? $info['name'] : $module; + return \Drupal::service('extension.list.module')->getName($module); } } diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index 6b2738c779..0253358cfe 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -306,6 +306,9 @@ public function getModuleDirectories(); * @return string * Returns the human readable name of the module or the machine name passed * in if no matching module is found. + * + * @deprecated in Drupal 8.4.0, will be removed before Drupal 9.0.0. + * Use \Drupal::service('extension.list.module')->getName() instead. */ public function getName($module); diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index f9500a5fc7..b7264f4e17 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_get_path('module', $name); + $module_path = \Drupal::service('extension.list.module')->getPath($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); @@ -182,10 +182,10 @@ public function install(array $module_list, $enable_dependencies = TRUE) { $this->moduleHandler->load($module); module_load_install($module); - // Clear the static cache of system_rebuild_module_data() to pick up the - // new module, since it merges the installation status of modules into - // its statically cached list. - drupal_static_reset('system_rebuild_module_data'); + // Clear the static cache of the "extension.list.module" service to pick + // up the new module, since it merges the installation status of modules + // into its statically cached list. + \Drupal::service('extension.list.module')->reset(); // Update the kernel to include it. $this->updateKernel($module_filenames); @@ -437,10 +437,10 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Remove any potential cache bins provided by the module. $this->removeCacheBins($module); - // Clear the static cache of system_rebuild_module_data() to pick up the - // new module, since it merges the installation status of modules into - // its statically cached list. - drupal_static_reset('system_rebuild_module_data'); + // Clear the static cache of the "extension.list.module" service to pick + // up the new module, since it merges the installation status of modules + // into its statically cached list. + \Drupal::service('extension.list.module')->reset(); // Clear plugin manager caches. \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions(); diff --git a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php new file mode 100644 index 0000000000..09b15885fa --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php @@ -0,0 +1,28 @@ + [], + 'description' => '', + 'package' => 'Other', + 'version' => NULL, + 'php' => DRUPAL_MINIMUM_PHP, + ]; + + /** + * {@inheritdoc} + */ + protected function getInstalledExtensionsNames() { + return [$this->installProfile]; + } + +} diff --git a/core/lib/Drupal/Core/Session/AccountProxy.php b/core/lib/Drupal/Core/Session/AccountProxy.php index afe800bfac..750cf0a8e9 100644 --- a/core/lib/Drupal/Core/Session/AccountProxy.php +++ b/core/lib/Drupal/Core/Session/AccountProxy.php @@ -55,6 +55,13 @@ public function setAccount(AccountInterface $account) { /** * {@inheritdoc} */ + public function hasAccount() { + return isset($this->account); + } + + /** + * {@inheritdoc} + */ public function getAccount() { if (!isset($this->account)) { if ($this->id) { diff --git a/core/lib/Drupal/Core/Session/AccountProxyInterface.php b/core/lib/Drupal/Core/Session/AccountProxyInterface.php index 378b89dbf8..b7525aa97b 100644 --- a/core/lib/Drupal/Core/Session/AccountProxyInterface.php +++ b/core/lib/Drupal/Core/Session/AccountProxyInterface.php @@ -6,6 +6,7 @@ * Defines an interface for a service which has the current account stored. * * @ingroup user_api + * @internal */ interface AccountProxyInterface extends AccountInterface { @@ -43,4 +44,12 @@ public function getAccount(); */ public function setInitialAccountId($account_id); + /** + * Checks whether an account is currently wrapped. + * + * @return bool + * TRUE, if an account is currently wrapped. FALSE otherwise. + */ + public function hasAccount(); + } diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php index 0a244b46bc..0eaf46c634 100644 --- a/core/lib/Drupal/Core/Updater/Module.php +++ b/core/lib/Drupal/Core/Updater/Module.php @@ -49,8 +49,7 @@ public static function getRootDirectoryRelativePath() { public function isInstalled() { // Check if the module exists in the file system, regardless of whether it // is enabled or not. - $modules = \Drupal::state()->get('system.module.files', []); - return isset($modules[$this->name]); + return \Drupal::service('extension.list.module')->extensionExists($this->name); } /** diff --git a/core/modules/book/tests/src/Kernel/BookUninstallTest.php b/core/modules/book/tests/src/Kernel/BookUninstallTest.php index 9430991ffe..279873481b 100644 --- a/core/modules/book/tests/src/Kernel/BookUninstallTest.php +++ b/core/modules/book/tests/src/Kernel/BookUninstallTest.php @@ -76,7 +76,7 @@ public function testBookUninstall() { $book_node->delete(); // No nodes exist therefore the book module is not required. - $module_data = _system_rebuild_module_data(); + $module_data = \Drupal::service('extension.list.module')->reset()->listExtensions(); $this->assertFalse(isset($module_data['book']->info['required']), 'The book module is not required.'); $node = Node::create(['title' => $this->randomString(), 'type' => $content_type->id()]); diff --git a/core/modules/config/src/Tests/ConfigImportAllTest.php b/core/modules/config/src/Tests/ConfigImportAllTest.php index 459be7a24f..dc023fcddc 100644 --- a/core/modules/config/src/Tests/ConfigImportAllTest.php +++ b/core/modules/config/src/Tests/ConfigImportAllTest.php @@ -97,7 +97,7 @@ public function testInstallUninstall() { // Ensure that only core required modules and the install profile can not be uninstalled. $validation_reasons = \Drupal::service('module_installer')->validateUninstall(array_keys($all_modules)); - $this->assertEqual(['standard', 'system', 'user'], array_keys($validation_reasons)); + $this->assertEqual(['system', 'user', 'standard'], array_keys($validation_reasons)); $modules_to_uninstall = array_filter($all_modules, function ($module) use ($validation_reasons) { // Filter required and not enabled modules. diff --git a/core/modules/filter/tests/src/Kernel/FilterAPITest.php b/core/modules/filter/tests/src/Kernel/FilterAPITest.php index 2203bbec1f..6b3eee0db0 100644 --- a/core/modules/filter/tests/src/Kernel/FilterAPITest.php +++ b/core/modules/filter/tests/src/Kernel/FilterAPITest.php @@ -485,7 +485,7 @@ public function testDependencyRemoval() { drupal_static_reset('filter_formats'); \Drupal::entityManager()->getStorage('filter_format')->resetCache(); - $module_data = _system_rebuild_module_data(); + $module_data = \Drupal::service('extension.list.module')->reset()->listExtensions(); $this->assertFalse(isset($module_data['filter_test']->info['required']), 'The filter_test module is required.'); // Verify that a dependency exists on the module that provides the filter diff --git a/core/modules/system/system.module b/core/modules/system/system.module index a1a6b715a3..2c2c6b6ee5 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -12,7 +12,6 @@ use Drupal\Core\Queue\QueueGarbageCollectionInterface; use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Extension\Extension; -use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory; use Drupal\Core\PageCache\RequestPolicyInterface; @@ -950,27 +949,17 @@ function system_check_directory($form_element, FormStateInterface $form_state) { */ function system_get_info($type, $name = NULL) { if ($type == 'module') { - $info = &drupal_static(__FUNCTION__); - if (!isset($info)) { - if ($cache = \Drupal::cache()->get('system.module.info')) { - $info = $cache->data; - } - else { - $data = system_rebuild_module_data(); - foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) { - if (isset($data[$module])) { - $info[$module] = $data[$module]->info; - } - } - // Store the module information in cache. This cache is cleared by - // calling system_rebuild_module_data(), for example, when listing - // modules, (un)installing modules, importing configuration, updating - // the site and when flushing all the caches. - \Drupal::cache()->set('system.module.info', $info); - } + /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */ + $module_list = \Drupal::service('extension.list.module'); + if (isset($name)) { + return $module_list->getExtensionInfo($name); + } + else { + return $module_list->getAllInstalledInfo(); } } else { + // @todo move into ThemeExtensionList https://www.drupal.org/node/2659940 $info = []; $list = system_list($type); foreach ($list as $shortname => $item) { @@ -978,97 +967,11 @@ function system_get_info($type, $name = NULL) { $info[$shortname] = $item->info; } } - } - if (isset($name)) { - return isset($info[$name]) ? $info[$name] : []; - } - return $info; -} - -/** - * Helper function to scan and collect module .info.yml data. - * - * @return \Drupal\Core\Extension\Extension[] - * An associative array of module information. - */ -function _system_rebuild_module_data() { - $listing = new ExtensionDiscovery(\Drupal::root()); - - // Find installation profiles. This needs to happen before performing a - // module scan as the module scan requires knowing what the active profile is. - // @todo Remove as part of https://www.drupal.org/node/2186491. - $profiles = $listing->scan('profile'); - $profile = drupal_get_profile(); - if ($profile && isset($profiles[$profile])) { - // Prime the drupal_get_filename() static cache with the profile info file - // location so we can use drupal_get_path() on the active profile during - // the module scan. - // @todo Remove as part of https://www.drupal.org/node/2186491. - drupal_get_filename('profile', $profile, $profiles[$profile]->getPathname()); - } - - // Find modules. - $modules = $listing->scan('module'); - // Include the installation profile in modules that are loaded. - if ($profile) { - $modules[$profile] = $profiles[$profile]; - // Installation profile hooks are always executed last. - $modules[$profile]->weight = 1000; - } - - // Set defaults for module info. - $defaults = [ - 'dependencies' => [], - 'description' => '', - 'package' => 'Other', - 'version' => NULL, - 'php' => DRUPAL_MINIMUM_PHP, - ]; - - // Read info files for each module. - foreach ($modules as $key => $module) { - // Look for the info file. - $module->info = \Drupal::service('info_parser')->parse($module->getPathname()); - - // Add the info file modification time, so it becomes available for - // contributed modules to use for ordering module lists. - $module->info['mtime'] = $module->getMTime(); - - // Merge in defaults and save. - $modules[$key]->info = $module->info + $defaults; - - // Installation profiles are hidden by default, unless explicitly specified - // otherwise in the .info.yml file. - if ($key == $profile && !isset($modules[$key]->info['hidden'])) { - $modules[$key]->info['hidden'] = TRUE; - } - - // Invoke hook_system_info_alter() to give installed modules a chance to - // modify the data in the .info.yml files if necessary. - // @todo Remove $type argument, obsolete with $module->getType(). - $type = 'module'; - \Drupal::moduleHandler()->alter('system_info', $modules[$key]->info, $modules[$key], $type); - } - - // 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 ($modules as $module) { - _system_rebuild_module_data_ensure_required($module, $modules); - } - - - if ($profile && isset($modules[$profile])) { - // The installation profile is required, if it's a valid module. - $modules[$profile]->info['required'] = TRUE; - // Add a default distribution name if the profile did not provide one. - // @see install_profile_info() - // @see drupal_install_profile_distribution_name() - if (!isset($modules[$profile]->info['distribution']['name'])) { - $modules[$profile]->info['distribution']['name'] = 'Drupal'; + if (isset($name)) { + return isset($info[$name]) ? $info[$name] : []; } + return $info; } - - return $modules; } /** @@ -1078,6 +981,9 @@ function _system_rebuild_module_data() { * The module info. * @param \Drupal\Core\Extension\Extension[] $modules * The array of all module info. + * + * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. This + * function is no longer used in Drupal core. */ function _system_rebuild_module_data_ensure_required($module, &$modules) { if (!empty($module->info['required'])) { @@ -1094,39 +1000,31 @@ function _system_rebuild_module_data_ensure_required($module, &$modules) { } /** + * Helper function to scan and collect module .info.yml data. + * + * @return \Drupal\Core\Extension\Extension[] + * An associative array of module information. + * + * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. + * Use \Drupal::service('extension.list.module')->reset()->listExtensions() + * instead. Note: You probably don't need the reset() method. + */ +function _system_rebuild_module_data() { + @trigger_error("_system_rebuild_module_data() is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, you should use \\Drupal::service('extension.list.module')->reset()->listExtensions(). See https://www.drupal.org/node/2709919", E_USER_DEPRECATED); + return \Drupal::service('extension.list.module')->reset()->listExtensions(); +} + +/** * Rebuild, save, and return data about all currently available modules. * * @return \Drupal\Core\Extension\Extension[] * Array of all available modules and their data. + * + * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. */ function system_rebuild_module_data() { - $modules_cache = &drupal_static(__FUNCTION__); - // Only rebuild once per request. $modules and $modules_cache cannot be - // combined into one variable, because the $modules_cache variable is reset by - // reference from system_list_reset() during the rebuild. - if (!isset($modules_cache)) { - $modules = _system_rebuild_module_data(); - $files = []; - ksort($modules); - // Add status, weight, and schema version. - $installed_modules = \Drupal::config('core.extension')->get('module') ?: []; - foreach ($modules as $name => $module) { - $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0; - $module->status = (int) isset($installed_modules[$name]); - $module->schema_version = SCHEMA_UNINSTALLED; - $files[$name] = $module->getPathname(); - } - $modules = \Drupal::moduleHandler()->buildModuleDependencies($modules); - $modules_cache = $modules; - - // Store filenames to allow drupal_get_filename() to retrieve them without - // having to rebuild or scan the filesystem. - \Drupal::state()->set('system.module.files', $files); - // Clear the module info cache. - \Drupal::cache()->delete('system.module.info'); - drupal_static_reset('system_get_info'); - } - return $modules_cache; + @trigger_error("system_rebuild_module_data() is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, you should use \\Drupal::service('extension.list.module')->reset()->listExtensions(). See https://www.drupal.org/node/2709919", E_USER_DEPRECATED); + return \Drupal::service('extension.list.module')->reset()->listExtensions(); } /** diff --git a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php index 22648a6391..9515835399 100644 --- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php +++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php @@ -105,7 +105,7 @@ public function testDependencyResolution() { // Color will depend on Config, which depends on a non-existing module Foo. // Nothing should be installed. \Drupal::state()->set('module_test.dependency', 'missing dependency'); - drupal_static_reset('system_rebuild_module_data'); + \Drupal::service('extension.list.module')->reset(); try { $result = $this->moduleInstaller()->install(['color']); @@ -120,7 +120,7 @@ public function testDependencyResolution() { // Fix the missing dependency. // Color module depends on Config. Config depends on Help module. \Drupal::state()->set('module_test.dependency', 'dependency'); - drupal_static_reset('system_rebuild_module_data'); + \Drupal::service('extension.list.module')->reset(); $result = $this->moduleInstaller()->install(['color']); $this->assertTrue($result, 'ModuleInstaller::install() returns the correct value.'); @@ -152,7 +152,7 @@ public function testDependencyResolution() { // dependency on a specific version of Help module in its info file. Make // sure that Drupal\Core\Extension\ModuleInstaller::install() still works. \Drupal::state()->set('module_test.dependency', 'version dependency'); - drupal_static_reset('system_rebuild_module_data'); + \Drupal::service('extension.list.module')->reset(); $result = $this->moduleInstaller()->install(['color']); $this->assertTrue($result, 'ModuleInstaller::install() returns the correct value.'); @@ -182,8 +182,7 @@ public function testUninstallProfileDependency() { drupal_get_filename('profile', $profile, 'core/profiles/' . $profile . '/' . $profile . '.info.yml'); $this->enableModules(['module_test', $profile]); - drupal_static_reset('system_rebuild_module_data'); - $data = system_rebuild_module_data(); + $data = \Drupal::service('extension.list.module')->reset()->listExtensions(); $this->assertTrue(isset($data[$profile]->requires[$dependency])); $this->moduleInstaller()->install([$dependency]); @@ -221,7 +220,7 @@ public function testUninstallContentDependency() { // entity_test will depend on help. This way help can not be uninstalled // when there is test content preventing entity_test from being uninstalled. \Drupal::state()->set('module_test.dependency', 'dependency'); - drupal_static_reset('system_rebuild_module_data'); + \Drupal::service('extension.list.module')->reset(); // Create an entity so that the modules can not be disabled. $entity = EntityTest::create(['name' => $this->randomString()]); diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleExtensionListTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleExtensionListTest.php new file mode 100644 index 0000000000..70512c644e --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleExtensionListTest.php @@ -0,0 +1,38 @@ +getEditable('core.extension') + ->set('module.testing', 1000) + ->save(); + + // The installation profile is provided by a container parameter. + // Saving the configuration doesn't automatically trigger invalidation + $this->container->get('kernel')->rebuildContainer(); + + /** @var \Drupal\Core\Extension\ModuleExtensionList $module_extension_list */ + $module_extension_list = \Drupal::service('extension.list.module'); + $extensions = $module_extension_list->listExtensions(); + + $this->assertArrayHasKey('testing', $extensions); + $this->assertEquals(1000, $extensions['testing']->weight); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Extension/ExtensionListTest.php b/core/tests/Drupal/Tests/Core/Extension/ExtensionListTest.php new file mode 100644 index 0000000000..c24729c58c --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Extension/ExtensionListTest.php @@ -0,0 +1,259 @@ +getMocks(); + $test_extension_list = new TestExtension($this->root, 'test_extension', $cache->reveal(), $info_parser->reveal(), $module_handler->reveal(), $state->reveal(), 'testing'); + + $extension_discovery = $this->prophesize(ExtensionDiscovery::class); + $extension_discovery->scan('test_extension')->willReturn([]); + $test_extension_list->setExtensionDiscovery($extension_discovery->reveal()); + + $this->setExpectedException(\InvalidArgumentException::class); + $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 + */ + public function testGetExtensionWithNonExistingExtension() { + list($cache, $info_parser, $module_handler, $state) = $this->getMocks(); + $test_extension_list = new TestExtension($this->root, 'test_extension', $cache->reveal(), $info_parser->reveal(), $module_handler->reveal(), $state->reveal(), 'testing'); + + $extension_discovery = $this->prophesize(ExtensionDiscovery::class); + $extension_discovery->scan('test_extension')->willReturn([]); + $test_extension_list->setExtensionDiscovery($extension_discovery->reveal()); + + $this->setExpectedException(\InvalidArgumentException::class); + $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 ::getExtensionInfo + * @covers ::getAllInstalledInfo + */ + public function testGetExtensionInfo() { + $test_extension_list = $this->setupTestExtensionList(); + $test_extension_list->setInstalledExtensions(['test_name']); + + $info = $test_extension_list->getExtensionInfo('test_name'); + $this->assertEquals([ + 'type' => 'test_extension', + 'core' => '8.x', + 'name' => 'test name', + 'mtime' => 123456789, + ], $info); + } + + /** + * @covers ::getAllAvailableInfo + */ + public function testGetAllAvailableInfo() { + $test_extension_list = $this->setupTestExtensionList(); + + $infos = $test_extension_list->getAllAvailableInfo(); + $this->assertEquals(['test_name' => [ + 'type' => 'test_extension', + 'core' => '8.x', + 'name' => 'test name', + 'mtime' => 123456789, + ]], $infos); + } + + /** + * @covers ::getAllInstalledInfo + */ + public function testGetAllInstalledInfo() { + $test_extension_list = $this->setupTestExtensionList(['test_name', 'test_name_2']); + $test_extension_list->setInstalledExtensions(['test_name_2']); + + $infos = $test_extension_list->getAllInstalledInfo(); + $this->assertEquals(['test_name_2' => [ + 'type' => 'test_extension', + 'core' => '8.x', + 'name' => 'test name', + 'mtime' => 123456789, + ]], $infos); + } + + /** + * @covers ::getPathnames + */ + public function testGetPathnames() { + $test_extension_list = $this->setupTestExtensionList(); + + $filenames = $test_extension_list->getPathnames(); + $this->assertEquals([ + 'test_name' => 'vfs://drupal_root/example/test_name/test_name.info.yml', + ], $filenames); + } + + /** + * @covers ::getPathname + */ + public function testGetPathname() { + $test_extension_list = $this->setupTestExtensionList(); + + $pathname = $test_extension_list->getPathname('test_name'); + $this->assertEquals('vfs://drupal_root/example/test_name/test_name.info.yml', $pathname); + } + + + /** + * @covers ::setPathname + * @covers ::getPathname + */ + public function testSetPathname() { + $test_extension_list = $this->setupTestExtensionList(); + + $test_extension_list->setPathname('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->getPathname('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($extension_names = ['test_name']) { + vfsStream::setup('drupal_root'); + + $folders = ['example' => []]; + foreach ($extension_names as $extension_name) { + $folders['example'][$extension_name][$extension_name . '.info.yml'] = Yaml::encode([ + 'name' => 'test name', + 'type' => 'test_extension', + 'core' => '8.x', + ]) ; + } + vfsStream::create($folders); + foreach ($extension_names as $extension_name) { + touch("vfs://drupal_root/example/$extension_name/$extension_name.info.yml", 123456789); + } + + list($cache, $info_parser, $module_handler, $state) = $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(), $state->reveal(), 'testing'); + + $extension_discovery = $this->prophesize(ExtensionDiscovery::class); + $extension_scan_result = []; + foreach ($extension_names as $extension_name) { + $extension_scan_result[$extension_name] = new Extension($this->root, 'test_extension', "vfs://drupal_root/example/$extension_name/$extension_name.info.yml"); + } + $extension_discovery->scan('test_extension')->willReturn($extension_scan_result); + $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); + $state = $this->prophesize(StateInterface::class); + return [$cache, $info_parser, $module_handler, $state]; + } + +} + +class TestExtension extends ExtensionList { + + /** + * @var string[] + */ + protected $installedExtensions = []; + + /** + * @var \Drupal\Core\Extension\ExtensionDiscovery|null + */ + protected $extensionDiscovery; + + /** + * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery + */ + public function setExtensionDiscovery(ExtensionDiscovery $extension_discovery) { + $this->extensionDiscovery = $extension_discovery; + } + + public function setInstalledExtensions(array $extension_names) { + $this->installedExtensions = $extension_names; + } + + /** + * {@inheritdoc} + */ + protected function getInstalledExtensionsNames() { + return $this->installedExtensions; + } + + /** + * {@inheritdoc} + */ + protected function getExtensionDiscovery() { + return $this->extensionDiscovery ?: parent::getExtensionDiscovery(); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php b/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php index 0e575a07ec..f17858cf80 100644 --- a/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php +++ b/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php @@ -42,6 +42,17 @@ public function testSetInitialAccountIdException() { $account_proxy->setInitialAccountId(1); } + /** + * @covers ::hasAccount + */ + public function testHasAccount() { + $account_proxy = new AccountProxy(); + $this->assertFalse($account_proxy->hasAccount()); + + $account_proxy->getAccount(); + $this->assertTrue($account_proxy->hasAccount()); + } + } namespace Drupal\Core\Session;