commit 113802bf42d1ce247a82016a4208f46f810e9154
Author: Damien Tournoud <damien@commerceguys.com>
Date:   Sat Jun 11 00:44:08 2011 -0400

    Loading modules from .phar files.

diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 17c8e83..549db4e 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -158,6 +158,8 @@ define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
  */
 define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
 
+require_once DRUPAL_ROOT . '/includes/module-fs.inc';
+
 /**
  * Start the timer with the specified name. If you start and stop the same
  * timer multiple times, the measured intervals will be accumulated.
@@ -838,10 +840,10 @@ function drupal_load($type, $name) {
     return TRUE;
   }
 
-  $filename = drupal_get_filename($type, $name);
+  $filename = DrupalModuleStreamWrapper::getBaseFilename($type, $name);
 
   if ($filename) {
-    include_once DRUPAL_ROOT . '/' . $filename;
+    include_once $filename;
     $files[$type][$name] = TRUE;
 
     return TRUE;
diff --git a/includes/common.inc b/includes/common.inc
index 4ec37dc..5f0b3cf 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -5154,6 +5154,17 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1)
   if (!function_exists('file_scan_directory')) {
     require_once DRUPAL_ROOT . '/includes/file.inc';
   }
+
+  // Add the PHAR archives from the search directories as search directories
+  // themselves. They will be scanned for files matching the pattern below.
+  $pharfiles = array();
+  foreach ($searchdir as $dir) {
+    foreach (file_scan_directory($dir, '/\.phar$/', array('key' => $key, 'min_depth' => $min_depth)) as $file) {
+      $pharfiles[] = 'phar://' . $file->uri;
+    }
+  }
+  $searchdir = array_merge($searchdir, $pharfiles);
+
   foreach ($searchdir as $dir) {
     $files_to_add = file_scan_directory($dir, $mask, array('key' => $key, 'min_depth' => $min_depth));
 
diff --git a/includes/file.inc b/includes/file.inc
index b7a1204..7698b68 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -220,6 +220,11 @@ function file_uri_scheme($uri) {
  *   or FALSE if the scheme does not have a registered handler.
  */
 function file_stream_wrapper_valid_scheme($scheme) {
+  // Is the scheme already known by PHP?
+  if (in_array($scheme, stream_get_wrappers())) {
+    return TRUE;
+  }
+
   // Does the scheme have a registered handler that is callable?
   $class = file_stream_wrapper_get_class($scheme);
   if (class_exists($class)) {
diff --git a/includes/module-fs.inc b/includes/module-fs.inc
new file mode 100644
index 0000000..ecaa3f7
--- /dev/null
+++ b/includes/module-fs.inc
@@ -0,0 +1,191 @@
+<?php
+
+class DrupalModuleStreamWrapper {
+  public $context;
+  public $handle = NULL;
+  protected $uri;
+
+  protected function getFilename() {
+    list($scheme, $target) = explode('://', $this->uri, 2);
+    list($type, $name, $file) = explode('/', $target, 3);
+
+    return DrupalModuleStreamWrapper::getBaseDirectory($type, $name) . '/' . $file;
+  }
+
+  public static $baseFilenames = array();
+
+  public static function getBaseFilename($type, $name) {
+    if (!isset(DrupalModuleStreamWrapper::$baseFilenames[$type])) {
+      DrupalModuleStreamWrapper::$baseFilenames[$type] = array();
+    }
+
+    if (!isset(DrupalModuleStreamWrapper::$baseFilenames[$type][$name])) {
+      // Verify that we have an active database connection, before querying
+      // the database. This is required because this function is called both
+      // before we have a database connection (i.e. during installation) and
+      // when a database connection fails.
+      try {
+        if (function_exists('db_query')) {
+          $result = db_query("SELECT type, name, filename FROM {system} WHERE status = 1");
+          foreach ($result as $file) {
+            DrupalModuleStreamWrapper::$baseFilenames[$file->type][$file->name] = $file->filename;
+          }
+        }
+      }
+      catch (Exception $e) {
+        // The database table may not exist because Drupal is not yet installed,
+        // or the database might be down. We have a fallback for this case so we
+        // hide the error completely.
+      }
+
+      // Fallback to searching the filesystem if the database could not find the
+      // file or the file returned by the database is not found.
+      if (!isset(DrupalModuleStreamWrapper::$baseFilenames[$type][$name])) {
+        // We have a consistent directory naming: modules, themes...
+        $dir = $type . 's';
+        if ($type == 'theme_engine') {
+          $dir = 'themes/engines';
+          $extension = 'engine';
+        }
+        elseif ($type == 'theme') {
+          $extension = 'info';
+        }
+        else {
+          $extension = $type;
+        }
+
+        if (!function_exists('drupal_system_listing')) {
+          require_once DRUPAL_ROOT . '/includes/common.inc';
+        }
+        // Scan the appropriate directories for all files with the requested
+        // extension, not just the file we are currently looking for. This
+        // prevents unnecessary scans from being repeated when this function is
+        // called more than once in the same page request.
+        $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
+        foreach ($matches as $matched_name => $file) {
+          DrupalModuleStreamWrapper::$baseFilenames[$type][$matched_name] = $file->uri;
+        }
+      }
+    }
+
+    if (isset(DrupalModuleStreamWrapper::$baseFilenames[$type][$name])) {
+      return DrupalModuleStreamWrapper::$baseFilenames[$type][$name];
+    }
+  }
+
+  public static function getBaseDirectory($type, $name) {
+    return dirname(DrupalModuleStreamWrapper::getBaseDirectory, $type, $name);
+  }
+
+  public function stream_open($uri, $mode, $options, &$opened_path) {
+    $this->uri = $uri;
+    $path = $this->getFilename();
+    $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
+
+    if ((bool) $this->handle && $options & STREAM_USE_PATH) {
+      $opened_url = $path;
+    }
+
+    return (bool) $this->handle;
+  }
+
+  public function stream_lock($operation) {
+    // This streamwrapper is read-only.
+    return FALSE;
+  }
+
+  public function stream_read($count) {
+    return fread($this->handle, $count);
+  }
+
+  public function stream_write($data) {
+    return FALSE;
+  }
+
+  public function stream_eof() {
+    return feof($this->handle);
+  }
+
+  public function stream_seek($offset, $whence) {
+    // fseek returns 0 on success and -1 on a failure.
+    // stream_seek   1 on success and  0 on a failure.
+    return !fseek($this->handle, $offset, $whence);
+  }
+
+  public function stream_flush() {
+    return FALSE;
+  }
+
+  public function stream_tell() {
+    return ftell($this->handle);
+  }
+
+  public function stream_stat() {
+    return fstat($this->handle);
+  }
+
+  public function stream_close() {
+    return fclose($this->handle);
+  }
+
+  public function unlink($uri) {
+    // This streamwrapper is read-only.
+    return FALSE;
+  }
+
+  public function rename($from_uri, $to_uri) {
+    // This streamwrapper is read-only.
+    return FALSE;
+  }
+
+  public function mkdir($uri, $mode, $options) {
+    // This streamwrapper is read-only.
+    return FALSE;
+  }
+
+  public function rmdir($uri, $options) {
+    // This streamwrapper is read-only.
+    return FALSE;
+  }
+
+  public function url_stat($uri, $flags) {
+    $this->uri = $uri;
+    $path = $this->getFilename();
+    // Suppress warnings if requested or if the file or directory does not
+    // exist. This is consistent with PHP's plain filesystem stream wrapper.
+    if ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) {
+      return @stat($path);
+    }
+    else {
+      return stat($path);
+    }
+  }
+
+  public function dir_opendir($uri, $options) {
+    $this->uri = $uri;
+    $this->handle = opendir($this->getFilename());
+
+    return (bool) $this->handle;
+  }
+
+  public function dir_readdir() {
+    return readdir($this->handle);
+  }
+
+  public function dir_rewinddir() {
+    rewinddir($this->handle);
+    // We do not really have a way to signal a failure as rewinddir() does not
+    // have a return value and there is no way to read a directory handler
+    // without advancing to the next file.
+    return TRUE;
+  }
+
+  public function dir_closedir() {
+    closedir($this->handle);
+    // We do not really have a way to signal a failure as closedir() does not
+    // have a return value.
+    return TRUE;
+  }
+}
+
+stream_wrapper_register('drupal', 'DrupalModuleStreamWrapper');
diff --git a/includes/module.inc b/includes/module.inc
index 23f2fa8..fc5bfde 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -140,7 +140,7 @@ function system_list($type) {
     // drupal_get_filename() static cache for bootstrap modules only.
     // The rest is stored separately to keep the bootstrap module cache small.
     foreach ($bootstrap_list as $module) {
-      drupal_get_filename('module', $module->name, $module->filename);
+      DrupalModuleStreamWrapper::$baseFilenames['module'][$module->name] = $module->filename;
     }
     // We only return the module names here since module_list() doesn't need
     // the filename itself.
@@ -183,7 +183,7 @@ function system_list($type) {
     // 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']);
+      DrupalModuleStreamWrapper::$baseFilenames[$item['type']][$item['name']] = $item['filepath'];
     }
   }
 
