[b354002] | 1 | <?php |
---|
| 2 | |
---|
| 3 | /** |
---|
| 4 | * @file |
---|
| 5 | * Code required only when comparing available updates to existing data. |
---|
| 6 | */ |
---|
| 7 | |
---|
| 8 | /** |
---|
| 9 | * Fetch an array of installed and enabled projects. |
---|
| 10 | * |
---|
| 11 | * This is only responsible for generating an array of projects (taking into |
---|
| 12 | * account projects that include more than one module or theme). Other |
---|
| 13 | * information like the specific version and install type (official release, |
---|
| 14 | * dev snapshot, etc) is handled later in update_process_project_info() since |
---|
| 15 | * that logic is only required when preparing the status report, not for |
---|
| 16 | * fetching the available release data. |
---|
| 17 | * |
---|
| 18 | * This array is fairly expensive to construct, since it involves a lot of |
---|
| 19 | * disk I/O, so we cache the results into the {cache_update} table using the |
---|
| 20 | * 'update_project_projects' cache ID. However, since this is not the data |
---|
| 21 | * about available updates fetched from the network, it is ok to invalidate it |
---|
| 22 | * somewhat quickly. If we keep this data for very long, site administrators |
---|
| 23 | * are more likely to see incorrect results if they upgrade to a newer version |
---|
| 24 | * of a module or theme but do not visit certain pages that automatically |
---|
| 25 | * clear this cache. |
---|
| 26 | * |
---|
| 27 | * @see update_process_project_info() |
---|
| 28 | * @see update_calculate_project_data() |
---|
| 29 | * @see update_project_cache() |
---|
| 30 | */ |
---|
| 31 | function update_get_projects() { |
---|
| 32 | static $projects = array(); |
---|
| 33 | if (empty($projects)) { |
---|
| 34 | // Retrieve the projects from cache, if present. |
---|
| 35 | $projects = update_project_cache('update_project_projects'); |
---|
| 36 | if (empty($projects)) { |
---|
| 37 | // Still empty, so we have to rebuild the cache. |
---|
| 38 | _update_process_info_list($projects, module_rebuild_cache(), 'module'); |
---|
| 39 | _update_process_info_list($projects, system_theme_data(), 'theme'); |
---|
| 40 | // Allow other modules to alter projects before fetching and comparing. |
---|
| 41 | drupal_alter('update_projects', $projects); |
---|
| 42 | // Cache the site's project data for at most 1 hour. |
---|
| 43 | _update_cache_set('update_project_projects', $projects, time() + 3600); |
---|
| 44 | } |
---|
| 45 | } |
---|
| 46 | return $projects; |
---|
| 47 | } |
---|
| 48 | |
---|
| 49 | /** |
---|
| 50 | * Populate an array of project data. |
---|
| 51 | */ |
---|
| 52 | function _update_process_info_list(&$projects, $list, $project_type) { |
---|
| 53 | foreach ($list as $file) { |
---|
| 54 | // A disabled base theme of an enabled sub-theme still has all of its code |
---|
| 55 | // run by the sub-theme, so we include it in our "enabled" projects list. |
---|
| 56 | if (!$file->status && !empty($file->sub_themes)) { |
---|
| 57 | foreach ($file->sub_themes as $key => $name) { |
---|
| 58 | // Build a list of enabled sub-themes. |
---|
| 59 | if ($list[$key]->status) { |
---|
| 60 | $file->enabled_sub_themes[$key] = $name; |
---|
| 61 | } |
---|
| 62 | } |
---|
| 63 | // If there are no enabled subthemes, we should ingore this theme and go |
---|
| 64 | // on to the next one. |
---|
| 65 | if (empty($file->enabled_sub_themes)) { |
---|
| 66 | continue; |
---|
| 67 | } |
---|
| 68 | } |
---|
| 69 | elseif (empty($file->status)) { |
---|
| 70 | // Skip disabled modules or themes. |
---|
| 71 | continue; |
---|
| 72 | } |
---|
| 73 | |
---|
| 74 | // Skip if the .info file is broken. |
---|
| 75 | if (empty($file->info)) { |
---|
| 76 | continue; |
---|
| 77 | } |
---|
| 78 | |
---|
| 79 | // If the .info doesn't define the 'project', try to figure it out. |
---|
| 80 | if (!isset($file->info['project'])) { |
---|
| 81 | $file->info['project'] = update_get_project_name($file); |
---|
| 82 | } |
---|
| 83 | |
---|
| 84 | // If we still don't know the 'project', give up. |
---|
| 85 | if (empty($file->info['project'])) { |
---|
| 86 | continue; |
---|
| 87 | } |
---|
| 88 | |
---|
| 89 | // If we don't already know it, grab the change time on the .info file |
---|
| 90 | // itself. Note: we need to use the ctime, not the mtime (modification |
---|
| 91 | // time) since many (all?) tar implementations will go out of their way to |
---|
| 92 | // set the mtime on the files it creates to the timestamps recorded in the |
---|
| 93 | // tarball. We want to see the last time the file was changed on disk, |
---|
| 94 | // which is left alone by tar and correctly set to the time the .info file |
---|
| 95 | // was unpacked. |
---|
| 96 | if (!isset($file->info['_info_file_ctime'])) { |
---|
| 97 | $info_filename = dirname($file->filename) .'/'. $file->name .'.info'; |
---|
| 98 | $file->info['_info_file_ctime'] = filectime($info_filename); |
---|
| 99 | } |
---|
| 100 | |
---|
| 101 | if (!isset($file->info['datestamp'])) { |
---|
| 102 | $file->info['datestamp'] = 0; |
---|
| 103 | } |
---|
| 104 | |
---|
| 105 | $project_name = $file->info['project']; |
---|
| 106 | |
---|
| 107 | // Add a list of sub-themes that "depend on" the project and a list of base |
---|
| 108 | // themes that are "required by" the project. |
---|
| 109 | if ($project_name == 'drupal') { |
---|
| 110 | // Drupal core is always required, so this extra info would be noise. |
---|
| 111 | $sub_themes = array(); |
---|
| 112 | $base_themes = array(); |
---|
| 113 | } |
---|
| 114 | else { |
---|
| 115 | // Add list of enabled sub-themes. |
---|
| 116 | $sub_themes = !empty($file->enabled_sub_themes) ? $file->enabled_sub_themes : array(); |
---|
| 117 | // Add list of base themes. |
---|
| 118 | $base_themes = !empty($file->base_themes) ? $file->base_themes : array(); |
---|
| 119 | } |
---|
| 120 | |
---|
| 121 | if (!isset($projects[$project_name])) { |
---|
| 122 | // Only process this if we haven't done this project, since a single |
---|
| 123 | // project can have multiple modules or themes. |
---|
| 124 | $projects[$project_name] = array( |
---|
| 125 | 'name' => $project_name, |
---|
| 126 | // Only save attributes from the .info file we care about so we do not |
---|
| 127 | // bloat our RAM usage needlessly. |
---|
| 128 | 'info' => update_filter_project_info($file->info), |
---|
| 129 | 'datestamp' => $file->info['datestamp'], |
---|
| 130 | 'includes' => array($file->name => $file->info['name']), |
---|
| 131 | 'project_type' => $project_name == 'drupal' ? 'core' : $project_type, |
---|
| 132 | 'sub_themes' => $sub_themes, |
---|
| 133 | 'base_themes' => $base_themes, |
---|
| 134 | ); |
---|
| 135 | } |
---|
| 136 | else { |
---|
| 137 | $projects[$project_name]['includes'][$file->name] = $file->info['name']; |
---|
| 138 | $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']); |
---|
| 139 | $projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']); |
---|
| 140 | $projects[$project_name]['sub_themes'] = array_merge($projects[$project_name]['sub_themes'], $sub_themes); |
---|
| 141 | $projects[$project_name]['base_themes'] = array_merge($projects[$project_name]['base_themes'], $base_themes); |
---|
| 142 | } |
---|
| 143 | } |
---|
| 144 | } |
---|
| 145 | |
---|
| 146 | /** |
---|
| 147 | * Given a $file object (as returned by system_get_files_database()), figure |
---|
| 148 | * out what project it belongs to. |
---|
| 149 | * |
---|
| 150 | * @see system_get_files_database() |
---|
| 151 | */ |
---|
| 152 | function update_get_project_name($file) { |
---|
| 153 | $project_name = ''; |
---|
| 154 | if (isset($file->info['project'])) { |
---|
| 155 | $project_name = $file->info['project']; |
---|
| 156 | } |
---|
| 157 | elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core -') !== FALSE)) { |
---|
| 158 | $project_name = 'drupal'; |
---|
| 159 | } |
---|
| 160 | elseif (in_array($file->name, array('bluemarine', 'chameleon', 'garland', 'marvin', 'minnelli', 'pushbutton'))) { |
---|
| 161 | // Unfortunately, there's no way to tell if a theme is part of core, |
---|
| 162 | // so we must hard-code a list here. |
---|
| 163 | $project_name = 'drupal'; |
---|
| 164 | } |
---|
| 165 | return $project_name; |
---|
| 166 | } |
---|
| 167 | |
---|
| 168 | /** |
---|
| 169 | * Process the list of projects on the system to figure out the currently |
---|
| 170 | * installed versions, and other information that is required before we can |
---|
| 171 | * compare against the available releases to produce the status report. |
---|
| 172 | * |
---|
| 173 | * @param $projects |
---|
| 174 | * Array of project information from update_get_projects(). |
---|
| 175 | */ |
---|
| 176 | function update_process_project_info(&$projects) { |
---|
| 177 | foreach ($projects as $key => $project) { |
---|
| 178 | // Assume an official release until we see otherwise. |
---|
| 179 | $install_type = 'official'; |
---|
| 180 | |
---|
| 181 | $info = $project['info']; |
---|
| 182 | |
---|
| 183 | if (isset($info['version'])) { |
---|
| 184 | // Check for development snapshots |
---|
| 185 | if (preg_match('@(dev|HEAD)@', $info['version'])) { |
---|
| 186 | $install_type = 'dev'; |
---|
| 187 | } |
---|
| 188 | |
---|
| 189 | // Figure out what the currently installed major version is. We need |
---|
| 190 | // to handle both contribution (e.g. "5.x-1.3", major = 1) and core |
---|
| 191 | // (e.g. "5.1", major = 5) version strings. |
---|
| 192 | $matches = array(); |
---|
| 193 | if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) { |
---|
| 194 | $info['major'] = $matches[2]; |
---|
| 195 | } |
---|
| 196 | elseif (!isset($info['major'])) { |
---|
| 197 | // This would only happen for version strings that don't follow the |
---|
| 198 | // drupal.org convention. We let contribs define "major" in their |
---|
| 199 | // .info in this case, and only if that's missing would we hit this. |
---|
| 200 | $info['major'] = -1; |
---|
| 201 | } |
---|
| 202 | } |
---|
| 203 | else { |
---|
| 204 | // No version info available at all. |
---|
| 205 | $install_type = 'unknown'; |
---|
| 206 | $info['version'] = t('Unknown'); |
---|
| 207 | $info['major'] = -1; |
---|
| 208 | } |
---|
| 209 | |
---|
| 210 | // Finally, save the results we care about into the $projects array. |
---|
| 211 | $projects[$key]['existing_version'] = $info['version']; |
---|
| 212 | $projects[$key]['existing_major'] = $info['major']; |
---|
| 213 | $projects[$key]['install_type'] = $install_type; |
---|
| 214 | } |
---|
| 215 | } |
---|
| 216 | |
---|
| 217 | /** |
---|
| 218 | * Given the installed projects and the available release data retrieved from |
---|
| 219 | * remote servers, calculate the current status. |
---|
| 220 | * |
---|
| 221 | * This function is the heart of the update status feature. It iterates over |
---|
| 222 | * every currently installed project. For each one, it first checks if the |
---|
| 223 | * project has been flagged with a special status like "unsupported" or |
---|
| 224 | * "insecure", or if the project node itself has been unpublished. In any of |
---|
| 225 | * those cases, the project is marked with an error and the next project is |
---|
| 226 | * considered. |
---|
| 227 | * |
---|
| 228 | * If the project itself is valid, the function decides what major release |
---|
| 229 | * series to consider. The project defines what the currently supported major |
---|
| 230 | * versions are for each version of core, so the first step is to make sure |
---|
| 231 | * the current version is still supported. If so, that's the target version. |
---|
| 232 | * If the current version is unsupported, the project maintainer's recommended |
---|
| 233 | * major version is used. There's also a check to make sure that this function |
---|
| 234 | * never recommends an earlier release than the currently installed major |
---|
| 235 | * version. |
---|
| 236 | * |
---|
| 237 | * Given a target major version, it scans the available releases looking for |
---|
| 238 | * the specific release to recommend (avoiding beta releases and development |
---|
| 239 | * snapshots if possible). This is complicated to describe, but an example |
---|
| 240 | * will help clarify. For the target major version, find the highest patch |
---|
| 241 | * level. If there is a release at that patch level with no extra ("beta", |
---|
| 242 | * etc), then we recommend the release at that patch level with the most |
---|
| 243 | * recent release date. If every release at that patch level has extra (only |
---|
| 244 | * betas), then recommend the latest release from the previous patch |
---|
| 245 | * level. For example: |
---|
| 246 | * |
---|
| 247 | * 1.6-bugfix <-- recommended version because 1.6 already exists. |
---|
| 248 | * 1.6 |
---|
| 249 | * |
---|
| 250 | * or |
---|
| 251 | * |
---|
| 252 | * 1.6-beta |
---|
| 253 | * 1.5 <-- recommended version because no 1.6 exists. |
---|
| 254 | * 1.4 |
---|
| 255 | * |
---|
| 256 | * It also looks for the latest release from the same major version, even a |
---|
| 257 | * beta release, to display to the user as the "Latest version" option. |
---|
| 258 | * Additionally, it finds the latest official release from any higher major |
---|
| 259 | * versions that have been released to provide a set of "Also available" |
---|
| 260 | * options. |
---|
| 261 | * |
---|
| 262 | * Finally, and most importantly, it keeps scanning the release history until |
---|
| 263 | * it gets to the currently installed release, searching for anything marked |
---|
| 264 | * as a security update. If any security updates have been found between the |
---|
| 265 | * recommended release and the installed version, all of the releases that |
---|
| 266 | * included a security fix are recorded so that the site administrator can be |
---|
| 267 | * warned their site is insecure, and links pointing to the release notes for |
---|
| 268 | * each security update can be included (which, in turn, will link to the |
---|
| 269 | * official security announcements for each vulnerability). |
---|
| 270 | * |
---|
| 271 | * This function relies on the fact that the .xml release history data comes |
---|
| 272 | * sorted based on major version and patch level, then finally by release date |
---|
| 273 | * if there are multiple releases such as betas from the same major.patch |
---|
| 274 | * version (e.g. 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development |
---|
| 275 | * snapshots for a given major version are always listed last. |
---|
| 276 | * |
---|
| 277 | * The results of this function are expensive to compute, especially on sites |
---|
| 278 | * with lots of modules or themes, since it involves a lot of comparisons and |
---|
| 279 | * other operations. Therefore, we cache the results into the {cache_update} |
---|
| 280 | * table using the 'update_project_data' cache ID. However, since this is not |
---|
| 281 | * the data about available updates fetched from the network, it is ok to |
---|
| 282 | * invalidate it somewhat quickly. If we keep this data for very long, site |
---|
| 283 | * administrators are more likely to see incorrect results if they upgrade to |
---|
| 284 | * a newer version of a module or theme but do not visit certain pages that |
---|
| 285 | * automatically clear this cache. |
---|
| 286 | * |
---|
| 287 | * @param $available |
---|
| 288 | * Array of data about available project releases. |
---|
| 289 | * |
---|
| 290 | * @see update_get_available() |
---|
| 291 | * @see update_get_projects() |
---|
| 292 | * @see update_process_project_info() |
---|
| 293 | * @see update_project_cache() |
---|
| 294 | */ |
---|
| 295 | function update_calculate_project_data($available) { |
---|
| 296 | // Retrieve the projects from cache, if present. |
---|
| 297 | $projects = update_project_cache('update_project_data'); |
---|
| 298 | // If $projects is empty, then the cache must be rebuilt. |
---|
| 299 | // Otherwise, return the cached data and skip the rest of the function. |
---|
| 300 | if (!empty($projects)) { |
---|
| 301 | return $projects; |
---|
| 302 | } |
---|
| 303 | $projects = update_get_projects(); |
---|
| 304 | update_process_project_info($projects); |
---|
| 305 | foreach ($projects as $project => $project_info) { |
---|
| 306 | if (isset($available[$project])) { |
---|
| 307 | |
---|
| 308 | // If the project status is marked as something bad, there's nothing |
---|
| 309 | // else to consider. |
---|
| 310 | if (isset($available[$project]['project_status'])) { |
---|
| 311 | switch ($available[$project]['project_status']) { |
---|
| 312 | case 'insecure': |
---|
| 313 | $projects[$project]['status'] = UPDATE_NOT_SECURE; |
---|
| 314 | if (empty($projects[$project]['extra'])) { |
---|
| 315 | $projects[$project]['extra'] = array(); |
---|
| 316 | } |
---|
| 317 | $projects[$project]['extra'][] = array( |
---|
| 318 | 'class' => 'project-not-secure', |
---|
| 319 | 'label' => t('Project not secure'), |
---|
| 320 | 'data' => t('This project has been labeled insecure by the Drupal security team, and is no longer available for download. Immediately disabling everything included by this project is strongly recommended!'), |
---|
| 321 | ); |
---|
| 322 | break; |
---|
| 323 | case 'unpublished': |
---|
| 324 | case 'revoked': |
---|
| 325 | $projects[$project]['status'] = UPDATE_REVOKED; |
---|
| 326 | if (empty($projects[$project]['extra'])) { |
---|
| 327 | $projects[$project]['extra'] = array(); |
---|
| 328 | } |
---|
| 329 | $projects[$project]['extra'][] = array( |
---|
| 330 | 'class' => 'project-revoked', |
---|
| 331 | 'label' => t('Project revoked'), |
---|
| 332 | 'data' => t('This project has been revoked, and is no longer available for download. Disabling everything included by this project is strongly recommended!'), |
---|
| 333 | ); |
---|
| 334 | break; |
---|
| 335 | case 'unsupported': |
---|
| 336 | $projects[$project]['status'] = UPDATE_NOT_SUPPORTED; |
---|
| 337 | if (empty($projects[$project]['extra'])) { |
---|
| 338 | $projects[$project]['extra'] = array(); |
---|
| 339 | } |
---|
| 340 | $projects[$project]['extra'][] = array( |
---|
| 341 | 'class' => 'project-not-supported', |
---|
| 342 | 'label' => t('Project not supported'), |
---|
| 343 | 'data' => t('This project is no longer supported, and is no longer available for download. Disabling everything included by this project is strongly recommended!'), |
---|
| 344 | ); |
---|
| 345 | break; |
---|
| 346 | case 'not-fetched': |
---|
| 347 | $projects[$project]['status'] = UPDATE_NOT_FETCHED; |
---|
| 348 | $projects[$project]['reason'] = t('Failed to fetch available update data'); |
---|
| 349 | break; |
---|
| 350 | |
---|
| 351 | default: |
---|
| 352 | // Assume anything else (e.g. 'published') is valid and we should |
---|
| 353 | // perform the rest of the logic in this function. |
---|
| 354 | break; |
---|
| 355 | } |
---|
| 356 | } |
---|
| 357 | |
---|
| 358 | if (!empty($projects[$project]['status'])) { |
---|
| 359 | // We already know the status for this project, so there's nothing |
---|
| 360 | // else to compute. Just record everything else we fetched from the |
---|
| 361 | // XML file into our projects array and move to the next project. |
---|
| 362 | $projects[$project] += $available[$project]; |
---|
| 363 | continue; |
---|
| 364 | } |
---|
| 365 | |
---|
| 366 | // Figure out the target major version. |
---|
| 367 | $existing_major = $project_info['existing_major']; |
---|
| 368 | $supported_majors = array(); |
---|
| 369 | if (isset($available[$project]['supported_majors'])) { |
---|
| 370 | $supported_majors = explode(',', $available[$project]['supported_majors']); |
---|
| 371 | } |
---|
| 372 | elseif (isset($available[$project]['default_major'])) { |
---|
| 373 | // Older release history XML file without supported or recommended. |
---|
| 374 | $supported_majors[] = $available[$project]['default_major']; |
---|
| 375 | } |
---|
| 376 | |
---|
| 377 | if (in_array($existing_major, $supported_majors)) { |
---|
| 378 | // Still supported, stay at the current major version. |
---|
| 379 | $target_major = $existing_major; |
---|
| 380 | } |
---|
| 381 | elseif (isset($available[$project]['recommended_major'])) { |
---|
| 382 | // Since 'recommended_major' is defined, we know this is the new XML |
---|
| 383 | // format. Therefore, we know the current release is unsupported since |
---|
| 384 | // its major version was not in the 'supported_majors' list. We should |
---|
| 385 | // find the best release from the recommended major version. |
---|
| 386 | $target_major = $available[$project]['recommended_major']; |
---|
| 387 | $projects[$project]['status'] = UPDATE_NOT_SUPPORTED; |
---|
| 388 | } |
---|
| 389 | elseif (isset($available[$project]['default_major'])) { |
---|
| 390 | // Older release history XML file without recommended, so recommend |
---|
| 391 | // the currently defined "default_major" version. |
---|
| 392 | $target_major = $available[$project]['default_major']; |
---|
| 393 | } |
---|
| 394 | else { |
---|
| 395 | // Malformed XML file? Stick with the current version. |
---|
| 396 | $target_major = $existing_major; |
---|
| 397 | } |
---|
| 398 | |
---|
| 399 | // Make sure we never tell the admin to downgrade. If we recommended an |
---|
| 400 | // earlier version than the one they're running, they'd face an |
---|
| 401 | // impossible data migration problem, since Drupal never supports a DB |
---|
| 402 | // downgrade path. In the unfortunate case that what they're running is |
---|
| 403 | // unsupported, and there's nothing newer for them to upgrade to, we |
---|
| 404 | // can't print out a "Recommended version", but just have to tell them |
---|
| 405 | // what they have is unsupported and let them figure it out. |
---|
| 406 | $target_major = max($existing_major, $target_major); |
---|
| 407 | |
---|
| 408 | $version_patch_changed = ''; |
---|
| 409 | $patch = ''; |
---|
| 410 | |
---|
| 411 | // Defend ourselves from XML history files that contain no releases. |
---|
| 412 | if (empty($available[$project]['releases'])) { |
---|
| 413 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
| 414 | $projects[$project]['reason'] = t('No available releases found'); |
---|
| 415 | continue; |
---|
| 416 | } |
---|
| 417 | foreach ($available[$project]['releases'] as $version => $release) { |
---|
| 418 | // First, if this is the existing release, check a few conditions. |
---|
| 419 | if ($projects[$project]['existing_version'] === $version) { |
---|
| 420 | if (isset($release['terms']['Release type']) && |
---|
| 421 | in_array('Insecure', $release['terms']['Release type'])) { |
---|
| 422 | $projects[$project]['status'] = UPDATE_NOT_SECURE; |
---|
| 423 | } |
---|
| 424 | elseif ($release['status'] == 'unpublished') { |
---|
| 425 | $projects[$project]['status'] = UPDATE_REVOKED; |
---|
| 426 | if (empty($projects[$project]['extra'])) { |
---|
| 427 | $projects[$project]['extra'] = array(); |
---|
| 428 | } |
---|
| 429 | $projects[$project]['extra'][] = array( |
---|
| 430 | 'class' => 'release-revoked', |
---|
| 431 | 'label' => t('Release revoked'), |
---|
| 432 | 'data' => t('Your currently installed release has been revoked, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'), |
---|
| 433 | ); |
---|
| 434 | } |
---|
| 435 | elseif (isset($release['terms']['Release type']) && |
---|
| 436 | in_array('Unsupported', $release['terms']['Release type'])) { |
---|
| 437 | $projects[$project]['status'] = UPDATE_NOT_SUPPORTED; |
---|
| 438 | if (empty($projects[$project]['extra'])) { |
---|
| 439 | $projects[$project]['extra'] = array(); |
---|
| 440 | } |
---|
| 441 | $projects[$project]['extra'][] = array( |
---|
| 442 | 'class' => 'release-not-supported', |
---|
| 443 | 'label' => t('Release not supported'), |
---|
| 444 | 'data' => t('Your currently installed release is now unsupported, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'), |
---|
| 445 | ); |
---|
| 446 | } |
---|
| 447 | } |
---|
| 448 | |
---|
| 449 | // Otherwise, ignore unpublished, insecure, or unsupported releases. |
---|
| 450 | if ($release['status'] == 'unpublished' || |
---|
| 451 | (isset($release['terms']['Release type']) && |
---|
| 452 | (in_array('Insecure', $release['terms']['Release type']) || |
---|
| 453 | in_array('Unsupported', $release['terms']['Release type'])))) { |
---|
| 454 | continue; |
---|
| 455 | } |
---|
| 456 | |
---|
| 457 | // See if this is a higher major version than our target and yet still |
---|
| 458 | // supported. If so, record it as an "Also available" release. |
---|
| 459 | if ($release['version_major'] > $target_major) { |
---|
| 460 | if (in_array($release['version_major'], $supported_majors)) { |
---|
| 461 | if (!isset($available[$project]['also'])) { |
---|
| 462 | $available[$project]['also'] = array(); |
---|
| 463 | } |
---|
| 464 | if (!isset($available[$project]['also'][$release['version_major']])) { |
---|
| 465 | $available[$project]['also'][$release['version_major']] = $version; |
---|
| 466 | } |
---|
| 467 | } |
---|
| 468 | // Otherwise, this release can't matter to us, since it's neither |
---|
| 469 | // from the release series we're currently using nor the recommended |
---|
| 470 | // release. We don't even care about security updates for this |
---|
| 471 | // branch, since if a project maintainer puts out a security release |
---|
| 472 | // at a higher major version and not at the lower major version, |
---|
| 473 | // they must remove the lower version from the supported major |
---|
| 474 | // versions at the same time, in which case we won't hit this code. |
---|
| 475 | continue; |
---|
| 476 | } |
---|
| 477 | |
---|
| 478 | // Look for the 'latest version' if we haven't found it yet. Latest is |
---|
| 479 | // defined as the most recent version for the target major version. |
---|
| 480 | if (!isset($available[$project]['latest_version']) |
---|
| 481 | && $release['version_major'] == $target_major) { |
---|
| 482 | $available[$project]['latest_version'] = $version; |
---|
| 483 | } |
---|
| 484 | |
---|
| 485 | // Look for the development snapshot release for this branch. |
---|
| 486 | if (!isset($available[$project]['dev_version']) |
---|
| 487 | && $release['version_major'] == $target_major |
---|
| 488 | && isset($release['version_extra']) |
---|
| 489 | && $release['version_extra'] == 'dev') { |
---|
| 490 | $available[$project]['dev_version'] = $version; |
---|
| 491 | } |
---|
| 492 | |
---|
| 493 | // Look for the 'recommended' version if we haven't found it yet (see |
---|
| 494 | // phpdoc at the top of this function for the definition). |
---|
| 495 | if (!isset($available[$project]['recommended']) |
---|
| 496 | && $release['version_major'] == $target_major |
---|
| 497 | && isset($release['version_patch'])) { |
---|
| 498 | if ($patch != $release['version_patch']) { |
---|
| 499 | $patch = $release['version_patch']; |
---|
| 500 | $version_patch_changed = $release['version']; |
---|
| 501 | } |
---|
| 502 | if (empty($release['version_extra']) && $patch == $release['version_patch']) { |
---|
| 503 | $available[$project]['recommended'] = $version_patch_changed; |
---|
| 504 | } |
---|
| 505 | } |
---|
| 506 | |
---|
| 507 | // Stop searching once we hit the currently installed version. |
---|
| 508 | if ($projects[$project]['existing_version'] === $version) { |
---|
| 509 | break; |
---|
| 510 | } |
---|
| 511 | |
---|
| 512 | // If we're running a dev snapshot and have a timestamp, stop |
---|
| 513 | // searching for security updates once we hit an official release |
---|
| 514 | // older than what we've got. Allow 100 seconds of leeway to handle |
---|
| 515 | // differences between the datestamp in the .info file and the |
---|
| 516 | // timestamp of the tarball itself (which are usually off by 1 or 2 |
---|
| 517 | // seconds) so that we don't flag that as a new release. |
---|
| 518 | if ($projects[$project]['install_type'] == 'dev') { |
---|
| 519 | if (empty($projects[$project]['datestamp'])) { |
---|
| 520 | // We don't have current timestamp info, so we can't know. |
---|
| 521 | continue; |
---|
| 522 | } |
---|
| 523 | elseif (isset($release['date']) && ($projects[$project]['datestamp'] + 100 > $release['date'])) { |
---|
| 524 | // We're newer than this, so we can skip it. |
---|
| 525 | continue; |
---|
| 526 | } |
---|
| 527 | } |
---|
| 528 | |
---|
| 529 | // See if this release is a security update. |
---|
| 530 | if (isset($release['terms']['Release type']) |
---|
| 531 | && in_array('Security update', $release['terms']['Release type'])) { |
---|
| 532 | $projects[$project]['security updates'][] = $release; |
---|
| 533 | } |
---|
| 534 | } |
---|
| 535 | |
---|
| 536 | // If we were unable to find a recommended version, then make the latest |
---|
| 537 | // version the recommended version if possible. |
---|
| 538 | if (!isset($available[$project]['recommended']) && isset($available[$project]['latest_version'])) { |
---|
| 539 | $available[$project]['recommended'] = $available[$project]['latest_version']; |
---|
| 540 | } |
---|
| 541 | |
---|
| 542 | // Stash the info about available releases into our $projects array. |
---|
| 543 | $projects[$project] += $available[$project]; |
---|
| 544 | |
---|
| 545 | // |
---|
| 546 | // Check to see if we need an update or not. |
---|
| 547 | // |
---|
| 548 | |
---|
| 549 | if (!empty($projects[$project]['security updates'])) { |
---|
| 550 | // If we found security updates, that always trumps any other status. |
---|
| 551 | $projects[$project]['status'] = UPDATE_NOT_SECURE; |
---|
| 552 | } |
---|
| 553 | |
---|
| 554 | if (isset($projects[$project]['status'])) { |
---|
| 555 | // If we already know the status, we're done. |
---|
| 556 | continue; |
---|
| 557 | } |
---|
| 558 | |
---|
| 559 | // If we don't know what to recommend, there's nothing we can report. |
---|
| 560 | // Bail out early. |
---|
| 561 | if (!isset($projects[$project]['recommended'])) { |
---|
| 562 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
| 563 | $projects[$project]['reason'] = t('No available releases found'); |
---|
| 564 | continue; |
---|
| 565 | } |
---|
| 566 | |
---|
| 567 | // If we're running a dev snapshot, compare the date of the dev snapshot |
---|
| 568 | // with the latest official version, and record the absolute latest in |
---|
| 569 | // 'latest_dev' so we can correctly decide if there's a newer release |
---|
| 570 | // than our current snapshot. |
---|
| 571 | if ($projects[$project]['install_type'] == 'dev') { |
---|
| 572 | if (isset($available[$project]['dev_version']) && $available[$project]['releases'][$available[$project]['dev_version']]['date'] > $available[$project]['releases'][$available[$project]['latest_version']]['date']) { |
---|
| 573 | $projects[$project]['latest_dev'] = $available[$project]['dev_version']; |
---|
| 574 | } |
---|
| 575 | else { |
---|
| 576 | $projects[$project]['latest_dev'] = $available[$project]['latest_version']; |
---|
| 577 | } |
---|
| 578 | } |
---|
| 579 | |
---|
| 580 | // Figure out the status, based on what we've seen and the install type. |
---|
| 581 | switch ($projects[$project]['install_type']) { |
---|
| 582 | case 'official': |
---|
| 583 | if ($projects[$project]['existing_version'] === $projects[$project]['recommended'] || $projects[$project]['existing_version'] === $projects[$project]['latest_version']) { |
---|
| 584 | $projects[$project]['status'] = UPDATE_CURRENT; |
---|
| 585 | } |
---|
| 586 | else { |
---|
| 587 | $projects[$project]['status'] = UPDATE_NOT_CURRENT; |
---|
| 588 | } |
---|
| 589 | break; |
---|
| 590 | |
---|
| 591 | case 'dev': |
---|
| 592 | $latest = $available[$project]['releases'][$projects[$project]['latest_dev']]; |
---|
| 593 | if (empty($projects[$project]['datestamp'])) { |
---|
| 594 | $projects[$project]['status'] = UPDATE_NOT_CHECKED; |
---|
| 595 | $projects[$project]['reason'] = t('Unknown release date'); |
---|
| 596 | } |
---|
| 597 | elseif (($projects[$project]['datestamp'] + 100 > $latest['date'])) { |
---|
| 598 | $projects[$project]['status'] = UPDATE_CURRENT; |
---|
| 599 | } |
---|
| 600 | else { |
---|
| 601 | $projects[$project]['status'] = UPDATE_NOT_CURRENT; |
---|
| 602 | } |
---|
| 603 | break; |
---|
| 604 | |
---|
| 605 | default: |
---|
| 606 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
| 607 | $projects[$project]['reason'] = t('Invalid info'); |
---|
| 608 | } |
---|
| 609 | } |
---|
| 610 | else { |
---|
| 611 | $projects[$project]['status'] = UPDATE_UNKNOWN; |
---|
| 612 | $projects[$project]['reason'] = t('No available releases found'); |
---|
| 613 | } |
---|
| 614 | } |
---|
| 615 | // Give other modules a chance to alter the status (for example, to allow a |
---|
| 616 | // contrib module to provide fine-grained settings to ignore specific |
---|
| 617 | // projects or releases). |
---|
| 618 | drupal_alter('update_status', $projects); |
---|
| 619 | |
---|
| 620 | // Cache the site's update status for at most 1 hour. |
---|
| 621 | _update_cache_set('update_project_data', $projects, time() + 3600); |
---|
| 622 | return $projects; |
---|
| 623 | } |
---|
| 624 | |
---|
| 625 | /** |
---|
| 626 | * Retrieve data from {cache_update} or empty the cache when necessary. |
---|
| 627 | * |
---|
| 628 | * Two very expensive arrays computed by this module are the list of all |
---|
| 629 | * installed modules and themes (and .info data, project associations, etc), |
---|
| 630 | * and the current status of the site relative to the currently available |
---|
| 631 | * releases. These two arrays are cached in the {cache_update} table and used |
---|
| 632 | * whenever possible. The cache is cleared whenever the administrator visits |
---|
| 633 | * the status report, available updates report, or the module or theme |
---|
| 634 | * administration pages, since we should always recompute the most current |
---|
| 635 | * values on any of those pages. |
---|
| 636 | * |
---|
| 637 | * Note: while both of these arrays are expensive to compute (in terms of disk |
---|
| 638 | * I/O and some fairly heavy CPU processing), neither of these is the actual |
---|
| 639 | * data about available updates that we have to fetch over the network from |
---|
| 640 | * updates.drupal.org. That information is stored with the |
---|
| 641 | * 'update_available_releases' cache ID -- it needs to persist longer than 1 |
---|
| 642 | * hour and never get invalidated just by visiting a page on the site. |
---|
| 643 | * |
---|
| 644 | * @param $cid |
---|
| 645 | * The cache id of data to return from the cache. Valid options are |
---|
| 646 | * 'update_project_data' and 'update_project_projects'. |
---|
| 647 | * |
---|
| 648 | * @return |
---|
| 649 | * The cached value of the $projects array generated by |
---|
| 650 | * update_calculate_project_data() or update_get_projects(), or an empty |
---|
| 651 | * array when the cache is cleared. |
---|
| 652 | */ |
---|
| 653 | function update_project_cache($cid) { |
---|
| 654 | $projects = array(); |
---|
| 655 | |
---|
| 656 | // On certain paths, we should clear the cache and recompute the projects or |
---|
| 657 | // update status of the site to avoid presenting stale information. |
---|
| 658 | $q = $_GET['q']; |
---|
| 659 | $paths = array('admin/build/modules', 'admin/build/themes', 'admin/reports', 'admin/reports/updates', 'admin/reports/status', 'admin/reports/updates/check'); |
---|
| 660 | if (in_array($q, $paths)) { |
---|
| 661 | _update_cache_clear($cid); |
---|
| 662 | } |
---|
| 663 | else { |
---|
| 664 | $cache = _update_cache_get($cid); |
---|
| 665 | if (!empty($cache->data) && $cache->expire > time()) { |
---|
| 666 | $projects = $cache->data; |
---|
| 667 | } |
---|
| 668 | } |
---|
| 669 | return $projects; |
---|
| 670 | } |
---|
| 671 | |
---|
| 672 | /** |
---|
| 673 | * Filter the project .info data to only save attributes we need. |
---|
| 674 | * |
---|
| 675 | * @param array $info |
---|
| 676 | * Array of .info file data as returned by drupal_parse_info_file(). |
---|
| 677 | * |
---|
| 678 | * @return |
---|
| 679 | * Array of .info file data we need for the Update manager. |
---|
| 680 | * |
---|
| 681 | * @see _update_process_info_list() |
---|
| 682 | */ |
---|
| 683 | function update_filter_project_info($info) { |
---|
| 684 | $whitelist = array( |
---|
| 685 | '_info_file_ctime', |
---|
| 686 | 'datestamp', |
---|
| 687 | 'major', |
---|
| 688 | 'name', |
---|
| 689 | 'package', |
---|
| 690 | 'project', |
---|
| 691 | 'project status url', |
---|
| 692 | 'version', |
---|
| 693 | ); |
---|
| 694 | $whitelist = array_flip($whitelist); |
---|
| 695 | foreach ($info as $key => $value) { |
---|
| 696 | if (!isset($whitelist[$key])) { |
---|
| 697 | unset($info[$key]); |
---|
| 698 | } |
---|
| 699 | } |
---|
| 700 | return $info; |
---|
| 701 | } |
---|