Index: common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.756.2.57
diff -u -r1.756.2.57 common.inc
--- common.inc	1 Jul 2009 20:51:55 -0000	1.756.2.57
+++ common.inc	23 Sep 2009 01:38:11 -0000
@@ -2743,8 +2743,30 @@
   }
 
   // Get current list of items
+  $scandir_ini_name = NULL;
+  $auto_suffix = NULL;
+  switch ($mask) {
+    // when scanning for .module files or .info files,
+    // the system will look for scandir.ini files,
+    // that allow to skip a lot of subfolders.
+    case '\.module$':
+      $auto_suffix = '.module';
+      $scandir_ini_name = 'scandir.ini';
+      break;
+    case '\.info$':
+      $auto_suffix = '.info';
+      $scandir_ini_name = 'scandir.ini';
+  }
   foreach ($searchdir as $dir) {
-    $files = array_merge($files, file_scan_directory($dir, $mask, array('.', '..', 'CVS'), 0, TRUE, $key, $min_depth));
+    // use the faster method..
+    file_scan_system_directory($dir, $files, array(
+      'mask' => $mask,
+      'key' => $key,
+      'min_depth' => $min_depth,
+      'scandir_ini_name' => $scandir_ini_name,
+      'auto_suffix' => $auto_suffix
+    ));
+    // $files = array_merge($files, file_scan_directory($dir, $mask, array('.', '..', 'CVS'), 0, TRUE, $key, $min_depth));
   }
 
   return $files;
@@ -2752,6 +2774,116 @@
 
 
 /**
+ * Similar to file_scan_directory,
+ * but allows to exclude specific folders.
+ * The exclusion rules can be defined in scandir.ini files.
+ * These files can be anywhere in the modules folder.
+ * The ideal place is in sites/all/modules/contrib/scandir.ini
+ * 
+ * @param $dir
+ *   the directory to scan
+ * @param &$files
+ *   the array to collect the files that were found
+ * @param $options
+ *   an options array.
+ *   mask: regex to specifiy which kind of files to search
+ *   key: can be 'filename', 'basename' or 'name'.
+ *   min_depth: min depth to scan..
+ *   scandir_ini_name: name of the ini file for exclusion rules
+ *   auto_suffix: typically this is either NULL or ".module" or ".info"
+ */
+function file_scan_system_directory($dir, &$files, $options, $depth = 0, $scandir_hints = array()) {
+  // merge in default options, and extract the options
+  foreach(array(
+    'mask' => $mask,
+    'key' => 'filename',
+    'min_depth' => 0,
+    'scandir_ini_name' => NULL,
+    'auto_suffix' => NULL,
+  ) as $k => $v) {
+    if (!isset($options[$k])) {
+      $options[$k] = $v;
+    }
+    $$k = $options[$k];
+  }
+  if (is_dir($dir)) {
+    $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
+    $localdirs = array();
+    $localfiles = array();
+    if ($scandir_ini_name && file_exists("$dir/$scandir_ini_name")) {
+      foreach (drupal_parse_info_file("$dir/$scandir_ini_name") as $hint_dir => $hint_elements) {
+        if ($auto_suffix) {
+          foreach ($hint_elements as $k => &$v) {
+            $hint_elements[$k] .= $auto_suffix;
+          }
+        }
+        if ($hint_dir == '.') {
+          $scandir_hints[$dir] = $hint_elements;
+        } else {
+          $scandir_hints["$dir/$hint_dir"] = $hint_elements;
+        }
+      }
+    }
+    if (isset($scandir_hints[$dir])) {
+      foreach ($scandir_hints[$dir] as $hint) {
+        $fragments = explode('/', $hint);
+        $file = array_pop($fragments);
+        $subdir = $dir;
+        if (count($fragments)) {
+          $subdir .= '/' . implode('/', $fragments);
+        }
+        if ($file != 'CVS' && $file[0] != '.') {
+          if (is_dir("$subdir/$file")) {
+            $localdirs[] = array("$subdir/$file", $depth + count($fragments) + 1);
+          }
+          elseif (is_file("$subdir/$file") && $depth + count($fragments) >= $min_depth && ereg($mask, $file)) {
+            // Always use this match over anything already set in $files with the same $$key.
+            $filename = "$subdir/$file";
+            $basename = basename($file);
+            $name = substr($basename, 0, strrpos($basename, '.'));
+            $localfiles[$$key] = new stdClass();
+            $localfiles[$$key]->filename = $filename;
+            $localfiles[$$key]->basename = $basename;
+            $localfiles[$$key]->name = $name;
+          }
+        }
+      }
+    } else {
+      if ($handle = opendir($dir)) {
+        while (FALSE !== ($file = readdir($handle))) {
+          if ($file != 'CVS' && $file[0] != '.') {
+            if (is_dir("$dir/$file")) {
+              $localdirs[] = array("$dir/$file", $depth+1);
+            }
+            elseif ($depth >= $min_depth && ereg($mask, $file)) {
+              // Always use this match over anything already set in $files with the same $$key.
+              $filename = "$dir/$file";
+              $basename = basename($file);
+              $name = substr($basename, 0, strrpos($basename, '.'));
+              $localfiles[$$key] = new stdClass();
+              $localfiles[$$key]->filename = $filename;
+              $localfiles[$$key]->basename = $basename;
+              $localfiles[$$key]->name = $name;
+            }
+          }
+        }
+        closedir($handle);
+      }
+    }
+    
+    for ($i = count($localdirs)-1; $i >= 0; --$i) {
+      list($localdir, $localdir_depth) = $localdirs[$i];
+      file_scan_system_directory($localdir, $files, $options, $localdir_depth, $scandir_hints);
+    }
+    
+    foreach ($localfiles as $k => $v) {
+      $files[$k] = $v;
+    }
+  }
+}
+
+
+/**
  * This dispatch function hands off structured Drupal arrays to type-specific
  * *_alter implementations. It ensures a consistent interface for all altering
  * operations.

