ProjectInfo.php 7.55 KB
Newer Older
1
2
3
4
<?php

namespace Drupal\Core\Utility;

5
6
use Drupal\Core\Extension\Extension;

7
8
9
10
11
12
13
14
15
16
/**
 * Performs operations on drupal.org project data.
 */
class ProjectInfo {

  /**
   * Populates an array of project data.
   *
   * This iterates over a list of the installed modules or themes and groups
   * 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
20
   * uninstalled code should be included in the Available updates report or
   * not),those are only processed after $projects has been populated with
21
22
23
24
25
26
   * information about the enabled code. 'Hidden' modules and themes are
   * ignored if they are not installed. 'Hidden' Modules and themes in the
   * "Testing" package are ignored regardless of installation status.
   *
   * This function also records the latest change time on the .info.yml files
   * for each module or theme, which is important data which is used when
27
28
   * 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'.
35
   * @param bool $status
36
37
   *   Boolean that controls what status (enabled or uninstalled) to process out
   *   of the $list and add to the $projects array.
38
   * @param array $additional_whitelist
39
40
41
   *   (optional) Array of additional elements to be collected from the .info.yml
   *   file. Defaults to array().
   */
42
  function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_whitelist = array()) {
43
    foreach ($list as $file) {
44
45
      // Just projects with a matching status should be listed.
      if ($file->status != $status) {
46
47
48
49
50
51
52
53
        continue;
      }

      // Skip if the .info.yml file is broken.
      if (empty($file->info)) {
        continue;
      }

54
55
56
57
58
59
60
61
62
      // Skip if it's a hidden project and the project is not installed.
      if (!empty($file->info['hidden']) && empty($status)) {
        continue;
      }

      // Skip if it's a hidden project and the project is a test project. Tests
      // should use hook_system_info_alter() to test ProjectInfo's
      // functionality.
      if (!empty($file->info['hidden']) && isset($file->info['package']) && $file->info['package'] == 'Testing') {
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
        continue;
      }

      // If the .info.yml doesn't define the 'project', try to figure it out.
      if (!isset($file->info['project'])) {
        $file->info['project'] = $this->getProjectName($file);
      }

      // If we still don't know the 'project', give up.
      if (empty($file->info['project'])) {
        continue;
      }

      // If we don't already know it, grab the change time on the .info.yml file
      // itself. Note: we need to use the ctime, not the mtime (modification
      // time) since many (all?) tar implementations will go out of their way to
      // set the mtime on the files it creates to the timestamps recorded in the
      // tarball. We want to see the last time the file was changed on disk,
      // which is left alone by tar and correctly set to the time the .info.yml
      // file was unpacked.
      if (!isset($file->info['_info_file_ctime'])) {
84
        $file->info['_info_file_ctime'] = $file->getCTime();
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
      }

      if (!isset($file->info['datestamp'])) {
        $file->info['datestamp'] = 0;
      }

      $project_name = $file->info['project'];

      // Figure out what project type we're going to use to display this module
      // or theme. If the project name is 'drupal', we don't want it to show up
      // under the usual "Modules" section, we put it at a special "Drupal Core"
      // section at the top of the report.
      if ($project_name == 'drupal') {
        $project_display_type = 'core';
      }
      else {
        $project_display_type = $project_type;
      }
103
      if (empty($status)) {
104
        // If we're processing uninstalled modules or themes, append a suffix.
105
106
107
108
109
110
111
112
113
114
115
        $project_display_type .= '-disabled';
      }
      if (!isset($projects[$project_name])) {
        // Only process this if we haven't done this project, since a single
        // project can have multiple modules or themes.
        $projects[$project_name] = array(
          'name' => $project_name,
          // Only save attributes from the .info.yml file we care about so we do
          // not bloat our RAM usage needlessly.
          'info' => $this->filterProjectInfo($file->info, $additional_whitelist),
          'datestamp' => $file->info['datestamp'],
116
          'includes' => array($file->getName() => $file->info['name']),
117
118
119
120
121
122
123
          'project_type' => $project_display_type,
          'project_status' => $status,
        );
      }
      elseif ($projects[$project_name]['project_type'] == $project_display_type) {
        // Only add the file we're processing to the 'includes' array for this
        // 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
129
130
131
132
        $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
        $projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']);
      }
      elseif (empty($status)) {
        // If we have a project_name that matches, but the project_display_type
133
134
135
136
        // does not, it means we're processing a uninstalled module or theme
        // that belongs to a project that has some enabled code. In this case,
        // we add the uninstalled thing into a separate array for separate
        // display.
137
        $projects[$project_name]['disabled'][$file->getName()] = $file->info['name'];
138
139
140
141
142
143
144
      }
    }
  }

  /**
   * Determines what project a given file object belongs to.
   *
145
146
   * @param \Drupal\Core\Extension\Extension $file
   *   An extension object.
147
   *
148
   * @return string
149
150
   *   The canonical project short name.
   */
151
  function getProjectName(Extension $file) {
152
153
154
155
    $project_name = '';
    if (isset($file->info['project'])) {
      $project_name = $file->info['project'];
    }
156
    elseif (strpos($file->getPath(), 'core/modules') === 0) {
157
158
159
160
161
162
163
164
165
      $project_name = 'drupal';
    }
    return $project_name;
  }

  /**
   * Filters the project .info.yml data to only save attributes we need.
   *
   * @param array $info
166
167
   *   Array of .info.yml file data as returned by
   *   \Drupal\Core\Extension\InfoParser.
168
169
170
171
172
173
174
   * @param $additional_whitelist
   *   (optional) Array of additional elements to be collected from the .info.yml
   *   file. Defaults to array().
   *
   * @return
   *   Array of .info.yml file data we need for the update manager.
   *
175
   * @see \Drupal\Core\Utility\ProjectInfo::processInfoList()
176
177
178
179
180
181
182
183
184
185
186
187
188
   */
  function filterProjectInfo($info, $additional_whitelist = array()) {
    $whitelist = array(
      '_info_file_ctime',
      'datestamp',
      'major',
      'name',
      'package',
      'project',
      'project status url',
      'version',
    );
    $whitelist = array_merge($whitelist, $additional_whitelist);
189
    return array_intersect_key($info, array_combine($whitelist, $whitelist));
190
191
192
  }

}