diff --git a/config/install/libraries.settings.yml b/config/install/libraries.settings.yml index 0c0aaf2585ead04aee56b0eabe641ecb0bff5fcc..0981884b1ad3ee32726149b1d6b4f4e950a16354 100644 --- a/config/install/libraries.settings.yml +++ b/config/install/libraries.settings.yml @@ -7,3 +7,4 @@ definition: enable: TRUE urls: - 'http://cgit.drupalcode.org/libraries_registry/plain/registry/8' +global_locators: [] \ No newline at end of file diff --git a/config/schema/libraries.schema.yml b/config/schema/libraries.schema.yml index 35af8cb67918d30d2c5354c4304698c9d6f51068..72c4c52f2e80873654a90422578c90d5ae68948f 100644 --- a/config/schema/libraries.schema.yml +++ b/config/schema/libraries.schema.yml @@ -1,5 +1,6 @@ # Configuration schema for the Libraries API module. +# Base configuration schema libraries.settings: type: config_object label: 'Libraries API settings' @@ -28,3 +29,25 @@ libraries.settings: sequence: type: uri label: 'The URL of a remote library registry' + global_locators: + type: sequence + title: 'Global library locators' + sequence: + type: mapping + title: 'Global locator plugins' + mapping: + id: + type: string + title: 'The locator plugin id' + configuration: + type: libraries.locator.[%parent.id] + title: 'The plugin configuration' + +# Dynamic locator plugin schema +libraries.locator.uri: + type: mapping + label: 'URI locator configuration' + mapping: + uri: + type: uri + label: 'The locator URI' \ No newline at end of file diff --git a/src/ExternalLibrary/Asset/AssetLibrary.php b/src/ExternalLibrary/Asset/AssetLibrary.php index e4471586e53eb2ce7d6ec5de21ddae76c61a3bc1..50a31868682d824ee0f3cdcc7fa35d0b3f963a8d 100644 --- a/src/ExternalLibrary/Asset/AssetLibrary.php +++ b/src/ExternalLibrary/Asset/AssetLibrary.php @@ -118,7 +118,11 @@ class AssetLibrary extends LibraryBase implements * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocator() */ public function getLocator(FactoryInterface $locator_factory) { - return $locator_factory->createInstance('stream', ['scheme' => 'asset']); + // @todo Consider consolidating the stream wrappers used here. For now we + // allow asset libs to live almost anywhere. + return $locator_factory->createInstance('chain') + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'asset://'])) + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'php-file://'])); } } diff --git a/src/ExternalLibrary/Asset/MultipleAssetLibrary.php b/src/ExternalLibrary/Asset/MultipleAssetLibrary.php index 0e424475a2145536e028d58c0b4548530d61f143..f2c3d3c46c23d1b5d25acb405d6d1096ea2392f0 100644 --- a/src/ExternalLibrary/Asset/MultipleAssetLibrary.php +++ b/src/ExternalLibrary/Asset/MultipleAssetLibrary.php @@ -113,7 +113,11 @@ class MultipleAssetLibrary extends LibraryBase implements * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocator() */ public function getLocator(FactoryInterface $locator_factory) { - return $locator_factory->createInstance('stream', ['scheme' => 'asset']); + // @todo Consider consolidating the stream wrappers used here. For now we + // allow asset libs to live almost anywhere. + return $locator_factory->createInstance('chain') + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'asset://'])) + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'php-file://'])); } } diff --git a/src/ExternalLibrary/PhpFile/PhpFileLibrary.php b/src/ExternalLibrary/PhpFile/PhpFileLibrary.php index 6134849186345dc7158670027436479d52b912f7..bf3c68328c205225920ebb79e6770d0f6e7b88fd 100644 --- a/src/ExternalLibrary/PhpFile/PhpFileLibrary.php +++ b/src/ExternalLibrary/PhpFile/PhpFileLibrary.php @@ -66,7 +66,8 @@ class PhpFileLibrary extends LibraryBase implements PhpFileLibraryInterface { * {@inheritdoc} */ public function getLocator(FactoryInterface $locator_factory) { - return $locator_factory->createInstance('stream', ['scheme' => 'php-file']); + // @todo Consider refining the stream wrapper used here. + return $locator_factory->createInstance('uri', ['uri' => 'php-file://']); } } diff --git a/src/ExternalLibrary/Type/LibraryTypeBase.php b/src/ExternalLibrary/Type/LibraryTypeBase.php index a21ef04b85e22bd4f60d54f5365a28e86ddbc264..08cd61a80b2ee4b84cb1c664e66f7d5b18d1a112 100644 --- a/src/ExternalLibrary/Type/LibraryTypeBase.php +++ b/src/ExternalLibrary/Type/LibraryTypeBase.php @@ -68,6 +68,12 @@ abstract class LibraryTypeBase implements public function onLibraryCreate(LibraryInterface $library) { if ($library instanceof LocalLibraryInterface) { $library->getLocator($this->locatorFactory)->locate($library); + // Fallback on global locators. + // @todo Consider if global locators should be checked as a fallback or as + // the primary locator source. + if (!$library->isInstalled()) { + $this->locatorFactory->createInstance('global')->locate($library); + } } if ($library instanceof VersionedLibraryInterface) { $library->getVersionDetector($this->detectorFactory)->detectVersion($library); diff --git a/src/Plugin/libraries/Locator/ChainLocator.php b/src/Plugin/libraries/Locator/ChainLocator.php new file mode 100644 index 0000000000000000000000000000000000000000..4e0acff84e1630c44e5552e2114e32b7eb9538de --- /dev/null +++ b/src/Plugin/libraries/Locator/ChainLocator.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Locator; + +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocatorInterface; + +/** + * Provides a locator utilizing a chain of other individual locators. + * + * @Locator("chain") + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ +class ChainLocator implements LocatorInterface { + + /** + * The locators to check. + * + * @var \Drupal\libraries\ExternalLibrary\Local\LocatorInterface[] + */ + protected $locators = []; + + /** + * Add a locator to the chain. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocatorInterface $locator + * A locator to add to the chain. + */ + public function addLocator(LocatorInterface $locator) { + $this->locators[] = $locator; + return $this; + } + + /** + * Locates a library. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library + * The library to locate. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate() + */ + public function locate(LocalLibraryInterface $library) { + foreach ($this->locators as $locator) { + $locator->locate($library); + if ($library->isInstalled()) { + return; + } + } + $library->setUninstalled(); + } + +} diff --git a/src/Plugin/libraries/Locator/GlobalLocator.php b/src/Plugin/libraries/Locator/GlobalLocator.php new file mode 100644 index 0000000000000000000000000000000000000000..3501338d4f66133e9c2d49ecb32ec9b70ef45650 --- /dev/null +++ b/src/Plugin/libraries/Locator/GlobalLocator.php @@ -0,0 +1,76 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Locator; + +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocatorInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a locator based on global configuration. + * + * @Locator("global") + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ +class GlobalLocator implements LocatorInterface, ContainerFactoryPluginInterface { + + /** + * The Drupal config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The locator factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $locatorFactory; + + /** + * Constructs a global locator. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The Drupal config factory service. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * The locator factory. + */ + public function __construct(ConfigFactoryInterface $config_factory, FactoryInterface $locator_factory) { + $this->configFactory = $config_factory; + $this->locatorFactory = $locator_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('config.factory'), + $container->get('plugin.manager.libraries.locator') + ); + } + + /** + * Locates a library. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library + * The library to locate. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate() + */ + public function locate(LocalLibraryInterface $library) { + foreach ($this->configFactory->get('libraries.settings')->get('global_locators') as $locator) { + $this->locatorFactory->createInstance($locator['id'], $locator['configuration'])->locate($library); + if ($library->isInstalled()) { + return; + } + } + $library->setUninstalled(); + } + +} diff --git a/src/Plugin/libraries/Locator/StreamLocator.php b/src/Plugin/libraries/Locator/UriLocator.php similarity index 73% rename from src/Plugin/libraries/Locator/StreamLocator.php rename to src/Plugin/libraries/Locator/UriLocator.php index 18731763416d1c59bc33d87cd5adb4f2c965824c..348be0c247c8aec40cbba68e28c132437b9c0938 100644 --- a/src/Plugin/libraries/Locator/StreamLocator.php +++ b/src/Plugin/libraries/Locator/UriLocator.php @@ -10,7 +10,7 @@ use Drupal\libraries\Plugin\MissingPluginConfigurationException; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides a locator utilizing a stream wrapper. + * Provides a locator utilizing a URI. * * It makes the following assumptions: * - The library files can be accessed using a specified stream. @@ -19,11 +19,11 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * - The first component of the file URIs are the library IDs (i.e. file URIs * are of the form: scheme://library-id/path/to/file/filename). * - * @Locator("stream") + * @Locator("uri") * * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface */ -class StreamLocator implements LocatorInterface, ContainerFactoryPluginInterface { +class UriLocator implements LocatorInterface, ContainerFactoryPluginInterface { /** * The stream wrapper manager. @@ -33,33 +33,33 @@ class StreamLocator implements LocatorInterface, ContainerFactoryPluginInterface protected $streamWrapperManager; /** - * The scheme of the stream wrapper. + * The URI to check. * * @var string */ - protected $scheme; + protected $uri; /** - * Constructs a stream locator. + * Constructs a URI locator. * * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager * The stream wrapper manager. - * @param string $scheme - * The scheme of the stream wrapper. + * @param string $uri + * The URI to check. */ - public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, $scheme) { + public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, $uri) { $this->streamWrapperManager = $stream_wrapper_manager; - $this->scheme = (string) $scheme; + $this->uri = (string) $uri; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - if (!isset($configuration['scheme'])) { - throw new MissingPluginConfigurationException($plugin_id, $plugin_definition, $configuration, 'scheme'); + if (!isset($configuration['uri'])) { + throw new MissingPluginConfigurationException($plugin_id, $plugin_definition, $configuration, 'uri'); } - return new static($container->get('stream_wrapper_manager'), $configuration['scheme']); + return new static($container->get('stream_wrapper_manager'), $configuration['uri']); } /** @@ -72,19 +72,18 @@ class StreamLocator implements LocatorInterface, ContainerFactoryPluginInterface */ public function locate(LocalLibraryInterface $library) { /** @var \Drupal\Core\StreamWrapper\LocalStream $stream_wrapper */ - $stream_wrapper = $this->streamWrapperManager->getViaScheme($this->scheme); + $stream_wrapper = $this->streamWrapperManager->getViaUri($this->uri); assert('$stream_wrapper instanceof \Drupal\Core\StreamWrapper\LocalStream'); // Calling LocalStream::getDirectoryPath() explicitly avoids the realpath() // usage in LocalStream::getLocalPath(), which breaks if Libraries API is // symbolically linked into the Drupal installation. - $path = $stream_wrapper->getDirectoryPath() . '/' . $library->getId(); - - if (is_dir($path) && is_readable($path)) { - $library->setLocalPath($path); - } - else { - $library->setUninstalled(); + list($scheme, $target) = explode('://', $this->uri, 2); + $base_path = str_replace('//', '/', $stream_wrapper->getDirectoryPath() . '/' . $target . '/' . $library->getId()); + if (is_dir($base_path) && is_readable($base_path)) { + $library->setLocalPath($base_path); + return; } + $library->setUninstalled(); } } diff --git a/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php b/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..84eb86eb1e532f06e8ded013c6f9b6da93587d34 --- /dev/null +++ b/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary; + +use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; +use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; + +/** + * Tests that a global locator can be properly used to load a libraries. + * + * @group libraries + */ +class GlobalLocatorTest extends LibraryTypeKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Assign our test stream (which points to the test php lib) to the asset + // scheme. This gives us a scheme to work with in the test that is not + // used to locate a php lib by default. + $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream( + $this->container->get('module_handler'), + $this->container->get('string_translation'), + 'libraries' + )); + } + + /** + * {@inheritdoc} + */ + protected function getLibraryTypeId() { + return 'php_file'; + } + + /** + * Tests that the library is located via the global loactor. + */ + public function testGlobalLocator() { + // By default the library will not be locatable (control assertion) until we + // add the asset stream to the global loctors conf list. + $library = $this->getLibrary(); + $this->assertFalse($library->isInstalled()); + $config_factory = $this->container->get('config.factory'); + $config_factory->getEditable('libraries.settings') + ->set('global_locators', [['id' => 'uri', 'configuration' => ['uri' => 'asset://']]]) + ->save(); + $library = $this->getLibrary(); + $this->assertTrue($library->isInstalled()); + } + +}