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