Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.177
diff -u -p -r1.177 module.inc
--- includes/module.inc	28 Dec 2009 10:48:51 -0000	1.177
+++ includes/module.inc	30 Dec 2009 15:36:53 -0000
@@ -89,31 +89,36 @@ function module_list($refresh = FALSE, $
  *
  * @param $type
  *   The type of list to return:
+ *   - module: All modules.
  *   - module_enabled: All enabled modules.
  *   - bootstrap: All enabled modules required for bootstrap.
  *   - theme: All themes.
+ *   - theme_enabled: All enabled themes.
  *
  * @return
  *   An associative array of modules or themes, keyed by name, and having the
- *   respective database row as value. For $type 'module_enabled' and
- *   'bootstrap', the array values equal the keys.
+ *   respective database row as value. For $type 'module_enabled',
+ *   'theme_enabled', and 'bootstrap', the array values equal the keys.
  *
  * @see module_list()
  * @see list_themes()
  */
 function system_list($type) {
-  $lists = &drupal_static(__FUNCTION__);
+  $lists = &drupal_static(__FUNCTION__, array());
 
   // For bootstrap modules, attempt to fetch the list from cache if possible.
   // if not fetch only the required information to fire bootstrap hooks
   // in case we are going to serve the page from cache.
   if ($type == 'bootstrap') {
-    if ($cached = cache_get('bootstrap_modules', 'cache_bootstrap')) {
+    if (isset($lists[$type])) {
+      return $lists[$type];
+    }
+    elseif ($cached = cache_get('system_list:bootstrap', 'cache_bootstrap')) {
       $bootstrap_list = $cached->data;
     }
     else {
       $bootstrap_list = db_query("SELECT name, filename FROM {system} WHERE status = 1 AND bootstrap = 1 AND type = 'module' ORDER BY weight ASC, name ASC")->fetchAllAssoc('name');
-      cache_set('bootstrap_modules', $bootstrap_list, 'cache_bootstrap');
+      cache_set('system_list:bootstrap', $bootstrap_list, 'cache_bootstrap');
     }
     // To avoid a separate database lookup for the filepath, prime the
     // drupal_get_filename() static cache for bootstrap modules only.
@@ -123,49 +128,77 @@ function system_list($type) {
     }
     // We only return the module names here since module_list() doesn't need
     // the filename itself.
-    $lists['bootstrap'] = array_keys($bootstrap_list);
+    $lists[$type] = array_keys($bootstrap_list);
   }
   // Otherwise build the list for enabled modules and themes.
-  elseif (!isset($lists['module_enabled'])) {
-    if ($cached = cache_get('system_list', 'cache_bootstrap')) {
+  elseif (!isset($lists[$type])) {
+    // These types are special-cased, because they are required for each full bootstrap.
+    if ($type == 'module_enabled' || $type == 'theme') {
+      if ($cached = cache_get('system_list:bootstrap_full', 'cache_bootstrap')) {
+        $lists['module_enabled'] = $cached->data['module_enabled'];
+        $lists['theme'] = $cached->data['theme'];
+        $lists['filepaths'] = $cached->data['filepaths'];
+      }
+    }
+    elseif ($cached = cache_get('system_list:all', 'cache_bootstrap')) {
       $lists = $cached->data;
     }
-    else {
-      $lists = array(
-        'module_enabled' => array(),
-        'theme' => array(),
-        'filepaths' => array(),
-      );
-      // The module name (rather than the filename) is used as the fallback
-      // weighting in order to guarantee consistent behavior across different
-      // Drupal installations, which might have modules installed in different
-      // locations in the file system. The ordering here must also be
-      // consistent with the one used in module_implements().
-      $result = db_query("SELECT * FROM {system} ORDER BY weight ASC, name ASC");
-      foreach ($result as $record) {
-        if ($record->type == 'module' && $record->status) {
-          // Build a list of all enabled modules.
-          $lists['module_enabled'][$record->name] = $record->name;
+    // Rebuild the lists if there was no cache hit.
+    if (!isset($lists[$type])) {
+      // During normal site operation, we can query the database.
+      try {
+        // The module name (rather than the filename) is used as the fallback
+        // weighting in order to guarantee consistent behavior across different
+        // Drupal installations, which might have modules installed in different
+        // locations in the file system. The ordering here must also be
+        // consistent with the one used in module_implements().
+        $result = db_query("SELECT * FROM {system} ORDER BY weight ASC, name ASC");
+        foreach ($result as $record) {
+          // Unserialize the 'info' field before saving into lists.
+          $record->info = unserialize($record->info);
+
+          // Build a generic list for each {system}.type.
+          $lists[$record->type][$record->name] = $record;
+
+          if ($record->status) {
+            // Build a list of enabled types.
+            $lists[$record->type . '_enabled'][$record->name] = $record->name;
+
+            // Build a list of filenames so drupal_get_filename() can use it.
+            $lists['filepaths'][] = array('type' => $record->type, 'name' => $record->name, 'filepath' => $record->filename);
+          }
         }
-        // Build a list of themes.
-        if ($record->type == 'theme') {
-          $lists['theme'][$record->name] = $record;
+
+        // Cache the lists separately, but only if we have results.
+        if (isset($lists[$type])) {
+          cache_set('system_list:bootstrap_full', array('module_enabled' => $lists['module_enabled'], 'theme' => $lists['theme'], 'filepaths' => $lists['filepaths']), 'cache_bootstrap');
+          cache_set('system_list:all', $lists, 'cache_bootstrap');
         }
-        // Build a list of filenames so drupal_get_filename can use it.
-        if ($record->status) {
-          $lists['filepaths'][] = array('type' => $record->type, 'name' => $record->name, 'filepath' => $record->filename);
+      }
+      // Fall back to scanning the file system for modules and themes.
+      catch (Exception $e) {
+        $types = array('modules' => 'module', 'themes' => 'theme');
+        foreach ($types as $dir => $file_type) {
+          $files = drupal_system_listing('/\.info$/', $dir, array('key' => 'name'));
+          foreach ($files as $name => $file) {
+            $file->type = $file_type;
+            $file->info = drupal_parse_info_file($file->uri);
+            $lists[$file->type][$file->name] = $file;
+          }
         }
       }
-      cache_set('system_list', $lists, 'cache_bootstrap');
     }
+
     // To avoid a separate database lookup for the filepath, prime the
     // drupal_get_filename() static cache with all enabled modules and themes.
-    foreach ($lists['filepaths'] as $item) {
-      drupal_get_filename($item['type'], $item['name'], $item['filepath']);
+    if (($type == 'module_enabled' || $type == 'theme') && isset($lists['filepaths'])) {
+      foreach ($lists['filepaths'] as $item) {
+        drupal_get_filename($item['type'], $item['name'], $item['filepath']);
+      }
     }
   }
 
-  return $lists[$type];
+  return isset($lists[$type]) ? $lists[$type] : array();
 }
 
 /**
@@ -173,8 +206,72 @@ function system_list($type) {
  */
 function system_list_reset() {
   drupal_static_reset('system_list');
-  cache_clear_all('bootstrap_modules', 'cache_bootstrap');
-  cache_clear_all('system_list', 'cache_bootstrap');
+  cache_clear_all('system_list', 'cache_bootstrap', TRUE);
+}
+
+/**
+ * Return a list of modules or themes defining a certain .info file property value.
+ *
+ * This function builds a list of all modules that are currently registered,
+ * including disabled modules. It can be used to easily find all modules that
+ * define a certain .info file property. For example:
+ * @code
+ *   system_list_by_info('module', array('required'), TRUE);
+ * @endcode
+ * would list all required Drupal core modules.
+ *
+ * @code
+ *   system_list_by_info('module_enabled', array('dependencies'), 'taxonomy');
+ * @endcode
+ * would list all modules that require Taxonomy module.
+ *
+ * @param $type
+ *   The type of system object to list, either 'module' or 'theme'.
+ * @param $properties
+ *   A list of .info file property keys to search $value in, for example
+ *   array('hidden').
+ * @param $value
+ *   The value to search for, for example 'TRUE'. If the last parent key defined
+ *   in $parents is an array (such as the 'dependencies' definition), then
+ *   $value is searched in the array of values defined by a module.
+ * @param $simple
+ *   (optional) Boolean whether to return human-readable module names as values
+ *   (TRUE) or fully populated module objects including .info file information
+ *   as values (FALSE). Defaults to TRUE.
+ *
+ * @return
+ *   An associative array containing the machine-readable module name as key,
+ *   and the human-readable module name as value if $simple is TRUE, or a fully
+ *   populated module object if $simple is FALSE.
+ *
+ * @see system_list()
+ */
+function system_list_by_info($type, array $properties, $value, $simple = TRUE) {
+  $list = array();
+  $result = system_list($type);
+  foreach ($result as $name => $object) {
+    $info = $object->info;
+    foreach ($properties as $property) {
+      // If the (next) property is not found, skip to the next module.
+      if (!isset($info[$property])) {
+        continue 2;
+      }
+      $info = $info[$property];
+    }
+    // Check whether $info is an array first.
+    if (is_array($info)) {
+      if (in_array($value, $info)) {
+        $list[$name] = ($simple ? $object->info['name'] : $object);
+      }
+    }
+    // Otherwise, look whether $info matches $value.
+    else {
+      if ($info == $value) {
+        $list[$name] = ($simple ? $object->info['name'] : $object);
+      }
+    }
+  }
+  return $list;
 }
 
 /**
@@ -634,18 +731,14 @@ function module_invoke_all() {
  * Array of modules required by core.
  */
 function drupal_required_modules() {
-  $files = drupal_system_listing('/\.info$/', 'modules', 'name', 0);
   $required = array();
 
   // An install profile is required and one must always be loaded.
   $required[] = drupal_get_profile();
 
-  foreach ($files as $name => $file) {
-    $info = drupal_parse_info_file($file->uri);
-    if (!empty($info) && !empty($info['required']) && $info['required']) {
-      $required[] = $name;
-    }
-  }
+  // Include any modules that have required = TRUE in their .info files.
+  $modules = system_list_by_info('module', array('required'), TRUE);
+  $required = array_merge($required, array_keys($modules));
 
   return $required;
 }
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.559
diff -u -p -r1.559 theme.inc
--- includes/theme.inc	28 Dec 2009 20:58:08 -0000	1.559
+++ includes/theme.inc	30 Dec 2009 13:59:56 -0000
@@ -573,7 +573,6 @@ function list_themes($refresh = FALSE) {
     if (db_is_active() && !defined('MAINTENANCE_MODE')) {
       foreach (system_list('theme') as $theme) {
         if (file_exists($theme->filename)) {
-          $theme->info = unserialize($theme->info);
           $themes[] = $theme;
         }
       }
