Index: libraries.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.api.php,v
retrieving revision 1.4
diff -u -p -r1.4 libraries.api.php
--- libraries.api.php	9 Oct 2010 22:26:03 -0000	1.4
+++ libraries.api.php	15 Oct 2010 17:19:19 -0000
@@ -17,9 +17,21 @@
  *   - title: The official, human-readable name of the library.
  *   - vendor url: The URL of the homepage of the library.
  *   - download url: The URL of a web page on which the library can be obtained.
+ *   - library path: (optional) The absolute path to the library directory. This
+ *     should not be declared normally, as it is automatically detected, to
+ *     allow for multiple possible library locations. A valid use-case is an
+ *     external library, in which case the full URL to the library should be
+ *     specified here.
  *   - path: (optional) A relative path from the directory of the library to the
  *     actual library. Only required if the extracted download package contains
  *     the actual library files in a sub-directory.
+ *   - version: (optional) The version of the library. This should not be
+ *     declared normally, as it is automatically detected (see 'version
+ *     callback' below) to allow for version changes of libraries without code
+ *     changes of implementing modules and to support different versions of a
+ *     library simultaneously (though only one version can be installed per
+ *     site). A valid use-case is an external library whose version cannot be
+ *     determined programatically.
  *   - version callback: (optional) The name of a function that detects and
  *     returns the full version string of the library. The first argument is
  *     always $library, an array containing all library information as described
@@ -311,6 +323,34 @@ function hook_libraries_info() {
       ),
     ),
   );
+  // An example library that is external. It has a local and a remote variant.
+  $libraries['openlayers'] = array(
+    'title' => 'OpenLayers',
+    'vendor url' => 'http://www.openlayers.org/',
+    'download url' => 'http://trac.osgeo.org/openlayers/wiki/HowToDownload',
+    // This makes Libraries API not scan the filesystem for the library.
+    'library path' => 'http://openlayers.org/api/OpenLayers.js',
+    // This should determine which variant is available, the local or the remote
+    // one, (with libraries_get_path()) and then conditionally determine the
+    // version (with libraries_get_version() and the respective parameters).
+    'version callback' => 'openlayers_get_version',
+    // The explicit declaration below is in part redundant, but is used here for
+    // demonstration purposes.
+    'variants' => array(
+      'local' => array(
+        'library path' => libraries_get_path('openlayers'),
+        'files' => array(
+          'js' => array('OpenLayers.js'),
+        ),
+      ),
+      'remote' => array(
+        'library path' => 'http://openlayers.org/api',
+        'files' => array(
+          'js' => array('OpenLayers.js'),
+        ),
+      ),
+    ),
+  );
   return $libraries;
 }
 
Index: libraries.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.module,v
retrieving revision 1.9
diff -u -p -r1.9 libraries.module
--- libraries.module	15 Oct 2010 15:15:53 -0000	1.9
+++ libraries.module	15 Oct 2010 18:13:52 -0000
@@ -131,26 +131,25 @@ function libraries_info_files() {
   if (!isset($profile)) {
     $profile = variable_get('install_profile', 'default');
   }
-  $config = conf_path();
+  $site = conf_path();
 
   // Build a list of directories.
   $directories = module_invoke_all('libraries_info_file_paths');
   $directories[] = 'libraries';
   $directories[] = "libraries/$profile/libraries";
   $directories[] = 'sites/all/libraries';
-  $directories[] = "sites/$config/libraries";
+  $directories[] = "sites/$site/libraries";
 
   // Scan for info files.
   $files = array();
   foreach ($directories as $dir) {
-    $files += file_scan_directory($dir, '/[a-z[a-z0-9_]+.info/', array(
+    $dir_files = file_scan_directory($dir, '/^[a-zA-Z0-9_-]+\.info$/', array(
       'key' => 'name',
       'recurse' => FALSE,
     ));
-  }
-
-  foreach ($files as &$file) {
-    $file = $file->uri;
+    foreach ($dir_files as $name => $file) {
+      $files[$name] = $file->uri;
+    }
   }
 
   return $files;
@@ -187,8 +186,7 @@ function libraries_info($library = NULL)
       }
     }
     // Gather information from .info files.
-    foreach (libraries_info_files() as $name => $path) {
-      $file = "$path/$name.info";
+    foreach (libraries_info_files() as $name => $file) {
       $libraries[$name] = drupal_parse_info_file($file);
     }
 
@@ -259,7 +257,7 @@ function libraries_detect_library(&$libr
   if (!isset($library['library path'])) {
     $library['library path'] = libraries_get_path($name);
   }
-  if (!file_exists($library['library path'])) {
+  if (!libraries_file_exists($library['library path'])) {
     $library['error'] = 'not found';
     $library['error message'] = t('%library could not be found.', array('%library' => $library['title']));
     return;
@@ -393,8 +391,9 @@ function libraries_load_files($library, 
         // If the value is not an array, it's a filename and passed as first
         // (and only) argument.
         if (!is_array($options)) {
-          // Prepend the library path to the file name.
-          $data = "$path/$options";
+          // Prepend the library path to the file name, if it is not an external
+          // file.
+          $data = (url_is_external($options) ? $options : "$path/$options");
           $options = NULL;
         }
         // In some cases, the first parameter ($data) is an array. Arrays can't
@@ -416,9 +415,9 @@ function libraries_load_files($library, 
   // Load PHP files.
   if (!empty($library['files']['php'])) {
     foreach ($library['files']['php'] as $file) {
-      $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
-      if (file_exists($file_path)) {
-        require_once $file_path;
+      $filepath = (url_is_external($file) ? $file : DRUPAL_ROOT . '/' . $path . '/' . $file);
+      if (libraries_file_exists($filepath)) {
+        include_once $filepath;
       }
     }
   }
@@ -455,8 +454,8 @@ function libraries_get_version($library,
     'cols' => 200,
   );
 
-  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
-  if (empty($options['file']) || !file_exists($file)) {
+  $file = (url_is_external($options['file']) ? $options['file'] : DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']);
+  if (empty($options['file']) || !libraries_file_exists($file)) {
     return;
   }
   $file = fopen($file, 'r');
@@ -470,3 +469,19 @@ function libraries_get_version($library,
   fclose($file);
 }
 
+/**
+ * Wrapper function for file_exists() with support for external files.
+ *
+ * For external files, it doesn't actually check their availability, as that
+ * would have to be done with fopen, which causes significant overhead. Instead,
+ * it simply assumes all external files to exist.
+ *
+ * @param $filepath
+ *   The path to the file. In case of an external file the full URL.
+ *
+ * @return
+ *   A boolean indicating whether this file exists or not.
+ */
+function libraries_file_exists($filepath) {
+  return url_is_external($filepath) || file_exists($filepath);
+}
Index: tests/libraries.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries.test,v
retrieving revision 1.8
diff -u -p -r1.8 libraries.test
--- tests/libraries.test	15 Oct 2010 15:15:53 -0000	1.8
+++ tests/libraries.test	15 Oct 2010 18:17:05 -0000
@@ -31,9 +31,11 @@ class LibrariesTestCase extends DrupalWe
    */
   function testLibraries() {
     // Test a library specified with an .info file gets detected.
-    $library = libraries_info('example');
+    $library = libraries_info('libraries_info_example');
     $expected = array(
-      'title' => 'example',
+      'name' => 'libraries_info_example',
+      'title' => 'Example info file',
+      'hidden' => TRUE,
       'vendor url' => '',
       'download url' => '',
       'path' => '',
@@ -44,6 +46,7 @@ class LibrariesTestCase extends DrupalWe
       'versions' => array(),
       'integration files' => array(),
     );
+    $this->verbose(var_export($library, TRUE));
     $this->assertEqual($library, $expected, 'Library specified with an .info file found');
 
     // Test missing library.
@@ -127,6 +130,16 @@ class LibrariesTestCase extends DrupalWe
     libraries_detect_library($library);
     $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.');
 
+    // Test external library.
+    $library = libraries_info('example_external');
+    libraries_detect_library($library);
+    $this->assertEqual($library['installed'], TRUE, 'External library found.');
+
+    // Test external library with version callback.
+    $library = libraries_info('example_external_version');
+    libraries_detect_library($library);
+    $this->assertEqual($library['version'], '2', 'Library version of an external library found.');
+
     // Test loading of a simple library with a top-level files property.
     $this->drupalGet('libraries_test/simple');
     $this->assertLibraryFiles('example_1');
@@ -148,6 +161,10 @@ class LibrariesTestCase extends DrupalWe
     // Test version overloading and variant loading.
     $this->drupalGet('libraries_test/versions_and_variants');
     $this->assertLibraryFiles('example_4');
+
+    // Test loading of an external library.
+    $this->drupalGet('libraries_test/external');
+    $this->assertLibraryFiles('example_1', array('js', 'css'));
   }
 
   /**
@@ -163,22 +180,25 @@ class LibrariesTestCase extends DrupalWe
    *   other files will be asserted to not be loaded. See
    *   tests/example/README.txt for more information on how the loading of the
    *   files is tested.
+   * @param $extensions
+   *   (optional) The expected file extensions of $name. Defaults to
+   *   array('js', 'css', 'php').
    */
-  function assertLibraryFiles($name) {
+  function assertLibraryFiles($name, $extensions = array('js', 'css', 'php')) {
     $names = drupal_map_assoc(array('example_1', 'example_2', 'example_3', 'example_4'));
     unset($names[$name]);
 
     // Test that the wrong files are not loaded.
     foreach ($names as $filename) {
-      $this->assertNoRaw("$filename.js", 'A wrong JavaScript file is not loaded.');
-      $this->assertNoRaw("$filename.css", 'A wrong CSS file is not loaded.');
-      $this->assertNoText("$filename.php", 'A wrong PHP file is not loaded.');
+      foreach ($extensions as $extension) {
+        $this->assertNoRaw("$filename.$extension");
+      }
     }
 
     // Test that the correct files are loaded.
-    $this->assertRaw("$name.js", 'The correct JavaScript file is loaded.');
-    $this->assertRaw("$name.css", 'The correct CSS file is loaded.');
-    $this->assertText("$name.php", 'The correct PHP file is loaded.');
+    foreach ($extensions as $extension) {
+      $this->assertRaw("$name.$extension");
+    }
   }
 
 }
Index: tests/libraries_test.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_test.module,v
retrieving revision 1.6
diff -u -p -r1.6 libraries_test.module
--- tests/libraries_test.module	15 Oct 2010 15:15:53 -0000	1.6
+++ tests/libraries_test.module	15 Oct 2010 17:49:27 -0000
@@ -47,6 +47,43 @@ function libraries_test_libraries_info()
     ),
   );
 
+  // Test external libraries.
+  $local_path = drupal_get_path('module', 'libraries_test') . '/example';
+  $remote_path = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'libraries_test') . '/example';
+  $libraries['example_external'] = array(
+    'library path' => $remote_path,
+    'version' => '2',
+    'files' => array(
+      'js' => array('example_1.js'),
+      'css' => array('example_1.css'),
+    ),
+  );
+  $libraries['example_external_variant'] = array(
+    'version' => '2',
+    'files' => array(
+      'js' => array('example_1.js'),
+      'css' => array('example_1.css'),
+    ),
+    'variants' => array(
+      'local' => array(
+        'library path' => $local_path,
+      ),
+      'remote' => array(
+        'library path' => $remote_path,
+      ),
+    ),
+  );
+  $libraries['example_external_version'] = array(
+    'library path' => $remote_path,
+    'version callback' => 'libraries_get_version',
+    'version arguments' => array(
+      'file' => 'README.txt',
+      // Version 2
+      'pattern' => '/Version (\d+)/',
+      'lines' => 5,
+    ),
+  );
+
   // Test the default version callback.
   $libraries['example_default_version_callback'] = array(
     'title' => 'Example default version callback',
@@ -234,7 +271,7 @@ function libraries_test_libraries_info()
  * Implements hook_libraries_info_file_paths()
  */
 function libraries_test_libraries_info_file_paths() {
-  return array(drupal_get_path('module', 'libraries_test') . '/example');
+  return array(drupal_get_path('module', 'libraries_test') . '/libraries_info_example');
 }
 
 /**
@@ -342,6 +379,12 @@ function libraries_test_menu() {
     'page arguments' => array('example_versions_and_variants', 'example_variant_2'),
     'access callback' => TRUE,
   );
+  $items['libraries_test/external'] = array(
+    'title' => 'Test loading of an external library',
+    'page callback' => '_libraries_test_load',
+    'page arguments' => array('example_external'),
+    'access callback' => TRUE,
+  );
   return $items;
 }
 
@@ -352,21 +395,6 @@ function _libraries_test_load($library, 
   libraries_load($library, $variant);
   // JavaScript and CSS files can be checked directly by SimpleTest, so we only
   // need to manually check for PHP files.
-  $output = '';
-  if (function_exists('_libraries_test_example_1')) {
-    $output .= 'example_1.php';
-  }
-  if (function_exists('_libraries_test_example_2')) {
-    $output .= 'example_2.php';
-  }
-  if (function_exists('_libraries_test_example_3')) {
-    $output .= 'example_3.php';
-  }
-  if (function_exists('_libraries_test_example_4')) {
-    $output .= 'example_4.php';
-  }
-  if (function_exists('_libraries_test_integration_file')) {
-    $output .= 'libraries_test.inc';
-  }
+  $output = implode("<br />\n", get_included_files());
   return $output;
 }
Index: tests/libraries_info_example/libraries_info_example.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_info_example/libraries_info_example.info,v
retrieving revision 1.1
diff -u -p -r1.1 libraries_info_example.info
--- tests/libraries_info_example/libraries_info_example.info	15 Oct 2010 16:11:08 -0000	1.1
+++ tests/libraries_info_example/libraries_info_example.info	15 Oct 2010 18:15:55 -0000
@@ -2,7 +2,7 @@
 
 ; This is an example info file of a library used for testing purposes.
 ; Do not declare name manually. It is set automatically.
-name = example_info_file
+name = libraries_info_example
 title = Example info file
 
 ; Because Drupal thinks this is a module's .info file, it is in the 'libraries'
