Index: libraries.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.api.php,v
retrieving revision 1.5
diff -u -p -r1.5 libraries.api.php
--- libraries.api.php	3 Nov 2010 22:22:42 -0000	1.5
+++ libraries.api.php	3 Nov 2010 22:46:00 -0000
@@ -114,7 +114,7 @@ function hook_libraries_info() {
   // directory, which should contain the entire, original extracted library.
   $libraries['example'] = array(
     // Only used in administrative UI of Libraries API.
-    'title' => 'Example library',
+    'name' => 'Example library',
     'vendor url' => 'http://example.com',
     'download url' => 'http://example.com/download',
     // Optional: If, after extraction, the actual library files are contained in
@@ -226,7 +226,7 @@ function hook_libraries_info() {
   // A very simple library. No changing APIs (hence, no versions), no variants.
   // Expected to be extracted into 'sites/all/libraries/simple'.
   $libraries['simple'] = array(
-    'title' => 'Simple library',
+    'name' => 'Simple library',
     'vendor url' => 'http://example.com/simple',
     'download url' => 'http://example.com/simple',
     'version arguments' => array(
@@ -245,7 +245,7 @@ function hook_libraries_info() {
 
   // A library that (naturally) evolves over time with API changes.
   $libraries['tinymce'] = array(
-    'title' => 'TinyMCE',
+    'name' => 'TinyMCE',
     'vendor url' => 'http://tinymce.moxiecode.com',
     'download url' => 'http://tinymce.moxiecode.com/download.php',
     'path' => 'jscripts/tiny_mce',
@@ -323,6 +323,31 @@ function hook_libraries_info() {
       ),
     ),
   );
+  // An example library that is external. It has a local and a remote variant.
+  $libraries['openlayers'] = array(
+    'name' => 'OpenLayers',
+    'vendor url' => 'http://www.openlayers.org/',
+    'download url' => 'http://trac.osgeo.org/openlayers/wiki/HowToDownload',
+    // 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',
+    'files' => array(
+      'js' => array('OpenLayers.js'),
+    ),
+    // It is usual for an external with a local and a remote variant to differ
+    // only in the library path. The actual library is usually the same, only
+    // the location is different. Therefore 'files' is specified on the top
+    // level and is not repeated.
+    'variants' => array(
+      'local' => array(
+        'library path' => libraries_get_path('openlayers'),
+      ),
+      'remote' => array(
+        'library path' => 'http://openlayers.org/api',
+      ),
+    ),
+  );
   return $libraries;
 }
 
Index: libraries.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.module,v
retrieving revision 1.11
diff -u -p -r1.11 libraries.module
--- libraries.module	3 Nov 2010 22:22:42 -0000	1.11
+++ libraries.module	3 Nov 2010 22:46:00 -0000
@@ -262,10 +262,43 @@ function libraries_detect_library(&$libr
   $name = $library['name'];
 
   // Check whether the library exists.
+  $found = FALSE;
   if (!isset($library['library path'])) {
+    // If the library path hasn't been set Libraries API scans all libraries
+    // directories for the library. This is the intended usage of the API.
     $library['library path'] = libraries_get_path($name);
   }
-  if (!file_exists($library['library path'])) {
+  if ($library_exists = libraries_file_exists($library['library path'])) {
+    $found = TRUE;
+  }
+  // Check the variants for library paths as well. This is especially needed for
+  // libraries which specify the library path in their variant declarations
+  // (e.g. external libraries).
+  if (!empty($library['variants'])) {
+    foreach ($library['variants'] as $name => &$variant) {
+      // We consider the variant not found if one of the following is true:
+      // 1. The library itself was not found and the variant did not specify a
+      //    library path.
+      // 2. The variant specified a library path that could not be found.
+      if (isset($variant['library path']) && $variant_exists = libraries_file_exists($variant['library path'])) {
+        $found = TRUE;
+      }
+      elseif (!$library_exists || (isset($variant_exists) && !$variant_exists)) {
+          $variant['installed'] = FALSE;
+          $variant['error'] = 'not found';
+          $variant['error message'] = t('The %variant variant of %library could not be found.', array(
+            '%variant' => $name,
+            '%library' => $library['name'],
+          ));
+        }
+      }
+    }
+  }
+  // We consider the library found if one of the following is true:
+  // 1. 'library path' was specified in the top-level library or in one of the
+  //    variants and resolves to an existing path.
+  // 2. libraries_get_path() finds the library in a libraries directory.
+  if (!$found) {
     $library['error'] = 'not found';
     $library['error message'] = t('%library could not be found.', array('%library' => $library['name']));
     return;
@@ -315,6 +348,10 @@ function libraries_detect_library(&$libr
   // Check each variant if it is installed.
   if (!empty($library['variants'])) {
     foreach ($library['variants'] as $name => &$variant) {
+      // If errors have been set already (during path detection), do nothing.
+      if (isset($variant['error'])) {
+        break;
+      }
       // If no variant callback has been set, assume the variant to be
       // installed.
       if (!isset($variant['variant callback'])) {
@@ -413,9 +450,10 @@ 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";
-          $options = NULL;
+          // This is needed because drupal_add_css() does not detect external
+          // paths automatically. See http://drupal.org/node/953340
+          $options = (url_is_external($data) ? array('type' => 'external') : NULL);
         }
         // In some cases, the first parameter ($data) is an array. Arrays can't
         // be passed as keys in PHP, so we have to get $data from the value
@@ -437,9 +475,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;
         $count++;
       }
     }
@@ -479,11 +517,12 @@ function libraries_get_version($library,
     'cols' => 200,
   );
 
-  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
-  if (empty($options['file']) || !file_exists($file)) {
+  $filepath = $library['library path'] . '/' . $options['file'];
+  $filepath = (url_is_external($filepath) ? $filepath : DRUPAL_ROOT . '/' . $filepath);
+  if (empty($options['file']) || !libraries_file_exists($filepath)) {
     return;
   }
-  $file = fopen($file, 'r');
+  $file = fopen($filepath, 'r');
   while ($options['lines'] && $line = fgets($file, $options['cols'])) {
     if (preg_match($options['pattern'], $line, $version)) {
       fclose($file);
@@ -494,3 +533,20 @@ 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.9
diff -u -p -r1.9 libraries.test
--- tests/libraries.test	3 Nov 2010 22:22:42 -0000	1.9
+++ tests/libraries.test	3 Nov 2010 22:46:01 -0000
@@ -149,6 +149,24 @@ class LibrariesTestCase extends DrupalWe
     $this->verbose(var_export($library, TRUE));
     $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.');
 
+    // Test external library.
+    $library = libraries_info('example_external');
+    libraries_detect_library($library);
+    $this->verbose(var_export($library, TRUE));
+    $this->assertEqual($library['installed'], TRUE, 'External library found.');
+
+    // Test external library with a local and remote variant
+    $library = libraries_info('example_external_variant');
+    libraries_detect_library($library);
+    $this->verbose(var_export($library, TRUE));
+    $this->assertEqual($library['installed'], TRUE, 'External library with local and remote variant found.');
+
+    // Test external library with version callback.
+    $library = libraries_info('example_external_version');
+    libraries_detect_library($library);
+    $this->verbose(var_export($library, TRUE));
+    $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', 'Simple library loading');
@@ -170,6 +188,18 @@ class LibrariesTestCase extends DrupalWe
     // Test version overloading and variant loading.
     $this->drupalGet('libraries_test/versions_and_variants');
     $this->assertLibraryFiles('example_4', 'Concurrent version and variant overloading');
+
+    // Test loading of an external library.
+    $this->drupalGet('libraries_test/external');
+    $this->assertLibraryFiles('example_1', 'External library loading', array('js', 'css'));
+
+    // Test the local variant of an external library.
+    $this->drupalGet('libraries_test/external_variant_local');
+    $this->assertLibraryFiles('example_1', 'Loading of a local variant of an external library', array('js', 'css'));
+
+    // Test the remote variant of an external library.
+    $this->drupalGet('libraries_test/external_variant_remote');
+    $this->assertLibraryFiles('example_1', 'Loading of a remote variant of an external library', array('js', 'css'));
   }
 
   /**
Index: tests/libraries_test.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_test.module,v
retrieving revision 1.7
diff -u -p -r1.7 libraries_test.module
--- tests/libraries_test.module	3 Nov 2010 22:22:42 -0000	1.7
+++ tests/libraries_test.module	3 Nov 2010 22:46:01 -0000
@@ -43,6 +43,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(
     'name' => 'Example default version callback',
@@ -334,6 +371,24 @@ function libraries_test_menu() {
     'page arguments' => array('example_versions_and_variants', 'example_variant_2'),
     'access callback' => TRUE,
   );
+  $items['libraries_test/external'] = array(
+    'title' => 'Test external library',
+    'page callback' => '_libraries_test_load',
+    'page arguments' => array('example_external'),
+    'access callback' => TRUE,
+  );
+  $items['libraries_test/external_variant_local'] = array(
+    'title' => 'Test local variant of an external library',
+    'page callback' => '_libraries_test_load',
+    'page arguments' => array('example_external_variant', 'local'),
+    'access callback' => TRUE,
+  );
+  $items['libraries_test/external_variant_remote'] = array(
+    'title' => 'Test remote variant of an external library',
+    'page callback' => '_libraries_test_load',
+    'page arguments' => array('example_external_variant', 'remote'),
+    'access callback' => TRUE,
+  );
   return $items;
 }
 
