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