Issue #2349991 by fago, Xano, amateescu: Provide a trait for categorizing plugin managers and use it for conditions and actions
parent
a5f0e6afb2
commit
d3e3d70151
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Plugin\CategorizingPluginManagerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an interface for plugin managers that categorize plugin definitions.
|
||||
*/
|
||||
interface CategorizingPluginManagerInterface extends PluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets the names of all categories.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of translated categories, sorted alphabetically.
|
||||
*/
|
||||
public function getCategories();
|
||||
|
||||
/**
|
||||
* Gets sorted plugin definitions.
|
||||
*
|
||||
* @param array[]|null $definitions
|
||||
* (optional) The plugin definitions to sort. If omitted, all plugin
|
||||
* definitions are used.
|
||||
*
|
||||
* @return array[]
|
||||
* An array of plugin definitions, sorted by category and label.
|
||||
*/
|
||||
public function getSortedDefinitions(array $definitions = NULL);
|
||||
|
||||
/**
|
||||
* Gets sorted plugin definitions grouped by category.
|
||||
*
|
||||
* In addition to grouping, both categories and its entries are sorted,
|
||||
* whereas plugin definitions are sorted by label.
|
||||
*
|
||||
* @param array[]|null $definitions
|
||||
* (optional) The plugin definitions to group. If omitted, all plugin
|
||||
* definitions are used.
|
||||
*
|
||||
* @return array[]
|
||||
* Keys are category names, and values are arrays of which the keys are
|
||||
* plugin IDs and the values are plugin definitions.
|
||||
*/
|
||||
public function getGroupedDefinitions(array $definitions = NULL);
|
||||
|
||||
}
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
|
@ -19,7 +21,9 @@ use Drupal\Core\Plugin\DefaultPluginManager;
|
|||
* @see \Drupal\Core\Action\ActionBase
|
||||
* @see plugin_api
|
||||
*/
|
||||
class ActionManager extends DefaultPluginManager {
|
||||
class ActionManager extends DefaultPluginManager implements CategorizingPluginManagerInterface {
|
||||
|
||||
use CategorizingPluginManagerTrait;
|
||||
|
||||
/**
|
||||
* Constructs a new class instance.
|
||||
|
|
|
@ -59,4 +59,13 @@ class Action extends Plugin {
|
|||
*/
|
||||
public $type = '';
|
||||
|
||||
/**
|
||||
* The category under which the action should be listed in the UI.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $category;
|
||||
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ namespace Drupal\Core\Block;
|
|||
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
|
||||
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Manages discovery and instantiation of block plugins.
|
||||
|
@ -23,16 +23,12 @@ use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|||
*/
|
||||
class BlockManager extends DefaultPluginManager implements BlockManagerInterface, FallbackPluginManagerInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
use CategorizingPluginManagerTrait {
|
||||
getSortedDefinitions as traitGetSortedDefinitions;
|
||||
getGroupedDefinitions as traitGetGroupedDefinitions;
|
||||
}
|
||||
use ContextAwarePluginManagerTrait;
|
||||
|
||||
/**
|
||||
* An array of all available modules and their data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $moduleData;
|
||||
|
||||
/**
|
||||
* Constructs a new \Drupal\Core\Block\BlockManager object.
|
||||
*
|
||||
|
@ -56,64 +52,30 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
|
|||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
parent::processDefinition($definition, $plugin_id);
|
||||
|
||||
// Ensure that every block has a category.
|
||||
if (empty($definition['category'])) {
|
||||
$definition['category'] = $this->getModuleName($definition['provider']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the module.
|
||||
*
|
||||
* @param string $module
|
||||
* The machine name of a module.
|
||||
*
|
||||
* @return string
|
||||
* The human-readable module name if it exists, otherwise the
|
||||
* machine-readable module name.
|
||||
*/
|
||||
protected function getModuleName($module) {
|
||||
// Gather module data.
|
||||
if (!isset($this->moduleData)) {
|
||||
$this->moduleData = system_get_info('module');
|
||||
}
|
||||
// If the module exists, return its human-readable name.
|
||||
if (isset($this->moduleData[$module])) {
|
||||
return $this->t($this->moduleData[$module]['name']);
|
||||
}
|
||||
// Otherwise, return the machine name.
|
||||
return $module;
|
||||
$this->processDefinitionCategory($definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCategories() {
|
||||
$categories = array_unique(array_values(array_map(function ($definition) {
|
||||
return $definition['category'];
|
||||
}, $this->getDefinitions())));
|
||||
natcasesort($categories);
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSortedDefinitions() {
|
||||
public function getSortedDefinitions(array $definitions = NULL) {
|
||||
// Sort the plugins first by category, then by label.
|
||||
$definitions = $this->getDefinitionsForContexts();
|
||||
uasort($definitions, function ($a, $b) {
|
||||
if ($a['category'] != $b['category']) {
|
||||
return strnatcasecmp($a['category'], $b['category']);
|
||||
}
|
||||
return strnatcasecmp($a['admin_label'], $b['admin_label']);
|
||||
});
|
||||
$definitions = $this->traitGetSortedDefinitions($definitions, 'admin_label');
|
||||
// Do not display the 'broken' plugin in the UI.
|
||||
unset($definitions['broken']);
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroupedDefinitions(array $definitions = NULL) {
|
||||
$definitions = $this->traitGetGroupedDefinitions($definitions, 'admin_label');
|
||||
// Do not display the 'broken' plugin in the UI.
|
||||
unset($definitions[$this->t('Block')]['broken']);
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -7,27 +7,12 @@
|
|||
|
||||
namespace Drupal\Core\Block;
|
||||
|
||||
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
|
||||
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for the discovery and instantiation of block plugins.
|
||||
*/
|
||||
interface BlockManagerInterface extends ContextAwarePluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets the names of all block categories.
|
||||
*
|
||||
* @return array
|
||||
* An array of translated categories, sorted alphabetically.
|
||||
*/
|
||||
public function getCategories();
|
||||
|
||||
/**
|
||||
* Gets the sorted definitions.
|
||||
*
|
||||
* @return array
|
||||
* An array of plugin definitions, sorted by category and admin label.
|
||||
*/
|
||||
public function getSortedDefinitions();
|
||||
interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface {
|
||||
|
||||
}
|
||||
|
|
|
@ -59,4 +59,13 @@ class Condition extends Plugin {
|
|||
*/
|
||||
public $condition = array();
|
||||
|
||||
/**
|
||||
* The category under which the condition should listed in the UI.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $category;
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
|
||||
namespace Drupal\Core\Condition;
|
||||
|
||||
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Executable\ExecutableManagerInterface;
|
||||
use Drupal\Core\Executable\ExecutableInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
|
||||
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
|
@ -23,8 +25,9 @@ use Drupal\Core\Plugin\DefaultPluginManager;
|
|||
*
|
||||
* @ingroup plugin_api
|
||||
*/
|
||||
class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface {
|
||||
class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface {
|
||||
|
||||
use CategorizingPluginManagerTrait;
|
||||
use ContextAwarePluginManagerTrait;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\CategorizingPluginManagerTrait.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Provides a trait for the CategorizingPluginManagerInterface.
|
||||
*
|
||||
* The trait provides methods for categorizing plugin definitions based on a
|
||||
* 'category' key. The plugin manager should make sure there is a default
|
||||
* category. For that the trait's processDefinitionCategory() method can be
|
||||
* invoked from the processDefinition() method.
|
||||
*
|
||||
* @see \Drupal\Component\Plugin\CategorizingPluginManagerInterface
|
||||
*/
|
||||
trait CategorizingPluginManagerTrait {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Processes a plugin definition to ensure there is a category.
|
||||
*
|
||||
* If the definition lacks a category, it defaults to the providing module.
|
||||
*
|
||||
* @param array $definition
|
||||
* The plugin definition.
|
||||
*/
|
||||
protected function processDefinitionCategory(&$definition) {
|
||||
// Ensure that every plugin has a category.
|
||||
if (empty($definition['category'])) {
|
||||
// Default to the human readable module name if the provider is a module;
|
||||
// otherwise the provider machine name is used.
|
||||
$definition['category'] = $this->getProviderName($definition['provider']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of a provider.
|
||||
*
|
||||
* @param string $provider
|
||||
* The machine name of a plugin provider.
|
||||
*
|
||||
* @return string
|
||||
* The human-readable module name if it exists, otherwise the
|
||||
* machine-readable name passed.
|
||||
*/
|
||||
protected function getProviderName($provider) {
|
||||
$list = $this->getModuleHandler()->getModuleList();
|
||||
// If the module exists, return its human-readable name.
|
||||
if (isset($list[$provider])) {
|
||||
return $this->getModuleHandler()->getName($provider);
|
||||
}
|
||||
// Otherwise, return the machine name.
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the module handler used.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
* The module handler.
|
||||
*/
|
||||
public function getModuleHandler() {
|
||||
// If the class has an injected module handler, use it. Otherwise fall back
|
||||
// to fetch it from the service container.
|
||||
if (isset($this->moduleHandler)) {
|
||||
return $this->moduleHandler;
|
||||
}
|
||||
return \Drupal::moduleHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Component\Plugin\CategorizingPluginManagerInterface::getCategories().
|
||||
*/
|
||||
public function getCategories() {
|
||||
/** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
|
||||
// Fetch all categories from definitions and remove duplicates.
|
||||
$categories = array_unique(array_values(array_map(function ($definition) {
|
||||
return $definition['category'];
|
||||
}, $this->getDefinitions())));
|
||||
natcasesort($categories);
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Component\Plugin\CategorizingPluginManagerInterface::getSortedDefinitions().
|
||||
*/
|
||||
public function getSortedDefinitions(array $definitions = NULL, $label_key = 'label') {
|
||||
// Sort the plugins first by category, then by label.
|
||||
/** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
|
||||
$definitions = isset($definitions) ? $definitions : $this->getDefinitions();
|
||||
uasort($definitions, function ($a, $b) use ($label_key) {
|
||||
if ($a['category'] != $b['category']) {
|
||||
return strnatcasecmp($a['category'], $b['category']);
|
||||
}
|
||||
return strnatcasecmp($a[$label_key], $b[$label_key]);
|
||||
});
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Component\Plugin\CategorizingPluginManagerInterface::getGroupedDefinitions().
|
||||
*/
|
||||
public function getGroupedDefinitions(array $definitions = NULL, $label_key = 'label') {
|
||||
/** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
|
||||
$definitions = $this->getSortedDefinitions(isset($definitions) ? $definitions : $this->getDefinitions(), $label_key);
|
||||
$grouped_definitions = array();
|
||||
foreach ($definitions as $id => $definition) {
|
||||
$grouped_definitions[(string) $definition['category']][$id] = $definition;
|
||||
}
|
||||
return $grouped_definitions;
|
||||
}
|
||||
|
||||
}
|
|
@ -332,9 +332,10 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
|
|||
$form['place_blocks']['list']['#type'] = 'container';
|
||||
$form['place_blocks']['list']['#attributes']['class'][] = 'entity-meta';
|
||||
|
||||
// Sort the plugins first by category, then by label.
|
||||
$plugins = $this->blockManager->getSortedDefinitions();
|
||||
foreach ($plugins as $plugin_id => $plugin_definition) {
|
||||
// Only add blocks which work without any available context.
|
||||
$definitions = $this->blockManager->getDefinitionsForContexts();
|
||||
$sorted_definitions = $this->blockManager->getSortedDefinitions($definitions);
|
||||
foreach ($sorted_definitions as $plugin_id => $plugin_definition) {
|
||||
$category = String::checkPlain($plugin_definition['category']);
|
||||
$category_key = 'category-' . $category;
|
||||
if (!isset($form['place_blocks']['list'][$category_key])) {
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Core\Plugin\CategorizingPluginManagerTraitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Core\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\Plugin\CategorizingPluginManagerTrait
|
||||
* @group Plugin
|
||||
*/
|
||||
class CategorizingPluginManagerTraitTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The plugin manager to test.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\CategorizingPluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
|
||||
$module_handler->expects($this->any())
|
||||
->method('getModuleList')
|
||||
->willReturn(['node' => []]);
|
||||
$module_handler->expects($this->any())
|
||||
->method('getName')
|
||||
->with('node')
|
||||
->willReturn('Node');
|
||||
|
||||
$this->pluginManager = new CategorizingPluginManager($module_handler);
|
||||
$this->pluginManager->setStringTranslation($this->getStringTranslationStub());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getCategories
|
||||
*/
|
||||
public function testGetCategories() {
|
||||
$this->assertSame(array_values($this->pluginManager->getCategories()), [
|
||||
'fruits',
|
||||
'vegetables',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getSortedDefinitions
|
||||
*/
|
||||
public function testGetSortedDefinitions() {
|
||||
$sorted = $this->pluginManager->getSortedDefinitions();
|
||||
$this->assertSame(array_keys($sorted), ['apple', 'mango', 'cucumber']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getGroupedDefinitions
|
||||
*/
|
||||
public function testGetGroupedDefinitions() {
|
||||
$grouped = $this->pluginManager->getGroupedDefinitions();
|
||||
$this->assertSame(array_keys($grouped), ['fruits', 'vegetables']);
|
||||
$this->assertSame(array_keys($grouped['fruits']), ['apple', 'mango']);
|
||||
$this->assertSame(array_keys($grouped['vegetables']), ['cucumber']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::processDefinitionCategory
|
||||
*/
|
||||
public function testProcessDefinitionCategory() {
|
||||
// Existing category.
|
||||
$definition = [
|
||||
'label' => 'some',
|
||||
'provider' => 'core',
|
||||
'category' => 'bag',
|
||||
];
|
||||
$this->pluginManager->processDefinition($definition, 'some');
|
||||
$this->assertSame($definition['category'], 'bag');
|
||||
|
||||
// No category, provider without label.
|
||||
$definition = [
|
||||
'label' => 'some',
|
||||
'provider' => 'core',
|
||||
];
|
||||
$this->pluginManager->processDefinition($definition, 'some');
|
||||
$this->assertSame($definition['category'], 'core');
|
||||
|
||||
// No category, provider is module with label.
|
||||
$definition = [
|
||||
'label' => 'some',
|
||||
'provider' => 'node',
|
||||
];
|
||||
$this->pluginManager->processDefinition($definition, 'some');
|
||||
$this->assertSame($definition['category'], 'Node');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that allows testing the trait.
|
||||
*/
|
||||
class CategorizingPluginManager extends DefaultPluginManager implements CategorizingPluginManagerInterface {
|
||||
|
||||
use CategorizingPluginManagerTrait;
|
||||
|
||||
/**
|
||||
* Replace the constructor so we can instantiate a stub.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler) {
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Provides some test definitions to the trait.
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
return [
|
||||
'cucumber' => [
|
||||
'label' => 'cucumber',
|
||||
'category' => 'vegetables',
|
||||
],
|
||||
'apple' => [
|
||||
'label' => 'apple',
|
||||
'category' => 'fruits',
|
||||
],
|
||||
'mango' => [
|
||||
'label' => 'mango',
|
||||
'category' => 'fruits',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
parent::processDefinition($definition, $plugin_id);
|
||||
$this->processDefinitionCategory($definition);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue