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