Index: modules/update/update.compare.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/update/update.compare.inc,v
retrieving revision 1.29
diff -u -r1.29 update.compare.inc
--- modules/update/update.compare.inc	24 Aug 2009 00:42:34 -0000	1.29
+++ modules/update/update.compare.inc	8 Sep 2009 19:31:27 -0000
@@ -32,210 +32,16 @@
 function update_get_projects() {
   $projects = &drupal_static(__FUNCTION__, array());
   if (empty($projects)) {
-    // Retrieve the projects from cache, if present.
-    $projects = update_project_cache('update_project_projects');
-    if (empty($projects)) {
-      // Still empty, so we have to rebuild the cache.
-      $module_data = system_get_module_data();
-      $theme_data = system_get_theme_data();
-      _update_process_info_list($projects, $module_data, 'module', TRUE);
-      _update_process_info_list($projects, $theme_data, 'theme', TRUE);
-      if (variable_get('update_check_disabled', FALSE)) {
-        _update_process_info_list($projects, $module_data, 'module', FALSE);
-        _update_process_info_list($projects, $theme_data, 'theme', FALSE);
-      }
-      // Allow other modules to alter projects before fetching and comparing.
-      drupal_alter('update_projects', $projects);
-      // Cache the site's project data for at most 1 hour.
-      _update_cache_set('update_project_projects', $projects, REQUEST_TIME + 3600);
-    }
+    // We have to rebuild the cache.
+    require_once DRUPAL_ROOT . '/includes/project.inc';
+    $projects = drupal_project_list(variable_get('update_check_disabled', FALSE));
+    // Allow other modules to alter projects before fetching and comparing.
+    drupal_alter('update_projects', $projects);
   }
   return $projects;
 }
 
 /**
- * Populate 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. This function also records the latest change time on the .info
- * files for each module or theme, which is important data which is used when
- * deciding if the cached available update data should be invalidated.
- *
- * @param $projects
- *   Reference to the array of project data of what's installed on this site.
- * @param $list
- *   Array of data to process to add the relevant info to the $projects array.
- * @param $project_type
- *   The kind of data in the list (can be 'module' or 'theme').
- * @param $status
- *   Boolean that controls what status (enabled or disabled) to process out of
- *   the $list and add to the $projects array.
- *
- * @see update_get_projects()
- */
-function _update_process_info_list(&$projects, $list, $project_type, $status) {
-  foreach ($list as $file) {
-    if ($file->status != $status) {
-      continue;
-    }
-
-    // Skip if the .info file is broken.
-    if (empty($file->info)) {
-      continue;
-    }
-
-    // Skip if it's a hidden module.
-    if (!empty($file->info['hidden'])) {
-      continue;
-    }
-
-    // If the .info doesn't define the 'project', try to figure it out.
-    if (!isset($file->info['project'])) {
-      $file->info['project'] = update_get_project_name($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 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 file
-    // was unpacked.
-    if (!isset($file->info['_info_file_ctime'])) {
-      $info_filename = dirname($file->uri) . '/' . $file->name . '.info';
-      $file->info['_info_file_ctime'] = filectime($info_filename);
-    }
-
-    $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)) {
-      // If we're processing disabled modules or themes, append a suffix.
-      $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,
-        'info' => $file->info,
-        'datestamp' => isset($file->info['datestamp']) ? $file->info['datestamp'] : 0,
-        'includes' => array($file->name => $file->info['name']),
-        '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
-      // $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']);
-    }
-    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'];
-    }
-  }
-}
-
-/**
- * Given a $file object (as returned by system_get_files_database()), figure
- * out what project it belongs to.
- *
- * @see system_get_files_database()
- */
-function update_get_project_name($file) {
-  $project_name = '';
-  if (isset($file->info['project'])) {
-    $project_name = $file->info['project'];
-  }
-  elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core') !== FALSE)) {
-    $project_name = 'drupal';
-  }
-  elseif (in_array($file->name, array('garland', 'minnelli', 'stark'))) {
-    // Unfortunately, there's no way to tell if a theme is part of core,
-    // so we must hard-code a list here.
-    $project_name = 'drupal';
-  }
-  return $project_name;
-}
-
-/**
- * Process the list of projects on the system to figure out the currently
- * installed versions, and other information that is required before we can
- * compare against the available releases to produce the status report.
- *
- * @param $projects
- *   Array of project information from update_get_projects().
- */
-function update_process_project_info(&$projects) {
-  foreach ($projects as $key => $project) {
-    // Assume an official release until we see otherwise.
-    $install_type = 'official';
-
-    $info = $project['info'];
-
-    if (isset($info['version'])) {
-      // Check for development snapshots
-      if (preg_match('@(dev|HEAD)@', $info['version'])) {
-        $install_type = 'dev';
-      }
-
-      // Figure out what the currently installed major version is. We need
-      // to handle both contribution (e.g. "5.x-1.3", major = 1) and core
-      // (e.g. "5.1", major = 5) version strings.
-      $matches = array();
-      if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
-        $info['major'] = $matches[2];
-      }
-      elseif (!isset($info['major'])) {
-        // This would only happen for version strings that don't follow the
-        // drupal.org convention. We let contribs define "major" in their
-        // .info in this case, and only if that's missing would we hit this.
-        $info['major'] = -1;
-      }
-    }
-    else {
-      // No version info available at all.
-      $install_type = 'unknown';
-      $info['version'] = t('Unknown');
-      $info['major'] = -1;
-    }
-
-    // Finally, save the results we care about into the $projects array.
-    $projects[$key]['existing_version'] = $info['version'];
-    $projects[$key]['existing_major'] = $info['major'];
-    $projects[$key]['install_type'] = $install_type;
-  }
-}
-
-/**
  * Given the installed projects and the available release data retrieved from
  * remote servers, calculate the current status.
  *
@@ -322,7 +128,7 @@
     return $projects;
   }
   $projects = update_get_projects();
-  update_process_project_info($projects);
+
   foreach ($projects as $project => $project_info) {
     if (isset($available[$project])) {
 
Index: includes/project.inc
===================================================================
RCS file: includes/project.inc
diff -N includes/project.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/project.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,252 @@
+<?php
+// $Id: update.compare.inc,v 1.29 2009/08/24 00:42:34 webchick Exp $
+
+/**
+ * @file
+ * Handle projects and component to project mapping
+ * 
+ * This is required for updates and localization
+ */
+
+/**
+ * Fetch an array of installed and enabled projects.
+ *
+ * This is only responsible for generating an array of projects (taking into
+ * account projects that include more than one module or theme). Other
+ * information like the specific version and install type (official release,
+ * dev snapshot, etc) is handled later in update_process_project_info() since
+ * that logic is only required when preparing the status report, not for
+ * fetching the available release data.
+ *
+ * This array is fairly expensive to construct, since it involves a lot of
+ * disk I/O, so we cache the results into the {cache_update} table using the
+ * 'update_project_projects' cache ID. However, since this is not the data
+ * about available updates fetched from the network, it is ok to invalidate it
+ * somewhat quickly. If we keep this data for very long, site administrators
+ * are more likely to see incorrect results if they upgrade to a newer version
+ * of a module or theme but do not visit certain pages that automatically
+ * clear this cache.
+ *
+ * @see update_process_project_info()
+ * @see update_calculate_project_data()
+ * @see update_project_cache()
+ * 
+ * @param $all
+ *   Get disabled projects too
+ */
+function drupal_project_list($all = TRUE) {
+  $projects = &drupal_static(__FUNCTION__, array());
+  if (empty($projects)) {
+    // Retrieve the projects from cache, if present.
+    if ($cached = cache_get('drupal_projects')) {
+      $projects = $cached->data;
+    }
+    if (empty($projects)) {
+      // Still empty, so we have to rebuild the cache.
+      $module_data = system_get_module_data();
+      $theme_data = system_get_theme_data();
+      _drupal_project_process_info_list($projects, $module_data, 'module');
+      _drupal_project_process_info_list($projects, $theme_data, 'theme');
+      _drupal_project_process_display_list($projects);
+      _drupal_project_process_version_info($projects);
+      // Allow other modules to alter projects before fetching and comparing.
+      drupal_alter('project_list', $projects);
+      // Cache the site's project data for at most 1 hour.
+      cache_set('drupal_projects', $projects, 'cache', REQUEST_TIME + 3600);
+    }
+  }
+
+  return $all ? $projects : array_filter($projects, '_drupal_project_list_enabled_filter');
+}
+
+/**
+ * Filter callback to filter out disabled projects
+ */
+function _drupal_project_list_enabled_filter($project) {
+  return $project['project_status'];
+}
+
+/**
+ * Populate 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. This function also records the latest change time on the .info
+ * files for each module or theme, which is important data which is used when
+ * deciding if the cached available update data should be invalidated.
+ *
+ * @param $projects
+ *   Reference to the array of project data of what's installed on this site.
+ * @param $list
+ *   Array of data to process to add the relevant info to the $projects array.
+ * @param $project_type
+ *   The kind of data in the list (can be 'module' or 'theme').
+ * @param $status
+ *   Boolean that controls what status (enabled or disabled) to process out of
+ *   the $list and add to the $projects array.
+ *
+ * @see update_get_projects()
+ */
+function _drupal_project_process_info_list(&$projects, $list, $project_type) {
+  foreach ($list as $file) {
+
+    // Skip if the .info file is broken.
+    if (empty($file->info)) {
+      continue;
+    }
+
+    // Skip if it's a hidden module.
+    if (!empty($file->info['hidden'])) {
+      continue;
+    }
+
+    // If the .info doesn't define the 'project', try to figure it out.
+    if (!isset($file->info['project'])) {
+      $file->info['project'] = drupal_file_get_project($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 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 file
+    // was unpacked.
+    if (!isset($file->info['_info_file_ctime'])) {
+      $info_filename = dirname($file->uri) . '/' . $file->name . '.info';
+      $file->info['_info_file_ctime'] = filectime($info_filename);
+    }
+
+    $project_name = $file->info['project'];
+
+    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,
+        'info' => $file->info,
+        'datestamp' => isset($file->info['datestamp']) ? $file->info['datestamp'] : 0,
+        'includes' => array($file->name => $file->info['name']),
+        'type' => $project_type,
+        'project_status' => FALSE,
+        'enabled' => array(),
+        'disabled' => array(),
+      );
+    }
+    // Update project file ctime
+    $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
+    // Add the module to the enable / disble list depending on status
+    if ($file->status) {
+      $projects[$project_name]['enabled'][$file->name] = $file->info['name'];
+      $projects[$project_name]['project_status'] = TRUE;
+    }
+    else {
+      $projects[$project_name]['disabled'][$file->name] = $file->info['name'];
+    }
+  }
+}
+
+/**
+ * Add display type
+ */
+function _drupal_project_process_display_list(&$projects) {
+  foreach (array_keys($projects) as $name) {
+    // 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 ($name == 'drupal') {
+      $project_display_type = 'core';
+    }
+    else {
+      $project_display_type = $projects[$name]['type'];
+    }
+    if (!$projects[$name]['project_status']) {
+      // If we're processing disabled modules or themes, append a suffix.
+      $project_display_type .= '-disabled';
+    }
+    $projects[$name]['project_type'] = $project_display_type;  
+  }  
+}
+
+/**
+ * Given a $file object (as returned by system_get_files_database()), figure
+ * out what project it belongs to.
+ *
+ * @see system_get_files_database()
+ */
+function drupal_file_get_project($file) {
+  $project_name = '';
+  if (isset($file->info['project'])) {
+    $project_name = $file->info['project'];
+  }
+  elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core') !== FALSE)) {
+    $project_name = 'drupal';
+  }
+  elseif (in_array($file->name, array('garland', 'minnelli', 'stark'))) {
+    // Unfortunately, there's no way to tell if a theme is part of core,
+    // so we must hard-code a list here.
+    $project_name = 'drupal';
+  }
+  return $project_name;
+}
+
+/**
+ * Process the list of projects on the system to figure out the currently
+ * installed versions, and other information that is required before we can
+ * compare against the available releases to produce the status report.
+ *
+ * @param $projects
+ *   Array of project information from update_get_projects().
+ */
+function _drupal_project_process_version_info(&$projects) {
+  foreach ($projects as $key => $project) {
+    // Assume an official release until we see otherwise.
+    $install_type = 'official';
+
+    $info = $project['info'];
+
+    if (isset($info['version'])) {
+      // Check for development snapshots
+      if (preg_match('@(dev|HEAD)@', $info['version'])) {
+        $install_type = 'dev';
+      }
+
+      // Figure out what the currently installed major version is. We need
+      // to handle both contribution (e.g. "5.x-1.3", major = 1) and core
+      // (e.g. "5.1", major = 5) version strings.
+      $matches = array();
+      if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
+        $info['major'] = $matches[2];
+      }
+      elseif (!isset($info['major'])) {
+        // This would only happen for version strings that don't follow the
+        // drupal.org convention. We let contribs define "major" in their
+        // .info in this case, and only if that's missing would we hit this.
+        $info['major'] = -1;
+      }
+    }
+    else {
+      // No version info available at all.
+      $install_type = 'unknown';
+      $info['version'] = t('Unknown');
+      $info['major'] = -1;
+    }
+
+    // Finally, save the results we care about into the $projects array.
+    $projects[$key]['existing_version'] = $info['version'];
+    $projects[$key]['existing_major'] = $info['major'];
+    $projects[$key]['install_type'] = $install_type;
+  }
+}
+
