diff --git a/core/lib/Drupal/Core/Plugin/Discovery/TemplateDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/TemplateDiscovery.php index 1ecdc19d66..063d065f8d 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/TemplateDiscovery.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/TemplateDiscovery.php @@ -13,26 +13,27 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; /** - * Discovers plugins from Twig files. + * Discovers plugins from Twig template files. */ abstract class TemplateDiscovery implements DiscoveryInterface { use DiscoveryTrait; /** - * Defines the key in the discovered data where the file path is stored. + * The key in the output discovered data for the file path. */ const FILE_KEY = '_discovered_file_path'; /** - * An array of directories to scan, keyed by the provider. + * Directories to scan. * - * The value can either be a string or an array of strings. The string values - * should be the path of a directory to scan. + * Array whose keys are provider (extension) short names, and whose values can + * be either a single string path to scan for plugins, or an array of paths + * for this provider. * * @var array */ - protected $directories = []; + protected $directories; /** * The suffix for the file cache key. @@ -42,30 +43,52 @@ abstract class TemplateDiscovery implements DiscoveryInterface { protected $fileCacheKeySuffix; /** - * The key contained in the discovered data that identifies it. + * The key in the output discovered data that contains the ID. * * @var string */ protected $idKey; + /** + * The regular expression filter used to find relevant files. + * + * @var string + */ + protected $fileFilter; + + /** + * An extension that is allowed to provide plugins on behalf of others. + * + * @var string + */ + protected $masterProvider; + /** * Constructs a TemplateDiscovery object. * * @param array $directories - * An array of directories to scan, keyed by the provider. The value can - * either be a string or an array of strings. The string values should be - * the path of a directory to scan. + * Array whose keys are provider (extension) short names, and whose values + * can be either a single string path to scan for plugins, or an array of + * paths for this provider. * @param string $file_cache_key_suffix - * The file cache key suffix. This should be unique for each type of - * discovery. - * @param string $key - * (optional) The key contained in the discovered data that identifies it. - * Defaults to 'id'. + * The file cache key suffix. This should be unique for each class that + * extends this abstract class. + * @param string $master_provider + * (optional) The name of a provider that is allowed to provide plugins on + * behalf of other extensions. + * @param string $id_key + * (optional) The key to use in the output discovered data for the plugin + * ID. Defaults to 'id'. + * @param string $file_filter + * (optional) Regular expression pattern to filter file names. Defaults to + * a pattern that finds files with extension .html.twig. */ - public function __construct(array $directories, $file_cache_key_suffix, $key = 'id') { + public function __construct(array $directories, string $file_cache_key_suffix, string $master_provider = '', string $id_key = 'id', string $file_filter = '/\.html\.twig$/i') { $this->directories = $directories; $this->fileCacheKeySuffix = $file_cache_key_suffix; - $this->idKey = $key; + $this->masterProvider = $master_provider; + $this->idKey = $id_key; + $this->fileFilter = $file_filter; } /** @@ -74,7 +97,7 @@ public function __construct(array $directories, $file_cache_key_suffix, $key = ' public function getDefinitions() { $plugins = $this->findAll(); - // Flatten definitions into what's expected from plugins. + // Flatten definitions array. $definitions = []; foreach ($plugins as $list) { foreach ($list as $id => $definition) { @@ -86,13 +109,26 @@ public function getDefinitions() { } /** - * Returns an array of discoverable items. + * Discovers plugins and returns them. * * @return array - * An array of discovered data keyed by provider. + * An array of discovered plugin data. The outer array is keyed by + * provider. For each provider, the inner array is an associated array of + * plugin data, keyed by plugin ID. Each array of plugin data contains the + * following elements: + * - The plugin ID (the file name without the extension). The key name + * for this can be overridden in the constructor of this class, but + * defaults to 'id'. + * - provider: Short name of the extension that defined the plugin. + * - _discovered_file_path: Path to the file (see static::FILE_KEY). + * - Additional information from front matter in the file, as returned + * by the extending class's implementation of + * $this->validateAndSaveFrontMatter(). * * @throws \Drupal\Component\Discovery\DiscoveryException * Exception thrown if there is a problem during discovery. + * + * @see \Drupal\Component\FrontMatter\FrontMatter */ public function findAll() { $all = []; @@ -103,7 +139,7 @@ public function findAll() { // Try to load from the file cache first. foreach ($file_cache->getMultiple(array_keys($files)) as $file => $data) { - $all[$files[$file]][$data['id']] = $data; + $all[$files[$file]][$data[$this->idKey]] = $data; unset($files[$file]); } @@ -113,13 +149,14 @@ public function findAll() { foreach ($files as $file => $provider) { $plugin_id = substr(basename($file), 0, -10); list($file_name_provider,) = explode('.', $plugin_id, 2); - if ($provider !== $file_name_provider) { + if ($provider !== $file_name_provider && + (empty($this->masterProvider) || $provider != $this->masterProvider)) { throw new DiscoveryException("$file file name should begin with '$provider'"); } $data = [ // The plugin ID is derived from the filename. The extension // '.html.twig' is removed. - 'id' => $plugin_id, + $this->idKey => $plugin_id, 'provider' => $file_name_provider, static::FILE_KEY => $file, ]; @@ -133,8 +170,8 @@ public function findAll() { throw new DiscoveryException(sprintf('Malformed YAML in template "%s": %s.', $file, $e->getMessage())); } - $data = $this->validateAndSave($front_matter, $data); - $all[$provider][$data['id']] = $data; + $data = $this->validateAndSaveFrontMatter($front_matter, $data, $file); + $all[$provider][$data[$this->idKey]] = $data; $file_cache->set($file, $data); } } @@ -143,27 +180,31 @@ public function findAll() { } /** - * Saves and validates front matter for a discovered template plugin. + * Validates and saves front matter for a discovered template plugin. + * + * Your method should throw an exception of there is an error in the front + * matter. * * @param array $front_matter * Array of parsed front matter from the template. * @param array $data - * Array of plugin information, with keys: - * - id: Plugin ID from the file name. - * - provider: Short name of the provider of the plugin. - * - _discovered_file_path: Path to the template. + * Array of plugin definition information to add the front matter data to. + * @param string $file + * File name for this plugin. * * @return array - * $data with the necessary front matter added to the plugin definition. - * Throw an exception of there is an error in the front matter. + * $data with the front matter data added to the array. + * + * @see \Drupal\Component\FrontMatter\FrontMatter */ - abstract function validateAndSave(array $front_matter, array $data); + abstract protected function validateAndSaveFrontMatter(array $front_matter, array $data, string $file); /** - * Returns an array of providers keyed by file path. + * Finds files containing plugins. * * @return array - * An array of providers keyed by file path. + * An array whose keys are files containing plugins, and whose values are + * the provider (extension defining the plugin). */ protected function findFiles() { $file_list = []; @@ -172,7 +213,7 @@ protected function findFiles() { foreach ($directories as $directory) { if (is_dir($directory)) { /** @var \SplFileInfo $fileInfo */ - $iterator = new RegexDirectoryIterator($directory, '/\.html\.twig$/i'); + $iterator = new RegexDirectoryIterator($directory, $this->fileFilter); foreach ($iterator as $fileInfo) { $file_list[$fileInfo->getPathname()] = $provider; }