diff --git a/libraries.api.php b/libraries.api.php index e89c3da153b9dad8dd1439519426fc1085fc48a1..859e6023eb4529bcdb3731fefc7a44971744aabe 100644 --- a/libraries.api.php +++ b/libraries.api.php @@ -42,15 +42,79 @@ * files. This has yet to be done. See https://www.drupal.org/node/773508 for * more information. * - * @section sec_definitions Library types + * @section sec_types Library types * Libraries are classed objects that implement LibraryInterface. This generic * interface only dictates that a library is aware of its ID. Any further * functionality depends on the type of library, each type of library comes with - * a dedicated interface. + * a dedicated interface. See LibraryInterface for more information. + * + * @subsection sub_types_version Version detection + * A central aspect of Libraries API is version detection. Modules or themes may + * only work with a specific version of an external library, so Libraries API + * needs a way to detect the version of a library by inspecting the library + * files. + * + * As the mechanism for doing this is generally not specific to any one + * library, it is handled by version detector plugins. A 'line_pattern' plugin + * that scans a file line by line whether for whether a pattern containing the + * version is matched. It can be used if the version is always specified in a + * particular place in a particular file, for example a changelog. See + * VersionDetectorInterface and LinePatternDetector for more information. + * + * @subsection sub_types_dependency Dependency handling + * Many libraries depend on other libraries to function. Thus, most library + * classes should implement DependentLibraryInterface to allow libraries to + * declare their dependencies as part of their metadata. In case of API changes + * in the dependencies libraries need to be able to declare dependencies on + * specific versions or version ranges of other libraries. This has yet to be + * implemented. + * + * Furthermore, Libraries API must also maintain a list of libraries that are + * required by the installed installation profile, modules, and themes + * (extensions). With this information installation of extensions with library + * dependencies can be prevented until the libraries are properly installed. + * This is currently not implemented. In the future this will be used to + * automatically retrieve library definitions of required libraries, and + * possibly to automatically download the libraries themselves. + * + * To declare library dependencies extensions can place a 'library_dependencies' + * key in their info file with a list of library machine names as the value. + * For example: + * @code + * name: My module + * type: module + * core: 8.x + * library_dependencies: + * - flexslider + * - jquery_mobile + * @endcode + * + * @subsection sub_types_asset Asset libraries + * With Drupal 8 relying on Composer for autoloading and dependency resolution + * of PHP libraries, asset libraries are the primary use-case for Libraries API. + * Because asset libraries cannot be loaded ad-hoc, but must be attached to a + * renderable element, Libraries API registers external asset libraries that are + * required by the installed extensions with the core asset library system. See + * AssetLibraryInterface for more information. + * + * @subsection sub_types_php_file + * For feature parity with the Drupal 7 version of this module, a PHP file + * library type is provided, that can load a list of PHP files on demand. + * Generally, it is encouraged to use Composer instead of this library type and + * avoid Libraries API altogether for PHP libraries. See PhpFileLibraryInterface + * for more information. + * + * This library type might be removed in a future version of Libraries API. * * @see \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface * @see \Drupal\libraries\ExternalLibrary\Definition\StreamDefinitionDiscovery * @see \Drupal\libraries\ExternalLibrary\LibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + * @see \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface * * @} */ diff --git a/libraries.module b/libraries.module index 4a5c9cb63ad1929df9f9ddf2af9988ac1dda6a7b..b8698b55110e0f0e8376a90ca4d081f0c9426a34 100644 --- a/libraries.module +++ b/libraries.module @@ -7,7 +7,7 @@ use Drupal\Core\DrupalKernel; use Drupal\Core\Extension\ModuleHandler; -use Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface; +use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface; use Symfony\Component\Yaml\Parser; /** @@ -17,15 +17,15 @@ function libraries_library_info_build() { /** @var \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager */ $library_manager = \Drupal::service('libraries.manager'); - $core_libraries = []; + $attachable_libraries = []; foreach ($library_manager->getRequiredLibraryIds() as $external_library_id) { $external_library = $library_manager->getLibrary($external_library_id); - // @todo Use a library type listener instead. - if ($external_library instanceof AssetLibraryInterface) { - $core_libraries += $external_library->getAttachableAssetLibraries($library_manager); + $library_type = $external_library->getType(); + if ($library_type instanceof AttachableAssetLibraryRegistrationInterface) { + $attachable_libraries += $library_type->getAttachableAssetLibraries($external_library, $library_manager); } } - return $core_libraries; + return $attachable_libraries; } /** diff --git a/src/ExternalLibrary/Asset/AssetLibrary.php b/src/ExternalLibrary/Asset/AssetLibrary.php index 6ddbf1b81a728ec84dcfd39a604ab2fa55ce07a6..e4471586e53eb2ce7d6ec5de21ddae76c61a3bc1 100644 --- a/src/ExternalLibrary/Asset/AssetLibrary.php +++ b/src/ExternalLibrary/Asset/AssetLibrary.php @@ -3,21 +3,20 @@ namespace Drupal\libraries\ExternalLibrary\Asset; use Drupal\Component\Plugin\Factory\FactoryInterface; -use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException; use Drupal\libraries\ExternalLibrary\LibraryBase; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait; use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface; use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryTrait; -use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; /** - * Provides a base asset library implementation. + * Provides a class for a single attachable asset library. */ class AssetLibrary extends LibraryBase implements AssetLibraryInterface, - VersionedLibraryInterface, - DependentLibraryInterface, LocalLibraryInterface, RemoteLibraryInterface { @@ -25,10 +24,30 @@ class AssetLibrary extends LibraryBase implements use LocalLibraryTrait, RemoteLibraryTrait, - SingleAssetLibraryTrait, LocalRemoteAssetTrait ; + /** + * An array containing the CSS assets of the library. + * + * @var array + */ + protected $cssAssets = []; + + /** + * An array containing the JavaScript assets of the library. + * + * @var array + */ + protected $jsAssets = []; + + /** + * An array of attachable asset library IDs that this library depends on. + * + * @todo Explain the difference to regular dependencies. + */ + protected $attachableDependencies = []; + /** * Construct an external library. * @@ -36,22 +55,56 @@ class AssetLibrary extends LibraryBase implements * The library ID. * @param array $definition * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $library_type + * The library type of the library. */ - public function __construct($id, array $definition) { - parent::__construct($id, $definition); + public function __construct($id, array $definition, LibraryTypeInterface $library_type) { + parent::__construct($id, $definition, $library_type); $this->remoteUrl = $definition['remote_url']; $this->cssAssets = $definition['css']; $this->jsAssets = $definition['js']; + $this->attachableDependencies = $definition['attachable_dependencies']; } /** * {@inheritdoc} */ - protected static function definitionDefaults() { - return parent::definitionDefaults() + [ + protected static function processDefinition(array &$definition) { + parent::processDefinition($definition); + $definition += [ 'remote_url' => '', 'css' => [], 'js' => [], + 'attachable_dependencies' => [], + ]; + } + + /** + * Returns a core library array structure for this library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + * The library manager that can be used to fetch dependencies. + * + * @return array + * + * @see \Drupal\libraries\ExternalLibrary\Asset\getAttachableAssetLibraries::getAttachableAssetLibraries() + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException + * @throws \Drupal\Component\Plugin\Exception\PluginException + * + * @todo Document the return value. + */ + public function getAttachableAssetLibrary(LibraryManagerInterface $library_manager) { + if (!$this->canBeAttached()) { + throw new LibraryNotInstalledException($this); + } + return [ + 'version' => $this->getVersion(), + 'css' => $this->processCssAssets($this->cssAssets), + 'js' => $this->processJsAssets($this->jsAssets), + 'dependencies' => $this->attachableDependencies, ]; } diff --git a/src/ExternalLibrary/Asset/AssetLibraryInterface.php b/src/ExternalLibrary/Asset/AssetLibraryInterface.php index 1beb8ecf3ffaca819783bafb5b5b63e8267a6750..eac629024d5ce2e3928d1af45531e2263a7d1827 100644 --- a/src/ExternalLibrary/Asset/AssetLibraryInterface.php +++ b/src/ExternalLibrary/Asset/AssetLibraryInterface.php @@ -2,17 +2,48 @@ namespace Drupal\libraries\ExternalLibrary\Asset; +use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface; use Drupal\libraries\ExternalLibrary\LibraryInterface; use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; /** - * Provides an interface for library with assets. + * Provides an interface for external asset libraries with a single library. * + * Asset is the generic term for CSS and JavaScript files. + * + * In order to load assets of external libraries as part of a page request the + * assets must be registered with Drupal core's library system. Therefore, + * Libraries API makes all libraries that are required by the installed + * installation profile, modules, and themes available as core asset libraries + * with the identifier 'libraries/[machine_name]' where '[machine_name]' is + * the Libraries API machine name of the external library. + * + * Thus, assuming that the external library 'flexslider' has been declared as a + * dependency, for example, it can be attached to a render array in the $build + * variable with the following code: + * @code + * $build['#attached']['library'] = ['libraries/flexslider']; + * @endcode + * + * In some cases an external library may contain multiple components, that + * should be loadable independently from each other. In this case, implement + * MultipleAssetLibraryInterface instead. + * + * @see libraries_library_info_build() * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface + * + * @todo Support loading of source or minified assets. + * @todo Document how library dependencies work. * - * @todo Explain + * @ingroup libraries */ -interface AssetLibraryInterface extends LibraryInterface { +interface AssetLibraryInterface extends + LibraryInterface, + VersionedLibraryInterface, + DependentLibraryInterface +{ /** * Returns a core asset library array structure for this library. @@ -22,7 +53,6 @@ interface AssetLibraryInterface extends LibraryInterface { * * @return array * - * @see libraries_library_info_build() * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait * * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException @@ -30,6 +60,6 @@ interface AssetLibraryInterface extends LibraryInterface { * @todo Document the return value. * @todo Reconsider passing the library manager. */ - public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager); + public function getAttachableAssetLibrary(LibraryManagerInterface $library_manager); } diff --git a/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php b/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8a8a6e5cfb1496dce64fa7d1f6c43069cad13e41 --- /dev/null +++ b/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; + +/** + * An interface for library types that want to react to library instantiation. + */ +interface AttachableAssetLibraryRegistrationInterface { + + /** + * Reacts to the instantiation of a library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $external_library + * The library that is being instantiated. + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + */ + public function getAttachableAssetLibraries(LibraryInterface $external_library, LibraryManagerInterface $library_manager); + +} diff --git a/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php b/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php index 09d3efe9649b7ece2fbdd45cc7a979a1d6a35982..e673caba975e86faf32d2d7249d77d3dfb5d1cc7 100644 --- a/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php +++ b/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php @@ -8,30 +8,14 @@ namespace Drupal\libraries\ExternalLibrary\Asset; * If the library files are available locally, they are served locally. * Otherwise, the remote files are served, assuming a remote URL is specified. * - * This trait should only be used in classes implementing AssetLibraryInterface, - * LocalLibraryInterface and RemoteLibraryInterface. + * This trait should only be used in classes implementing LocalLibraryInterface + * and RemoteLibraryInterface. * - * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait - * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface * @see \Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface */ trait LocalRemoteAssetTrait { - /** - * An array containing the CSS assets of the library. - * - * @var array - */ - protected $cssAssets; - - /** - * An array containing the JavaScript assets of the library. - * - * @var array - */ - protected $jsAssets; - /** * Checks whether this library can be attached. * @@ -74,6 +58,8 @@ trait LocalRemoteAssetTrait { /** * Gets the CSS assets attached to this library. * + * @param array $assets + * * @return array * An array of CSS assets of the library following the core library CSS * structure. The keys of the array must be among the SMACSS categories @@ -85,10 +71,10 @@ trait LocalRemoteAssetTrait { * * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::getCssAssets() */ - protected function getCssAssets() { + protected function processCssAssets(array $assets) { // @todo Consider somehow caching the processed information. $processed_assets = []; - foreach ($this->cssAssets as $category => $category_assets) { + foreach ($assets as $category => $category_assets) { // @todo Somehow consolidate this with getJsAssets(). foreach ($category_assets as $filename => $options) { $processed_assets[$category][$this->getPathPrefix() . '/' . $filename] = $options; @@ -100,6 +86,8 @@ trait LocalRemoteAssetTrait { /** * Gets the JavaScript assets attached to this library. * + * @param array $assets + * * @return array * An array of JavaScript assets of the library. The keys of the array are * the file paths of the JavaScript files and the values are JavaScript @@ -107,11 +95,11 @@ trait LocalRemoteAssetTrait { * * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::getJsAssets() */ - protected function getJsAssets() { + protected function processJsAssets(array $assets) { // @todo Consider somehow caching the processed information. $processed_assets = []; // @todo Somehow consolidate this with getCssAssets(). - foreach ($this->jsAssets as $filename => $options) { + foreach ($assets as $filename => $options) { $processed_assets[$this->getPathPrefix() . '/' . $filename] = $options; } return $processed_assets; diff --git a/src/ExternalLibrary/Asset/MultipleAssetLibrary.php b/src/ExternalLibrary/Asset/MultipleAssetLibrary.php new file mode 100644 index 0000000000000000000000000000000000000000..0e424475a2145536e028d58c0b4548530d61f143 --- /dev/null +++ b/src/ExternalLibrary/Asset/MultipleAssetLibrary.php @@ -0,0 +1,119 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException; +use Drupal\libraries\ExternalLibrary\LibraryBase; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait; +use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface; +use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryTrait; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; + +/** + * Provides a class for a library with multiple attachable asset libraries. + */ +class MultipleAssetLibrary extends LibraryBase implements + MultipleAssetLibraryInterface, + VersionedLibraryInterface, + DependentLibraryInterface, + LocalLibraryInterface, + RemoteLibraryInterface +{ + + use + LocalLibraryTrait, + RemoteLibraryTrait, + LocalRemoteAssetTrait + ; + + /** + * An array of attachable asset libraries. + */ + protected $libraries = []; + + /** + * Construct an external library. + * + * @param string $id + * The library ID. + * @param array $definition + * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $library_type + * The library type of the library. + */ + public function __construct($id, array $definition, LibraryTypeInterface $library_type) { + parent::__construct($id, $definition, $library_type); + $this->remoteUrl = $definition['remote_url']; + $this->libraries = $definition['libraries']; + } + + /** + * {@inheritdoc} + */ + protected static function processDefinition(array &$definition) { + parent::processDefinition($definition); + $definition += [ + 'remote_url' => '', + 'libraries' => [], + ]; + foreach ($definition['libraries'] as &$library) { + $library += [ + 'css' => [], + 'js' => [], + 'dependencies' => [], + ]; + } + } + + /** + * Returns a core library array structure for this library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + * The library manager that can be used to fetch dependencies. + * + * @return array + * + * @see \Drupal\libraries\ExternalLibrary\Asset\getAttachableAssetLibraries::getAttachableAssetLibraries() + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException + * @throws \Drupal\Component\Plugin\Exception\PluginException + * + * @todo Document the return value. + */ + public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager) { + if (!$this->canBeAttached()) { + throw new LibraryNotInstalledException($this); + } + $attachable_libraries = []; + foreach ($this->libraries as $attachable_library_id => $attachable_library) { + $attachable_libraries[$attachable_library_id] = [ + 'version' => $this->getVersion(), + 'css' => $this->processCssAssets($attachable_library['css']), + 'js' => $this->processJsAssets($attachable_library['js']), + 'dependencies' => $attachable_library['dependencies'], + ]; + } + return $attachable_libraries; + } + + /** + * Gets the locator of this library using the locator factory. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * + * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocator() + */ + public function getLocator(FactoryInterface $locator_factory) { + return $locator_factory->createInstance('stream', ['scheme' => 'asset']); + } + +} diff --git a/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php b/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5dc43ffdc700714fad11a149a0e69aac727411a3 --- /dev/null +++ b/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; + +/** + * Provides an interface for external asset libraries with multiple libraries. + * + * See SingleAssetLibraryInterface for more information on external asset + * libraries in general. + * + * In case an external asset library contains multiple components that should + * be loadable independently from each other, Libraries API registers each + * library component as a separate library in the core asset library system. The + * resulting core library identifier is + * 'libraries/[machine_name].[component_name]' where '[machine_name]' is the + * Libraries API machine name of the external library and '[component_name]' is + * the component name specified by the library definition. + * + * Thus, assuming that the external library 'bootstrap' has been declared as a + * dependency, for example, and it has 'button' and 'form' components, they can + * be attached to a render array in the $build variable with the following code: + * @code + * $build['#attached']['library'] = [ + * 'libraries/bootstrap.button', + * 'libraries/bootstrap.form', + * ]; + * @endcode + * + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface + * + * @todo Support loading of source or minified assets. + * @todo Document how library dependencies work. + */ +interface MultipleAssetLibraryInterface extends LibraryInterface { + + /** + * Separates the library machine name from its component name. + * + * The period is chosen in alignment with core asset libraries, which are + * named, for example, 'core/jquery.once'. + */ + const SEPARATOR = '.'; + + /** + * Returns a core asset library array structure for this library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + * The library manager that can be used to fetch dependencies. + * + * @return array + * + * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException + * + * @todo Document the return value. + * @todo Reconsider passing the library manager. + */ + public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager); + +} diff --git a/src/ExternalLibrary/Asset/SingleAssetLibraryTrait.php b/src/ExternalLibrary/Asset/SingleAssetLibraryTrait.php deleted file mode 100644 index e2f78ad7bfecb41d1545ff085494186f675b1736..0000000000000000000000000000000000000000 --- a/src/ExternalLibrary/Asset/SingleAssetLibraryTrait.php +++ /dev/null @@ -1,152 +0,0 @@ -<?php - -namespace Drupal\libraries\ExternalLibrary\Asset; - -use Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException; -use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; - -/** - * Provides a trait for external libraries that contain a single asset library. - * - * This trait should only be used by classes implementing - * ExternalLibraryInterface. - * - * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface - * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryInterface - * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface - */ -trait SingleAssetLibraryTrait { - - /** - * Returns a core library array structure for this library. - * - * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager - * The library manager that can be used to fetch dependencies. - * - * @return array - * - * @see \Drupal\libraries\ExternalLibrary\Asset\getAttachableAssetLibraries::getAttachableAssetLibraries() - * - * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException - * @throws \Drupal\Component\Plugin\Exception\PluginException - * - * @todo Document the return value. - */ - public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager) { - $libraries = []; - if ($this->canBeAttached()) { - $libraries[$this->getId()] = [ - 'version' => $this->getVersion(), - 'css' => $this->getCssAssets(), - 'js' => $this->getJsAssets(), - 'dependencies' => $this->processDependencies($library_manager, $this->getDependencies()), - ]; - } - return $libraries; - } - - /** - * Processes a list of dependencies into a list of attachable library IDs. - * - * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager - * The library manager that can be used to fetch dependencies. - * @param \Drupal\libraries\ExternalLibrary\LibraryInterface[] $dependency_ids - * An list of external libraries. - * - * @return string[] - * A list of attachable asset library IDs. - * - * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException - * @throws \Drupal\Component\Plugin\Exception\PluginException - */ - protected function processDependencies(LibraryManagerInterface $library_manager, array $dependency_ids) { - $attachable_dependency_ids = []; - foreach ($dependency_ids as $dependency_id) { - $dependency = $library_manager->getLibrary($dependency_id); - if (!$dependency instanceof AssetLibraryInterface) { - // @todo Somehow integrate this with canBeAttached(). - /** @var \Drupal\libraries\ExternalLibrary\LibraryInterface $this */ - throw new InvalidLibraryDependencyException($this, $dependency); - } - - $attachable_dependency_ids = array_keys($dependency->getAttachableAssetLibraries($library_manager)); - foreach ($attachable_dependency_ids as $attachable_dependency_id) { - // @todo It is not very elegant to hard-code the namespace here. - $attachable_dependency_ids[] = 'libraries/' . $attachable_dependency_id; - } - } - return $attachable_dependency_ids; - } - - /** - * Checks whether this library can be attached. - * - * @return bool - * TRUE if the library can be attached; FALSE otherwise. - */ - abstract protected function canBeAttached(); - - /** - * Returns the ID of the library. - * - * @return string - * The library ID. This must be unique among all known libraries. - * - * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryInterface::getId() - */ - abstract public function getId(); - - /** - * Returns the currently installed version of the library. - * - * @return string - * The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5. - * - * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryInterface::getVersion() - */ - abstract protected function getVersion(); - - /** - * Returns the libraries dependencies, if any. - * - * @return array - * An array of library IDs of libraries that the library depends on. - * - * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryInterface::getDependencies()() - */ - abstract protected function getDependencies(); - - /** - * Gets the CSS assets attached to this library. - * - * @return array - * An array of CSS assets of the library following the core library CSS - * structure. The keys of the array must be among the SMACSS categories - * 'base', 'layout, 'component', 'state', and 'theme'. The value of each - * category is in turn an array where the keys are the file paths of the CSS - * files and values are CSS options. - * - * @see https://smacss.com/ - * - * @todo Expand documentation. - * @todo Consider adding separate methods for the CSS categories. - */ - abstract protected function getCssAssets(); - - /** - * Gets the JavaScript assets attached to this library. - * - * @return array - * An array of JavaScript assets of the library. The keys of the array are - * the file paths of the JavaScript files and the values are JavaScript - * options. - * - * @todo Expand documentation. - */ - abstract protected function getJsAssets(); - -} diff --git a/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php b/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php index 2d7bebdf87d141bd901ea96bbbbf7605cac82aa1..61fc589ccab3fabec86faf97a7b3db0caab25bdc 100644 --- a/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php +++ b/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php @@ -10,6 +10,8 @@ namespace Drupal\libraries\ExternalLibrary\Definition; * getDefinitions() method. * * @see \Drupal\Component\Plugin\Discovery\DiscoveryInterface + * + * @ingroup libraries */ interface DefinitionDiscoveryInterface { diff --git a/src/ExternalLibrary/Definition/StreamDefinitionDiscovery.php b/src/ExternalLibrary/Definition/StreamDefinitionDiscovery.php index 328b0429babe0e745437e4056502cac608b27e1d..d2bf986c61844021c7d59cd114e1d39f88bd1906 100644 --- a/src/ExternalLibrary/Definition/StreamDefinitionDiscovery.php +++ b/src/ExternalLibrary/Definition/StreamDefinitionDiscovery.php @@ -21,6 +21,8 @@ use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundExceptio * itself needing to change. * * @see \Drupal\libraries\StreamWrapper\LibraryDefinitionsStream + * + * @ingroup libraries */ class StreamDefinitionDiscovery implements DefinitionDiscoveryInterface { diff --git a/src/ExternalLibrary/Dependency/DependentLibraryInterface.php b/src/ExternalLibrary/Dependency/DependentLibraryInterface.php index 487a59fa92921951347b0ea4ac4fa6b37c9f5707..fd838101f325f81b6dc8c8a8c5e4d5d3ebab7827 100644 --- a/src/ExternalLibrary/Dependency/DependentLibraryInterface.php +++ b/src/ExternalLibrary/Dependency/DependentLibraryInterface.php @@ -6,6 +6,8 @@ use Drupal\libraries\ExternalLibrary\LibraryInterface; /** * Provides an interface for libraries that depend on other libraries. + * + * @todo Implement versioned dependencies. */ interface DependentLibraryInterface extends LibraryInterface { diff --git a/src/ExternalLibrary/LibraryBase.php b/src/ExternalLibrary/LibraryBase.php index 64ee6cbbc70f4921a5f79321ef0e57fad14fc256..8571552c7dda49504545397e96472a2c14b360d2 100644 --- a/src/ExternalLibrary/LibraryBase.php +++ b/src/ExternalLibrary/LibraryBase.php @@ -4,6 +4,7 @@ namespace Drupal\libraries\ExternalLibrary; use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface; use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryTrait; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryTrait; @@ -23,6 +24,13 @@ abstract class LibraryBase implements VersionedLibraryTrait ; + /** + * The library type of this library. + * + * @var \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface + */ + protected $type; + /** * Constructs a library. * @@ -30,9 +38,12 @@ abstract class LibraryBase implements * The library ID. * @param array $definition * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type + * The library type of this library. */ - public function __construct($id, array $definition) { + public function __construct($id, array $definition, LibraryTypeInterface $type) { $this->id = (string) $id; + $this->type = $type; $this->dependencies = $definition['dependencies']; $this->versionDetector = $definition['version_detector']; } @@ -40,19 +51,19 @@ abstract class LibraryBase implements /** * {@inheritdoc} */ - public static function create($id, array $definition) { - $definition += static::definitionDefaults(); - return new static($id, $definition); + public static function create($id, array $definition, LibraryTypeInterface $type) { + static::processDefinition($definition); + return new static($id, $definition, $type); } /** * Gets library definition defaults. * - * @return array - * An array of library definition defaults. + * @param array $definition + * A library definition array. */ - protected static function definitionDefaults() { - return [ + protected static function processDefinition(array &$definition) { + $definition += [ 'dependencies' => [], // @todo This fallback is not very elegant. 'version_detector' => [ @@ -62,4 +73,11 @@ abstract class LibraryBase implements ]; } + /** + * {@inheritdoc} + */ + public function getType() { + return $this->type; + } + } diff --git a/src/ExternalLibrary/LibraryInterface.php b/src/ExternalLibrary/LibraryInterface.php index 5d0643e3e816f3df1d5b01c41aaf7d2dfb6185c3..ebf43a9b9fdccffc2fd80145265b77c4446c6dc6 100644 --- a/src/ExternalLibrary/LibraryInterface.php +++ b/src/ExternalLibrary/LibraryInterface.php @@ -2,21 +2,15 @@ namespace Drupal\libraries\ExternalLibrary; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; + /** * Provides an interface for different types of external libraries. + * + * @ingroup libraries */ interface LibraryInterface { - /** - * Returns the ID of the library. - * - * @return string - * The library ID. This must be unique among all known libraries. - * - * @todo Define what constitutes a "known" library. - */ - public function getId(); - /** * Creates an instance of the library from its definition. * @@ -24,11 +18,27 @@ interface LibraryInterface { * The library ID. * @param array $definition * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type + * The library type of this library. * * @return static + */ + public static function create($id, array $definition, LibraryTypeInterface $type); + + /** + * Returns the ID of the library. + * + * @return string + * The library ID. This must be unique among all known libraries. + */ + public function getId(); + + /** + * Returns the library type of the library. * - * @todo Consider passing in some stuff that might be useful. + * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface + * The library of the library. */ - public static function create($id, array $definition); + public function getType(); } diff --git a/src/ExternalLibrary/LibraryManager.php b/src/ExternalLibrary/LibraryManager.php index b67dc1ffed12005ebbbda33434a9c645f3cc5df5..3392c1ec20a47ef4edeee1a4390d898728b47dae 100644 --- a/src/ExternalLibrary/LibraryManager.php +++ b/src/ExternalLibrary/LibraryManager.php @@ -11,6 +11,10 @@ use Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface; /** * Provides a manager for external libraries. + * + * @todo Dispatch events at various points in the library lifecycle. + * @todo Automatically load PHP file libraries that are required by modules or + * themes. */ class LibraryManager implements LibraryManagerInterface { @@ -100,7 +104,7 @@ class LibraryManager implements LibraryManagerInterface { $class = $library_type->getLibraryClass(); // @todo Make sure that the library class implements the correct interface. - $library = $class::create($id, $definition); + $library = $class::create($id, $definition, $library_type); if ($library_type instanceof LibraryCreationListenerInterface) { $library_type->onLibraryCreate($library); @@ -110,9 +114,10 @@ class LibraryManager implements LibraryManagerInterface { } /** - * @param $id - * @param $definition - * @return object + * @param string $id + * @param array $definition + * + * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface */ protected function getLibraryType($id, $definition) { // @todo Validate that the type is a string. diff --git a/src/ExternalLibrary/PhpFile/PhpFileLibrary.php b/src/ExternalLibrary/PhpFile/PhpFileLibrary.php index e580b7ff58ed4e2778bb509ba886ff23e1697fbf..da46df3557d55abd10a31df5820a46c29982cbf6 100644 --- a/src/ExternalLibrary/PhpFile/PhpFileLibrary.php +++ b/src/ExternalLibrary/PhpFile/PhpFileLibrary.php @@ -6,6 +6,7 @@ use Drupal\Component\Plugin\Factory\FactoryInterface; use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException; use Drupal\libraries\ExternalLibrary\LibraryBase; use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; /** * Provides a base PHP file library implementation. @@ -28,17 +29,20 @@ class PhpFileLibrary extends LibraryBase implements PhpFileLibraryInterface { * The library ID. * @param array $definition * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type + * The library type of this library. */ - public function __construct($id, array $definition) { - parent::__construct($id, $definition); + public function __construct($id, array $definition, LibraryTypeInterface $type) { + parent::__construct($id, $definition, $type); $this->files = $definition['files']; } /** * {@inheritdoc} */ - protected static function definitionDefaults() { - return parent::definitionDefaults() + [ + protected static function processDefinition(array &$definition) { + parent::processDefinition($definition); + $definition += [ 'files' => [], ]; } diff --git a/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php b/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php index 558980312269957879a0d71d10a413f4f6862b40..01ca453713e55045af6947fcb5f39c86c15540fe 100644 --- a/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php +++ b/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php @@ -5,7 +5,7 @@ namespace Drupal\libraries\ExternalLibrary\PhpFile; use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; /** - * Provides an interface for libraries which can be loaded. + * Provides an interface for libraries with PHP files. * * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface */ diff --git a/src/ExternalLibrary/Type/LibraryTypeBase.php b/src/ExternalLibrary/Type/LibraryTypeBase.php new file mode 100644 index 0000000000000000000000000000000000000000..a21ef04b85e22bd4f60d54f5365a28e86ddbc264 --- /dev/null +++ b/src/ExternalLibrary/Type/LibraryTypeBase.php @@ -0,0 +1,77 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Type; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a base class for library types. + */ +abstract class LibraryTypeBase implements + LibraryTypeInterface, + LibraryCreationListenerInterface, + ContainerFactoryPluginInterface +{ + + use IdAccessorTrait; + + /** + * The locator factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $locatorFactory; + + /** + * The version detector factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $detectorFactory; + + /** + * Constructs the asset library type. + * + * @param string $plugin_id + * The plugin ID taken from the class annotation. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * The locator factory. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * The version detector factory. + */ + public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory) { + $this->id = $plugin_id; + $this->locatorFactory = $locator_factory; + $this->detectorFactory = $detector_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $container->get('plugin.manager.libraries.locator'), + $container->get('plugin.manager.libraries.version_detector') + ); + } + + /** + * {@inheritdoc} + */ + public function onLibraryCreate(LibraryInterface $library) { + if ($library instanceof LocalLibraryInterface) { + $library->getLocator($this->locatorFactory)->locate($library); + } + if ($library instanceof VersionedLibraryInterface) { + $library->getVersionDetector($this->detectorFactory)->detectVersion($library); + } + } + +} diff --git a/src/ExternalLibrary/Version/VersionDetectorInterface.php b/src/ExternalLibrary/Version/VersionDetectorInterface.php index da42b336be344ca9970416a53a67cfd2f60cb0fa..8b6d2a5bd04a2661ce569aeca00e78ae3d635295 100644 --- a/src/ExternalLibrary/Version/VersionDetectorInterface.php +++ b/src/ExternalLibrary/Version/VersionDetectorInterface.php @@ -4,6 +4,8 @@ namespace Drupal\libraries\ExternalLibrary\Version; /** * Provides an interface for version detectors. + * + * @ingroup libraries */ interface VersionDetectorInterface { diff --git a/src/ExternalLibrary/Version/VersionedLibraryInterface.php b/src/ExternalLibrary/Version/VersionedLibraryInterface.php index d9b8df46b028cda46d16684f176d0330784a081b..abeb758cd2974a3a2a50b94d1eef673c54a86d95 100644 --- a/src/ExternalLibrary/Version/VersionedLibraryInterface.php +++ b/src/ExternalLibrary/Version/VersionedLibraryInterface.php @@ -9,7 +9,17 @@ use Drupal\libraries\ExternalLibrary\LibraryInterface; * Provides an interface for versioned libraries. * * Version detection and negotiation is a key aspect of Libraries API's - * functionality so every type of library should implement this interface. + * functionality so most libraries should implement this interface. In theory, + * however, it might be possible for the same library to be available in + * multiple versions and, for example, different versions being loaded on + * different pages. In this case, a simple getVersion() method, does not make + * sense. To support such advanced version detection behavior in the future or + * in a separate module, version detection is split into a separate interface. + * + * @ingroup libraries + * + * @todo Support versioned metadata, i.e. different library file names or + * locations for different library versions. */ interface VersionedLibraryInterface extends LibraryInterface { diff --git a/src/Plugin/libraries/Type/AssetLibraryType.php b/src/Plugin/libraries/Type/AssetLibraryType.php index b2dbed91c5b8cbe502d3c7465fc5e72944f91070..ce0b276b17eb58aa46789392ae6eb6fb92e1c866 100644 --- a/src/Plugin/libraries/Type/AssetLibraryType.php +++ b/src/Plugin/libraries/Type/AssetLibraryType.php @@ -2,68 +2,16 @@ namespace Drupal\libraries\Plugin\libraries\Type; -use Drupal\Component\Plugin\Factory\FactoryInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\libraries\ExternalLibrary\Asset\AssetLibrary; +use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface; use Drupal\libraries\ExternalLibrary\LibraryInterface; -use Drupal\libraries\ExternalLibrary\Type\LibraryCreationListenerInterface; -use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; -use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; -use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; -use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase; /** * @LibraryType("asset") */ -class AssetLibraryType implements - LibraryTypeInterface, - LibraryCreationListenerInterface, - ContainerFactoryPluginInterface -{ - - use IdAccessorTrait; - - /** - * The locator factory. - * - * @var \Drupal\Component\Plugin\Factory\FactoryInterface - */ - protected $locatorFactory; - - /** - * The version detector factory. - * - * @var \Drupal\Component\Plugin\Factory\FactoryInterface - */ - protected $detectorFactory; - - /** - * Constructs the asset library type. - * - * @param string $plugin_id - * The plugin ID taken from the class annotation. - * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory - * The locator factory. - * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory - * The version detector factory. - */ - public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory) { - $this->id = $plugin_id; - $this->locatorFactory = $locator_factory; - $this->detectorFactory = $detector_factory; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $plugin_id, - $container->get('plugin.manager.libraries.locator'), - $container->get('plugin.manager.libraries.version_detector') - ); - } +class AssetLibraryType extends LibraryTypeBase implements AttachableAssetLibraryRegistrationInterface { /** * {@inheritdoc} @@ -75,15 +23,10 @@ class AssetLibraryType implements /** * {@inheritdoc} */ - public function onLibraryCreate(LibraryInterface $library) { - // The default implementation of asset libraries checks locally for library - // files, but this is not required. - if ($library instanceof LocalLibraryInterface) { - $library->getLocator($this->locatorFactory)->locate($library); - } - if ($library instanceof VersionedLibraryInterface) { - $library->getVersionDetector($this->detectorFactory)->detectVersion($library); - } + public function getAttachableAssetLibraries(LibraryInterface $library, LibraryManagerInterface $library_manager) { + assert('$library instanceof \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface'); + /** @var \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface $library */ + return [$library->getId() => $library->getAttachableAssetLibrary($library_manager)]; } } diff --git a/src/Plugin/libraries/Type/MultipleAssetLibraryType.php b/src/Plugin/libraries/Type/MultipleAssetLibraryType.php new file mode 100644 index 0000000000000000000000000000000000000000..31ec6a62c6c41aace30735496788f4f85118900d --- /dev/null +++ b/src/Plugin/libraries/Type/MultipleAssetLibraryType.php @@ -0,0 +1,48 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Type; + +use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface; +use Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibrary; +use Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase; + +/** + * @LibraryType("asset_multiple") + */ +class MultipleAssetLibraryType extends LibraryTypeBase implements AttachableAssetLibraryRegistrationInterface { + + /** + * {@inheritdoc} + */ + public function getLibraryClass() { + return MultipleAssetLibrary::class; + } + + /** + * {@inheritdoc} + */ + public function getAttachableAssetLibraries(LibraryInterface $external_library, LibraryManagerInterface $library_manager) { + assert('$external_library instanceof \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface'); + /** @var \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface $external_library */ + $attachable_libraries = []; + foreach ($external_library->getAttachableAssetLibraries($library_manager) as $component_name => $attachable_library) { + $attachable_library_id = $this->getAttachableLibraryId($external_library, $component_name); + $attachable_libraries[$attachable_library_id] = $attachable_library; + } + return $attachable_libraries; + } + + /** + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $external_library + * @param string $component_name + * + * @return string + */ + protected function getAttachableLibraryId(LibraryInterface $external_library, $component_name) { + return $external_library->getId() . MultipleAssetLibraryInterface::SEPARATOR . $component_name; + } + +} diff --git a/src/Plugin/libraries/Type/PhpFileLibraryType.php b/src/Plugin/libraries/Type/PhpFileLibraryType.php index 61ccf2528841519d6a5e04a95af594fbc0ae13d1..aa93b003c2976c84935cb04afb770a8b88c22d33 100644 --- a/src/Plugin/libraries/Type/PhpFileLibraryType.php +++ b/src/Plugin/libraries/Type/PhpFileLibraryType.php @@ -3,42 +3,17 @@ namespace Drupal\libraries\Plugin\libraries\Type; use Drupal\Component\Plugin\Factory\FactoryInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\libraries\ExternalLibrary\LibraryInterface; -use Drupal\libraries\ExternalLibrary\Type\LibraryCreationListenerInterface; use Drupal\libraries\ExternalLibrary\Type\LibraryLoadingListenerInterface; -use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary; use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface; -use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; -use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** * @LibraryType("php_file") */ -class PhpFileLibraryType implements - LibraryTypeInterface, - LibraryCreationListenerInterface, - LibraryLoadingListenerInterface, - ContainerFactoryPluginInterface -{ - - use IdAccessorTrait; - - /** - * The locator factory. - * - * @var \Drupal\Component\Plugin\Factory\FactoryInterface - */ - protected $locatorFactory; - - /** - * The version detector factory. - * - * @var \Drupal\Component\Plugin\Factory\FactoryInterface - */ - protected $detectorFactory; +class PhpFileLibraryType extends LibraryTypeBase implements LibraryLoadingListenerInterface { /** * The PHP file loader. @@ -60,9 +35,7 @@ class PhpFileLibraryType implements * The PHP file loader. */ public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory, PhpFileLoaderInterface $php_file_loader) { - $this->id = $plugin_id; - $this->locatorFactory = $locator_factory; - $this->detectorFactory = $detector_factory; + parent::__construct($plugin_id, $locator_factory, $detector_factory); $this->phpFileLoader = $php_file_loader; } @@ -85,17 +58,6 @@ class PhpFileLibraryType implements return PhpFileLibrary::class; } - /** - * {@inheritdoc} - */ - public function onLibraryCreate(LibraryInterface $library) { - /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface|\Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library */ - $library->getLocator($this->locatorFactory)->locate($library); - if ($library instanceof VersionedLibraryInterface) { - $library->getVersionDetector($this->detectorFactory)->detectVersion($library); - } - } - /** * {@inheritdoc} */ diff --git a/src/Plugin/libraries/VersionDetector/LinePatternDetector.php b/src/Plugin/libraries/VersionDetector/LinePatternDetector.php index 521d9939c08c88d813bf0bf924318ff52f0a2458..25a799c536d344e105400274e6ce2de5924a60d5 100644 --- a/src/Plugin/libraries/VersionDetector/LinePatternDetector.php +++ b/src/Plugin/libraries/VersionDetector/LinePatternDetector.php @@ -17,6 +17,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * particular format in a changelog or readme file, for example. * * @VersionDetector("line_pattern") + * + * @ingroup libraries */ class LinePatternDetector extends PluginBase implements VersionDetectorInterface, ContainerFactoryPluginInterface { diff --git a/tests/library_definitions/test_asset_multiple_library.yml b/tests/library_definitions/test_asset_multiple_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..093e9c830c9c56eb6ccfa75e00583f67683f9a25 --- /dev/null +++ b/tests/library_definitions/test_asset_multiple_library.yml @@ -0,0 +1,19 @@ +type: asset_multiple +version_detector: + id: static + configuration: + version: '1.0.0' +remote_url: http://example.com +libraries: + first: + css: + base: + example.first.css: { } + js: + example.first.js: { } + second: + css: + base: + example.second.css: { } + js: + example.second.js: { } diff --git a/tests/modules/libraries_test/libraries_test.info.yml b/tests/modules/libraries_test/libraries_test.info.yml index 8fed7fb2fb5fd7d147b7a5efeca40ce9bf697985..fb26baa1e84af2eee619247324f1f2f988968a4a 100644 --- a/tests/modules/libraries_test/libraries_test.info.yml +++ b/tests/modules/libraries_test/libraries_test.info.yml @@ -7,3 +7,4 @@ dependencies: hidden: TRUE library_dependencies: - test_asset_library + - test_asset_multiple_library diff --git a/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php index f7153fe4069e18aa1a47d3a4b4bb70fc167e418c..cb2551e22fcd2132a1b8b1b8f267c246be1d67b8 100644 --- a/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php +++ b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php @@ -3,37 +3,13 @@ namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; -use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; /** * Tests that external asset libraries are registered as core asset libraries. * * @group libraries */ -class AssetLibraryTest extends LibraryTypeKernelTestBase { - - /** - * {@inheritdoc} - * - * \Drupal\libraries\Extension requires system_get_info() which is in - * system.module. - */ - public static $modules = ['libraries', 'libraries_test', 'system']; - - /** - * The Drupal core library discovery. - * - * @var \Drupal\Core\Asset\LibraryDiscoveryInterface - */ - protected $coreLibraryDiscovery; - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - $this->coreLibraryDiscovery = $this->container->get('library.discovery'); - } +class AssetLibraryTest extends AssetLibraryTestBase { /** * {@inheritdoc} @@ -46,15 +22,18 @@ class AssetLibraryTest extends LibraryTypeKernelTestBase { * Tests that attachable asset library info is correctly gathered. */ public function testAttachableAssetInfo() { - /** @var \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary $library */ + /** @var \Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface $library_type */ + $library_type = $this->getLibraryType(); $library = $this->getLibrary(); - $expected = ['test_asset_library' => [ - 'version' => '1.0.0', - 'css' => ['base' => ['http://example.com/example.css' => []]], - 'js' => ['http://example.com/example.js' => []], - 'dependencies' => [], - ]]; - $this->assertEquals($expected, $library->getAttachableAssetLibraries($this->libraryManager)); + $expected = [ + 'test_asset_library' => [ + 'version' => '1.0.0', + 'css' => ['base' => ['http://example.com/example.css' => []]], + 'js' => ['http://example.com/example.js' => []], + 'dependencies' => [], + ], + ]; + $this->assertEquals($expected, $library_type->getAttachableAssetLibraries($library, $this->libraryManager)); } /** diff --git a/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..ae366328c9bbcbbb3505694dea78086457ce0947 --- /dev/null +++ b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; + +use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; + +/** + * Provides a base test class for asset library type tests. + */ +abstract class AssetLibraryTestBase extends LibraryTypeKernelTestBase { + + /** + * {@inheritdoc} + * + * \Drupal\libraries\Extension requires system_get_info() which is in + * system.module. + */ + public static $modules = ['system']; + + /** + * The Drupal core library discovery. + * + * @var \Drupal\Core\Asset\LibraryDiscoveryInterface + */ + protected $coreLibraryDiscovery; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->coreLibraryDiscovery = $this->container->get('library.discovery'); + } + +} diff --git a/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php b/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4a2af5c3d7a7424a13dd1d30faa93c0d040be8fe --- /dev/null +++ b/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php @@ -0,0 +1,172 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; + +use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; + +/** + * Tests that external asset libraries can register multiple core libraries. + * + * @group libraries + */ +class MultipleAssetLibraryTest extends AssetLibraryTestBase { + + /** + * {@inheritdoc} + */ + protected function getLibraryTypeId() { + return 'asset_multiple'; + } + + /** + * Tests that attachable asset library info is correctly gathered. + */ + public function testAttachableAssetInfo() { + /** @var \Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface $library_type */ + $library_type = $this->getLibraryType(); + $library = $this->getLibrary(); + $expected = [ + 'test_asset_multiple_library.first' => [ + 'version' => '1.0.0', + 'css' => ['base' => ['http://example.com/example.first.css' => []]], + 'js' => ['http://example.com/example.first.js' => []], + 'dependencies' => [], + ], + 'test_asset_multiple_library.second' => [ + 'version' => '1.0.0', + 'css' => ['base' => ['http://example.com/example.second.css' => []]], + 'js' => ['http://example.com/example.second.js' => []], + 'dependencies' => [], + ], + ]; + $this->assertEquals($expected, $library_type->getAttachableAssetLibraries($library, $this->libraryManager)); + } + + /** + * Tests that a remote asset library is registered as a core asset library. + * + * @see \Drupal\libraries\Extension\Extension + * @see \Drupal\libraries\Extension\ExtensionHandler + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryManager + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\Registry\ExternalLibraryRegistry + */ + public function testAssetLibraryRemote() { + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.first'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'external', + 'data' => 'http://example.com/example.first.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'external', + 'data' => 'http://example.com/example.first.js', + 'version' => '1.0.0', + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.second'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'external', + 'data' => 'http://example.com/example.second.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'external', + 'data' => 'http://example.com/example.second.js', + 'version' => '1.0.0', + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + } + + /** + * Tests that a local asset library is registered as a core asset library. + */ + public function testAssetLibraryLocal() { + $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream( + $this->container->get('module_handler'), + $this->container->get('string_translation'), + 'assets/vendor' + )); + $this->coreLibraryDiscovery->clearCachedDefinitions(); + + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.first'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.first.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.first.js', + 'version' => '1.0.0', + 'minified' => FALSE, + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.second'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.second.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.second.js', + 'version' => '1.0.0', + 'minified' => FALSE, + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + } + +} diff --git a/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php b/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php index 6efe4f3ca228f4af5b82c2cc854912ef4bfe1019..832eda563140fdee10412fc04e6aab33cc50bfd1 100644 --- a/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php +++ b/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php @@ -12,11 +12,6 @@ use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; */ class PhpFileLibraryTest extends LibraryTypeKernelTestBase { - /** - * {@inheritdoc} - */ - public static $modules = ['libraries', 'libraries_test']; - /** * {@inheritdoc} */ diff --git a/tests/src/Kernel/LibraryTypeKernelTestBase.php b/tests/src/Kernel/LibraryTypeKernelTestBase.php index c0ccaf96f0d8d97186e810bf082aeb60f19b474b..af52c9c133eab15de2d065d4a6048c0fdd0009fa 100644 --- a/tests/src/Kernel/LibraryTypeKernelTestBase.php +++ b/tests/src/Kernel/LibraryTypeKernelTestBase.php @@ -7,7 +7,7 @@ use Drupal\KernelTests\KernelTestBase; use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException; use Drupal\libraries\ExternalLibrary\LibraryInterface; -use Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; /** * Provides an improved version of the core kernel test base class. @@ -35,6 +35,16 @@ abstract class LibraryTypeKernelTestBase extends KernelTestBase { */ protected $modulePath; + /** + * {@inheritdoc} + */ + public static $modules = ['libraries', 'libraries_test']; + + /** + * Gets the ID of the library type that is being tested. + * + * @return string + */ abstract protected function getLibraryTypeId(); /** @@ -96,7 +106,10 @@ abstract class LibraryTypeKernelTestBase extends KernelTestBase { } /** - * @return \Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface + * Returns the library type that is being tested. + * + * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface + * The test library type. */ protected function getLibraryType() { try { @@ -111,7 +124,23 @@ abstract class LibraryTypeKernelTestBase extends KernelTestBase { } /** + * Retuns the library ID of the library used in the test. + * + * Defaults to 'test_[library_type]_library', where [library_type] is the + * ID of the library type being tested. + * + * @return string + */ + protected function getLibraryId() { + $type_id = $this->getLibraryTypeId(); + return "test_{$type_id}_library"; + } + + /** + * Returns the test library for this library type. + * * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + * The test library. */ protected function getLibrary() { try { @@ -131,13 +160,4 @@ abstract class LibraryTypeKernelTestBase extends KernelTestBase { } } - /** - * @param $type_id - * @return string - */ - protected function getLibraryId() { - $type_id = $this->getLibraryTypeId(); - return "test_{$type_id}_library"; - } - }