ProjectInfo.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <?php
  2. namespace Drupal\Core\Utility;
  3. use Drupal\Core\Extension\Extension;
  4. /**
  5. * Performs operations on drupal.org project data.
  6. */
  7. class ProjectInfo {
  8. /**
  9. * Populates an array of project data.
  10. *
  11. * This iterates over a list of the installed modules or themes and groups
  12. * them by project and status. A few parts of this function assume that
  13. * enabled modules and themes are always processed first, and if uninstalled
  14. * modules or themes are being processed (there is a setting to control if
  15. * uninstalled code should be included in the Available updates report or
  16. * not),those are only processed after $projects has been populated with
  17. * information about the enabled code. 'Hidden' modules and themes are
  18. * ignored if they are not installed. 'Hidden' Modules and themes in the
  19. * "Testing" package are ignored regardless of installation status.
  20. *
  21. * This function also records the latest change time on the .info.yml files
  22. * for each module or theme, which is important data which is used when
  23. * deciding if the available update data should be invalidated.
  24. *
  25. * @param array $projects
  26. * Reference to the array of project data of what's installed on this site.
  27. * @param \Drupal\Core\Extension\Extension[] $list
  28. * Array of data to process to add the relevant info to the $projects array.
  29. * @param string $project_type
  30. * The kind of data in the list. Can be 'module' or 'theme'.
  31. * @param bool $status
  32. * Boolean that controls what status (enabled or uninstalled) to process out
  33. * of the $list and add to the $projects array.
  34. * @param array $additional_elements
  35. * (optional) Array of additional elements to be collected from the .info.yml
  36. * file. Defaults to array().
  37. */
  38. public function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_elements = []) {
  39. foreach ($list as $file) {
  40. // Just projects with a matching status should be listed.
  41. if ($file->status != $status) {
  42. continue;
  43. }
  44. // Skip if the .info.yml file is broken.
  45. if (empty($file->info)) {
  46. continue;
  47. }
  48. // Skip if it's a hidden project and the project is not installed.
  49. if (!empty($file->info['hidden']) && empty($status)) {
  50. continue;
  51. }
  52. // Skip if it's a hidden project and the project is a test project. Tests
  53. // should use hook_system_info_alter() to test ProjectInfo's
  54. // functionality.
  55. if (!empty($file->info['hidden']) && isset($file->info['package']) && $file->info['package'] == 'Testing') {
  56. continue;
  57. }
  58. // If the .info.yml doesn't define the 'project', try to figure it out.
  59. if (!isset($file->info['project'])) {
  60. $file->info['project'] = $this->getProjectName($file);
  61. }
  62. // If we still don't know the 'project', give up.
  63. if (empty($file->info['project'])) {
  64. continue;
  65. }
  66. // If we don't already know it, grab the change time on the .info.yml file
  67. // itself. Note: we need to use the ctime, not the mtime (modification
  68. // time) since many (all?) tar implementations will go out of their way to
  69. // set the mtime on the files it creates to the timestamps recorded in the
  70. // tarball. We want to see the last time the file was changed on disk,
  71. // which is left alone by tar and correctly set to the time the .info.yml
  72. // file was unpacked.
  73. if (!isset($file->info['_info_file_ctime'])) {
  74. $file->info['_info_file_ctime'] = $file->getCTime();
  75. }
  76. if (!isset($file->info['datestamp'])) {
  77. $file->info['datestamp'] = 0;
  78. }
  79. $project_name = $file->info['project'];
  80. // Figure out what project type we're going to use to display this module
  81. // or theme. If the project name is 'drupal', we don't want it to show up
  82. // under the usual "Modules" section, we put it at a special "Drupal Core"
  83. // section at the top of the report.
  84. if ($project_name == 'drupal') {
  85. $project_display_type = 'core';
  86. }
  87. else {
  88. $project_display_type = $project_type;
  89. }
  90. if (empty($status)) {
  91. // If we're processing uninstalled modules or themes, append a suffix.
  92. $project_display_type .= '-disabled';
  93. }
  94. if (!isset($projects[$project_name])) {
  95. // Only process this if we haven't done this project, since a single
  96. // project can have multiple modules or themes.
  97. $projects[$project_name] = [
  98. 'name' => $project_name,
  99. // Only save attributes from the .info.yml file we care about so we do
  100. // not bloat our RAM usage needlessly.
  101. 'info' => $this->filterProjectInfo($file->info, $additional_elements),
  102. 'datestamp' => $file->info['datestamp'],
  103. 'includes' => [$file->getName() => $file->info['name']],
  104. 'project_type' => $project_display_type,
  105. 'project_status' => $status,
  106. ];
  107. }
  108. elseif ($projects[$project_name]['project_type'] == $project_display_type) {
  109. // Only add the file we're processing to the 'includes' array for this
  110. // project if it is of the same type and status (which is encoded in the
  111. // $project_display_type). This prevents listing all the uninstalled
  112. // modules included with an enabled project if we happen to be checking
  113. // for uninstalled modules, too.
  114. $projects[$project_name]['includes'][$file->getName()] = $file->info['name'];
  115. $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
  116. $projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']);
  117. }
  118. elseif (empty($status)) {
  119. // If we have a project_name that matches, but the project_display_type
  120. // does not, it means we're processing a uninstalled module or theme
  121. // that belongs to a project that has some enabled code. In this case,
  122. // we add the uninstalled thing into a separate array for separate
  123. // display.
  124. $projects[$project_name]['disabled'][$file->getName()] = $file->info['name'];
  125. }
  126. }
  127. }
  128. /**
  129. * Determines what project a given file object belongs to.
  130. *
  131. * @param \Drupal\Core\Extension\Extension $file
  132. * An extension object.
  133. *
  134. * @return string
  135. * The canonical project short name.
  136. */
  137. public function getProjectName(Extension $file) {
  138. $project_name = '';
  139. if (isset($file->info['project'])) {
  140. $project_name = $file->info['project'];
  141. }
  142. elseif (strpos($file->getPath(), 'core/modules') === 0) {
  143. $project_name = 'drupal';
  144. }
  145. return $project_name;
  146. }
  147. /**
  148. * Filters the project .info.yml data to only save attributes we need.
  149. *
  150. * @param array $info
  151. * Array of .info.yml file data as returned by
  152. * \Drupal\Core\Extension\InfoParser.
  153. * @param $additional_elements
  154. * (optional) Array of additional elements to be collected from the .info.yml
  155. * file. Defaults to array().
  156. *
  157. * @return
  158. * Array of .info.yml file data we need for the update manager.
  159. *
  160. * @see \Drupal\Core\Utility\ProjectInfo::processInfoList()
  161. */
  162. public function filterProjectInfo($info, $additional_elements = []) {
  163. $elements = [
  164. '_info_file_ctime',
  165. 'datestamp',
  166. 'major',
  167. 'name',
  168. 'package',
  169. 'project',
  170. 'project status url',
  171. 'version',
  172. ];
  173. $elements = array_merge($elements, $additional_elements);
  174. return array_intersect_key($info, array_combine($elements, $elements));
  175. }
  176. }