ModuleExtensionList.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <?php
  2. namespace Drupal\Core\Extension;
  3. use Drupal\Core\Cache\CacheBackendInterface;
  4. use Drupal\Core\Config\ConfigFactoryInterface;
  5. use Drupal\Core\State\StateInterface;
  6. use Drupal\Core\StringTranslation\StringTranslationTrait;
  7. /**
  8. * Provides a list of available modules.
  9. *
  10. * @internal
  11. * This class is not yet stable and therefore there are no guarantees that the
  12. * internal implementations including constructor signature and protected
  13. * properties / methods will not change over time. This will be reviewed after
  14. * https://www.drupal.org/project/drupal/issues/2940481
  15. */
  16. class ModuleExtensionList extends ExtensionList {
  17. use StringTranslationTrait;
  18. /**
  19. * {@inheritdoc}
  20. */
  21. protected $defaults = [
  22. 'dependencies' => [],
  23. 'description' => '',
  24. 'package' => 'Other',
  25. 'version' => NULL,
  26. 'php' => DRUPAL_MINIMUM_PHP,
  27. ];
  28. /**
  29. * The config factory.
  30. *
  31. * @var \Drupal\Core\Config\ConfigFactoryInterface
  32. */
  33. protected $configFactory;
  34. /**
  35. * The profile list needed by this module list.
  36. *
  37. * @var \Drupal\Core\Extension\ExtensionList
  38. */
  39. protected $profileList;
  40. /**
  41. * Constructs a new ModuleExtensionList instance.
  42. *
  43. * @param string $root
  44. * The app root.
  45. * @param string $type
  46. * The extension type.
  47. * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  48. * The cache.
  49. * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
  50. * The info parser.
  51. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  52. * The module handler.
  53. * @param \Drupal\Core\State\StateInterface $state
  54. * The state.
  55. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  56. * The config factory.
  57. * @param \Drupal\Core\Extension\ExtensionList $profile_list
  58. * The site profile listing.
  59. * @param string $install_profile
  60. * The install profile used by the site.
  61. * @param array[] $container_modules_info
  62. * (optional) The module locations coming from the compiled container.
  63. */
  64. 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 = []) {
  65. parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $install_profile);
  66. $this->configFactory = $config_factory;
  67. $this->profileList = $profile_list;
  68. // Use the information from the container. This is an optimization.
  69. foreach ($container_modules_info as $module_name => $info) {
  70. $this->setPathname($module_name, $info['pathname']);
  71. }
  72. }
  73. /**
  74. * {@inheritdoc}
  75. */
  76. protected function getExtensionDiscovery() {
  77. $discovery = parent::getExtensionDiscovery();
  78. if ($active_profile = $this->getActiveProfile()) {
  79. $discovery->setProfileDirectories($this->getProfileDirectories($discovery));
  80. }
  81. return $discovery;
  82. }
  83. /**
  84. * Finds all installation profile paths.
  85. *
  86. * @param \Drupal\Core\Extension\ExtensionDiscovery $discovery
  87. * The extension discovery.
  88. *
  89. * @return string[]
  90. * Paths to all installation profiles.
  91. */
  92. protected function getProfileDirectories(ExtensionDiscovery $discovery) {
  93. $discovery->setProfileDirectories([]);
  94. $all_profiles = $discovery->scan('profile');
  95. $active_profile = $all_profiles[$this->installProfile];
  96. $profiles = array_intersect_key($all_profiles, $this->configFactory->get('core.extension')->get('module') ?: [$active_profile->getName() => 0]);
  97. $profile_directories = array_map(function (Extension $profile) {
  98. return $profile->getPath();
  99. }, $profiles);
  100. return $profile_directories;
  101. }
  102. /**
  103. * Gets the processed active profile object, or null.
  104. *
  105. * @return \Drupal\Core\Extension\Extension|null
  106. * The active profile, if there is one.
  107. */
  108. protected function getActiveProfile() {
  109. $profiles = $this->profileList->getList();
  110. if ($this->installProfile && isset($profiles[$this->installProfile])) {
  111. return $profiles[$this->installProfile];
  112. }
  113. return NULL;
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. protected function doScanExtensions() {
  119. $extensions = parent::doScanExtensions();
  120. $profiles = $this->profileList->getList();
  121. // Modify the active profile object that was previously added to the module
  122. // list.
  123. if ($this->installProfile && isset($profiles[$this->installProfile])) {
  124. $extensions[$this->installProfile] = $profiles[$this->installProfile];
  125. }
  126. return $extensions;
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. protected function doList() {
  132. // Find modules.
  133. $extensions = parent::doList();
  134. // It is possible that a module was marked as required by
  135. // hook_system_info_alter() and modules that it depends on are not required.
  136. foreach ($extensions as $extension) {
  137. $this->ensureRequiredDependencies($extension, $extensions);
  138. }
  139. // Add status, weight, and schema version.
  140. $installed_modules = $this->configFactory->get('core.extension')->get('module') ?: [];
  141. foreach ($extensions as $name => $module) {
  142. $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0;
  143. $module->status = (int) isset($installed_modules[$name]);
  144. $module->schema_version = SCHEMA_UNINSTALLED;
  145. }
  146. $extensions = $this->moduleHandler->buildModuleDependencies($extensions);
  147. if ($this->installProfile && $extensions[$this->installProfile]) {
  148. $active_profile = $extensions[$this->installProfile];
  149. // Installation profile hooks are always executed last.
  150. $active_profile->weight = 1000;
  151. // Installation profiles are hidden by default, unless explicitly
  152. // specified otherwise in the .info.yml file.
  153. if (!isset($active_profile->info['hidden'])) {
  154. $active_profile->info['hidden'] = TRUE;
  155. }
  156. // The installation profile is required.
  157. $active_profile->info['required'] = TRUE;
  158. // Add a default distribution name if the profile did not provide one.
  159. // @see install_profile_info()
  160. // @see drupal_install_profile_distribution_name()
  161. if (!isset($active_profile->info['distribution']['name'])) {
  162. $active_profile->info['distribution']['name'] = 'Drupal';
  163. }
  164. }
  165. return $extensions;
  166. }
  167. /**
  168. * {@inheritdoc}
  169. */
  170. protected function getInstalledExtensionNames() {
  171. return array_keys($this->moduleHandler->getModuleList());
  172. }
  173. /**
  174. * Marks dependencies of required modules as 'required', recursively.
  175. *
  176. * @param \Drupal\Core\Extension\Extension $module
  177. * The module extension object.
  178. * @param \Drupal\Core\Extension\Extension[] $modules
  179. * Extension objects for all available modules.
  180. */
  181. protected function ensureRequiredDependencies(Extension $module, array $modules = []) {
  182. if (!empty($module->info['required'])) {
  183. foreach ($module->info['dependencies'] as $dependency) {
  184. $dependency_name = Dependency::createFromString($dependency)->getName();
  185. if (!isset($modules[$dependency_name]->info['required'])) {
  186. $modules[$dependency_name]->info['required'] = TRUE;
  187. $modules[$dependency_name]->info['explanation'] = $this->t('Dependency of required module @module', ['@module' => $module->info['name']]);
  188. // Ensure any dependencies it has are required.
  189. $this->ensureRequiredDependencies($modules[$dependency_name], $modules);
  190. }
  191. }
  192. }
  193. }
  194. }