diff --git a/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php b/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php new file mode 100644 index 0000000..bb9df77 --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php @@ -0,0 +1,50 @@ +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; + public function getSortedDefinitions(array $definitions = NULL) { + // Sort the plugins first by category, then by label. + $definitions = $this->traitGetSortedDefinitions($definitions, 'admin_label'); + // Do not display the 'broken' plugin in the UI. + unset($definitions['broken']); + return $definitions; } /** * {@inheritdoc} */ - public function getSortedDefinitions() { - // 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']); - }); + public function getGroupedDefinitions(array $definitions = NULL) { + $definitions = $this->traitGetGroupedDefinitions($definitions, 'admin_label'); // Do not display the 'broken' plugin in the UI. - unset($definitions['broken']); + unset($definitions[$this->t('Block')]['broken']); return $definitions; } diff --git a/core/lib/Drupal/Core/Block/BlockManagerInterface.php b/core/lib/Drupal/Core/Block/BlockManagerInterface.php index 2fa8611..592a9a3 100644 --- a/core/lib/Drupal/Core/Block/BlockManagerInterface.php +++ b/core/lib/Drupal/Core/Block/BlockManagerInterface.php @@ -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 { } diff --git a/core/lib/Drupal/Core/Condition/Annotation/Condition.php b/core/lib/Drupal/Core/Condition/Annotation/Condition.php index 55834d0..d796865 100644 --- a/core/lib/Drupal/Core/Condition/Annotation/Condition.php +++ b/core/lib/Drupal/Core/Condition/Annotation/Condition.php @@ -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; + } diff --git a/core/lib/Drupal/Core/Condition/ConditionManager.php b/core/lib/Drupal/Core/Condition/ConditionManager.php index c1e309a..809f28f 100644 --- a/core/lib/Drupal/Core/Condition/ConditionManager.php +++ b/core/lib/Drupal/Core/Condition/ConditionManager.php @@ -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 @@ * * @ingroup plugin_api */ -class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface { +class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface { + use CategorizingPluginManagerTrait; use ContextAwarePluginManagerTrait; /** diff --git a/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php new file mode 100644 index 0000000..6cb8c5a --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php @@ -0,0 +1,114 @@ +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; + } + + /** + * Implements \Drupal\Component\Plugin\CategorizingPluginManagerInterface::getCategories(). + */ + public function getCategories() { + /** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */ + $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; + } + +} diff --git a/core/modules/block/src/BlockListBuilder.php b/core/modules/block/src/BlockListBuilder.php index 1d11f99..07cbcc6 100644 --- a/core/modules/block/src/BlockListBuilder.php +++ b/core/modules/block/src/BlockListBuilder.php @@ -334,9 +334,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { $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])) { diff --git a/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php b/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php new file mode 100644 index 0000000..7c151c0 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php @@ -0,0 +1,151 @@ +pluginManager = new CategorizingPluginManagerImpl(); + $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 CategorizingPluginManagerImpl extends DefaultPluginManager implements CategorizingPluginManagerInterface { + + use CategorizingPluginManagerTrait; + + /** + * Replace the constructor so we can instantiate a stub. + */ + public function __construct() { + } + + /** + * {@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); + } + } +} + +namespace { + + /** + * "Mocks" system_get_info() as needed for the test. + */ + if (!function_exists('system_get_info')) { + + function system_get_info($type, $name = NULL) { + return ['node' => ['name' => 'Node']]; + } + } +}