ProjectInfo.php 9.11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
<?php

/**
 * @file
 * Contains \Drupal\Core\Utility\ProjectInfo.
 *
 * API for building lists of installed projects.
 */

namespace Drupal\Core\Utility;

/**
 * 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
   * enabled modules and themes are always processed first, and if disabled
   * modules or themes are being processed (there is a setting to control if
   * disabled code should be included in the Available updates report or not),
   * those are only processed after $projects has been populated with
   * information about the enabled code. 'Hidden' modules are always ignored.
   * 'Hidden' themes are ignored only if they have no enabled sub-themes.
   * 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
   * deciding if the available update data should be invalidated.
   *
32
   * @param $projects
33
   *   Reference to the array of project data of what's installed on this site.
34
   * @param $list
35
   *   Array of data to process to add the relevant info to the $projects array.
36
   * @param $project_type
37
   *   The kind of data in the list. Can be 'module' or 'theme'.
38
   * @param $status
39 40
   *   Boolean that controls what status (enabled or disabled) to process out of
   *   the $list and add to the $projects array.
41
   * @param $additional_whitelist
42 43 44
   *   (optional) Array of additional elements to be collected from the .info.yml
   *   file. Defaults to array().
   */
45
  function processInfoList(&$projects, $list, $project_type, $status, $additional_whitelist = array()) {
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    foreach ($list as $file) {
      // A disabled or hidden base theme of an enabled sub-theme still has all
      // of its code run by the sub-theme, so we include it in our "enabled"
      // projects list.
      if ($status && !empty($file->sub_themes)) {
        foreach ($file->sub_themes as $key => $name) {
          // Build a list of enabled sub-themes.
          if ($list[$key]->status) {
            $file->enabled_sub_themes[$key] = $name;
          }
        }
        // If the theme is disabled and there are no enabled subthemes, we
        // should ignore this base theme for the enabled case. If the site is
        // trying to display disabled themes, we'll catch it then.
        if (!$file->status && empty($file->enabled_sub_themes)) {
          continue;
        }
      }
      // Otherwise, just add projects of the proper status to our list.
      elseif ($file->status != $status) {
        continue;
      }

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

      // Skip if it's a hidden module or hidden theme without enabled sub-themes.
      if (!empty($file->info['hidden']) && empty($file->enabled_sub_themes)) {
        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'])) {
        $info_filename = dirname($file->uri) . '/' . $file->name . '.info.yml';
        $file->info['_info_file_ctime'] = filectime($info_filename);
      }

      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;
      }
      if (empty($status) && empty($file->enabled_sub_themes)) {
        // If we're processing disabled modules or themes, append a suffix.
        // However, we don't do this to a a base theme with enabled
        // subthemes, since we treat that case as if it is enabled.
        $project_display_type .= '-disabled';
      }
      // Add a list of sub-themes that "depend on" the project and a list of base
      // themes that are "required by" the project.
      if ($project_name == 'drupal') {
        // Drupal core is always required, so this extra info would be noise.
        $sub_themes = array();
        $base_themes = array();
      }
      else {
        // Add list of enabled sub-themes.
        $sub_themes = !empty($file->enabled_sub_themes) ? $file->enabled_sub_themes : array();
        // Add list of base themes.
        $base_themes = !empty($file->base_themes) ? $file->base_themes : array();
      }
      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'],
          'includes' => array($file->name => $file->info['name']),
          'project_type' => $project_display_type,
          'project_status' => $status,
          'sub_themes' => $sub_themes,
          'base_themes' => $base_themes,
        );
      }
      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
        // $project_display_type). This prevents listing all the disabled
        // modules included with an enabled project if we happen to be checking
        // for disabled modules, too.
        $projects[$project_name]['includes'][$file->name] = $file->info['name'];
        $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']);
        if (!empty($sub_themes)) {
          $projects[$project_name]['sub_themes'] += $sub_themes;
        }
        if (!empty($base_themes)) {
          $projects[$project_name]['base_themes'] += $base_themes;
        }
      }
      elseif (empty($status)) {
        // If we have a project_name that matches, but the project_display_type
        // does not, it means we're processing a disabled module or theme that
        // belongs to a project that has some enabled code. In this case, we add
        // the disabled thing into a separate array for separate display.
        $projects[$project_name]['disabled'][$file->name] = $file->info['name'];
      }
    }
  }

  /**
   * Determines what project a given file object belongs to.
   *
181 182
   * @param $file
   *   A file object as returned by system_get_files_database().
183
   *
184
   * @return
185
   *   The canonical project short name.
186 187
   *
   * @see system_get_files_database()
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
   */
  function getProjectName($file) {
    $project_name = '';
    if (isset($file->info['project'])) {
      $project_name = $file->info['project'];
    }
    elseif (isset($file->filename) && (strpos($file->filename, 'core/modules') === 0)) {
      $project_name = 'drupal';
    }
    return $project_name;
  }

  /**
   * Filters the project .info.yml data to only save attributes we need.
   *
   * @param array $info
204 205
   *   Array of .info.yml file data as returned by
   *   \Drupal\Core\Extension\InfoParser.
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
   * @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.
   *
   * @see \Drupal\Core\Utility\ProjectInfo->processInfoList()
   */
  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);
227
    return array_intersect_key($info, array_combine($whitelist, $whitelist));
228 229 230
  }

}