3 namespace Drupal\Core\Extension;
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\State\StateInterface;
8 use Drupal\Core\StringTranslation\StringTranslationTrait;
11 * Provides a list of available modules.
13 class ModuleExtensionList extends ExtensionList {
15 use StringTranslationTrait;
20 protected $defaults = [
25 'php' => DRUPAL_MINIMUM_PHP,
31 * @var \Drupal\Core\Config\ConfigFactoryInterface
33 protected $configFactory;
36 * The profile list needed by this module list.
38 * @var \Drupal\Core\Extension\ExtensionList
40 protected $profileList;
43 * Constructs a new ModuleExtensionList instance.
49 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
51 * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
53 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
55 * @param \Drupal\Core\State\StateInterface $state
57 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
59 * @param \Drupal\Core\Extension\ExtensionList $profile_list
60 * The site profile listing.
61 * @param string $install_profile
62 * The install profile used by the site.
63 * @param array[] $container_modules_info
64 * (optional) The module locations coming from the compiled container.
66 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 = []) {
67 parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $install_profile);
69 $this->configFactory = $config_factory;
70 $this->profileList = $profile_list;
72 // Use the information from the container. This is an optimization.
73 foreach ($container_modules_info as $module_name => $info) {
74 $this->setPathname($module_name, $info['pathname']);
81 protected function getExtensionDiscovery() {
82 $discovery = parent::getExtensionDiscovery();
84 if ($active_profile = $this->getActiveProfile()) {
85 $discovery->setProfileDirectories($this->getProfileDirectories($discovery));
92 * Finds all installation profile paths.
94 * @param \Drupal\Core\Extension\ExtensionDiscovery $discovery
95 * The extension discovery.
98 * Paths to all installation profiles.
100 protected function getProfileDirectories(ExtensionDiscovery $discovery) {
101 $discovery->setProfileDirectories([]);
102 $all_profiles = $discovery->scan('profile');
103 $active_profile = $all_profiles[$this->installProfile];
104 $profiles = array_intersect_key($all_profiles, $this->configFactory->get('core.extension')->get('module') ?: [$active_profile->getName() => 0]);
106 // If a module is within a profile directory but specifies another
107 // profile for testing, it needs to be found in the parent profile.
108 $parent_profile = $this->configFactory->get('simpletest.settings')->get('parent_profile');
110 if ($parent_profile && !isset($profiles[$parent_profile])) {
111 // In case both profile directories contain the same extension, the
112 // actual profile always has precedence.
113 $profiles = [$parent_profile => $all_profiles[$parent_profile]] + $profiles;
116 $profile_directories = array_map(function (Extension $profile) {
117 return $profile->getPath();
119 return $profile_directories;
123 * Gets the processed active profile object, or null.
125 * @return \Drupal\Core\Extension\Extension|null
126 * The active profile, if there is one.
128 protected function getActiveProfile() {
129 $profiles = $this->profileList->getList();
130 if ($this->installProfile && isset($profiles[$this->installProfile])) {
131 return $profiles[$this->installProfile];
140 protected function doScanExtensions() {
141 $extensions = parent::doScanExtensions();
143 $profiles = $this->profileList->getList();
144 // Modify the active profile object that was previously added to the module
146 if ($this->installProfile && isset($profiles[$this->installProfile])) {
147 $extensions[$this->installProfile] = $profiles[$this->installProfile];
156 protected function doList() {
158 $extensions = parent::doList();
159 // It is possible that a module was marked as required by
160 // hook_system_info_alter() and modules that it depends on are not required.
161 foreach ($extensions as $extension) {
162 $this->ensureRequiredDependencies($extension, $extensions);
165 // Add status, weight, and schema version.
166 $installed_modules = $this->configFactory->get('core.extension')->get('module') ?: [];
167 foreach ($extensions as $name => $module) {
168 $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0;
169 $module->status = (int) isset($installed_modules[$name]);
170 $module->schema_version = SCHEMA_UNINSTALLED;
172 $extensions = $this->moduleHandler->buildModuleDependencies($extensions);
174 if ($this->installProfile && $extensions[$this->installProfile]) {
175 $active_profile = $extensions[$this->installProfile];
177 // Installation profile hooks are always executed last.
178 $active_profile->weight = 1000;
180 // Installation profiles are hidden by default, unless explicitly
181 // specified otherwise in the .info.yml file.
182 if (!isset($active_profile->info['hidden'])) {
183 $active_profile->info['hidden'] = TRUE;
186 // The installation profile is required.
187 $active_profile->info['required'] = TRUE;
188 // Add a default distribution name if the profile did not provide one.
189 // @see install_profile_info()
190 // @see drupal_install_profile_distribution_name()
191 if (!isset($active_profile->info['distribution']['name'])) {
192 $active_profile->info['distribution']['name'] = 'Drupal';
202 protected function getInstalledExtensionNames() {
203 return array_keys($this->moduleHandler->getModuleList());
207 * Marks dependencies of required modules as 'required', recursively.
209 * @param \Drupal\Core\Extension\Extension $module
210 * The module extension object.
211 * @param \Drupal\Core\Extension\Extension[] $modules
212 * Extension objects for all available modules.
214 protected function ensureRequiredDependencies(Extension $module, array $modules = []) {
215 if (!empty($module->info['required'])) {
216 foreach ($module->info['dependencies'] as $dependency) {
217 $dependency_name = ModuleHandler::parseDependency($dependency)['name'];
218 if (!isset($modules[$dependency_name]->info['required'])) {
219 $modules[$dependency_name]->info['required'] = TRUE;
220 $modules[$dependency_name]->info['explanation'] = $this->t('Dependency of required module @module', ['@module' => $module->info['name']]);
221 // Ensure any dependencies it has are required.
222 $this->ensureRequiredDependencies($modules[$dependency_name], $modules);