Commit b435b703 authored by webchick's avatar webchick

Issue #2056513 by tim.plunkett: Remove PluginUI subsystem, provide block plugins UI as one-off.

parent c5f524fa
......@@ -61,14 +61,14 @@ function block_help($path, $arg) {
$output .= '<dd>' . t('Blocks can be configured to be visible only on certain pages, only to users of certain roles, or only on pages displaying certain <a href="@content-type">content types</a>. Some dynamic blocks, such as those generated by modules, will be displayed only on certain pages.', array('@content-type' => url('admin/structure/types'), '@user' => url('user'))) . '</dd>';
if (module_exists('custom_block')) {
$output .= '<dt>' . t('Creating custom blocks') . '</dt>';
$output .= '<dd>' . t('Users with the <em>Administer blocks</em> permission can <a href="@block-add">add custom blocks</a>, which are then listed on the <a href="@blocks">Blocks administration page</a>. Once created, custom blocks behave just like default and module-generated blocks.', array('@blocks' => url('admin/structure/block'), '@block-add' => url('admin/structure/block/list/block_plugin_ui:' . config('system.theme')->get('default') . '/add/custom_blocks'))) . '</dd>';
$output .= '<dd>' . t('Users with the <em>Administer blocks</em> permission can <a href="@block-add">add custom blocks</a>, which are then listed on the <a href="@blocks">Blocks administration page</a>. Once created, custom blocks behave just like default and module-generated blocks.', array('@blocks' => url('admin/structure/block'), '@block-add' => url('admin/structure/block/list/' . config('system.theme')->get('default') . '/add/custom_blocks'))) . '</dd>';
}
$output .= '</dl>';
return $output;
}
if ($arg[0] == 'admin' && $arg[1] == 'structure' && $arg['2'] == 'block' && (empty($arg[3]) || $arg[3] == 'list') && empty($arg[5])) {
if (!empty($arg[4])) {
list(, $demo_theme) = explode(':', $arg[4]);
$demo_theme = $arg[4];
}
else {
$demo_theme = config('system.theme')->get('default');
......@@ -136,28 +136,28 @@ function block_menu() {
// and plugin IDs to decouple the routes from these dependencies and allow
// hook_menu_local_tasks() to check for the untranslated tab_parent path.
// @see http://drupal.org/node/1067408
$themes = list_themes();
foreach (drupal_container()->get('plugin.manager.system.plugin_ui')->getDefinitions() as $plugin_id => $plugin) {
list($plugin_base, $key) = explode(':', $plugin_id);
if ($plugin_base == 'block_plugin_ui') {
$theme = $themes[$key];
$items['admin/structure/block/list/' . $plugin_id] = array(
'title' => check_plain($theme->info['name']),
'type' => $key == $default_theme ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
'route_name' => 'block_admin_display.' . $plugin_id
);
$items['admin/structure/block/demo/' . $key] = array(
'title' => check_plain($theme->info['name']),
'page callback' => 'block_admin_demo',
'page arguments' => array($key),
'type' => MENU_CALLBACK,
'access callback' => '_block_themes_access',
'access arguments' => array($key),
'theme callback' => '_block_custom_theme',
'theme arguments' => array($key),
'file' => 'block.admin.inc',
);
}
foreach (list_themes() as $key => $theme) {
$items["admin/structure/block/list/$key"] = array(
'title' => check_plain($theme->info['name']),
'type' => $key == $default_theme ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
'route_name' => "block_admin_display.$key",
);
$items["admin/structure/block/list/$key/add"] = array(
'title' => 'Place blocks',
'type' => MENU_LOCAL_ACTION,
'route_name' => "block_plugin_ui.$key",
);
$items["admin/structure/block/demo/$key"] = array(
'title' => check_plain($theme->info['name']),
'page callback' => 'block_admin_demo',
'page arguments' => array($key),
'type' => MENU_CALLBACK,
'access callback' => '_block_themes_access',
'access arguments' => array($key),
'theme callback' => '_block_custom_theme',
'theme arguments' => array($key),
'file' => 'block.admin.inc',
);
}
return $items;
}
......
......@@ -26,3 +26,10 @@ block_admin_add:
_content: '\Drupal\block\Controller\BlockAddController::blockAddConfigureForm'
requirements:
_permission: 'administer blocks'
block_autocomplete:
pattern: '/block/autocomplete'
defaults:
_controller: '\Drupal\block\Controller\BlockAutocompleteController::autocomplete'
requirements:
_permission: 'administer blocks'
......@@ -13,7 +13,6 @@ services:
class: Drupal\block\Routing\RouteSubscriber
tags:
- { name: event_subscriber}
arguments: ['@plugin.manager.system.plugin_ui']
block.theme_access_check:
class: Drupal\block\Access\BlockThemeAccessCheck
tags:
......
......@@ -283,7 +283,7 @@ public function submit(array $form, array &$form_state) {
drupal_set_message(t('The block configuration has been saved.'));
cache_invalidate_tags(array('content' => TRUE));
$form_state['redirect'] = 'admin/structure/block/list/block_plugin_ui:' . $entity->get('theme');
$form_state['redirect'] = 'admin/structure/block/list/' . $entity->get('theme');
}
/**
......
......@@ -2,51 +2,73 @@
/**
* @file
* Contains \Drupal\system\Controller\SystemController.
* Contains \Drupal\block\Controller\BlockAutocompleteController.
*/
namespace Drupal\system\Controller;
namespace Drupal\block\Controller;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\Tags;
use Drupal\Component\Utility\Unicode;
use Symfony\Component\DependencyInjection\ContainerAware;
use Drupal\Core\Controller\ControllerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Returns responses for System routes.
* Provides an autocomplete for blocks.
*/
class SystemController extends ContainerAware {
class BlockAutocompleteController implements ControllerInterface {
/**
* Autocompletes any plugin system tied to a plugin UI plugin.
* The block plugin manager.
*
* The passed plugin_id indicates the specific plugin_ui plugin that is in use
* here. The documentation within the annotation of that plugin will contain a
* manager for the plugins that need to be autocompleted allowing this
* function to autocomplete plugins for any plugin type.
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $manager;
/**
* Constructs a new BlockAutocompleteController object.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface $manager
* The block plugin manager.
*/
public function __construct(PluginManagerInterface $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.block')
);
}
/**
* Autocompletes a block plugin ID.
*
* @param string $plugin_id
* The plugin id for the calling plugin.
* @param Request $request
* The request object that contains the typed tags.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The matched plugins as json.
* The matched plugins as JSON.
*/
public function autocomplete($plugin_id, Request $request) {
public function autocomplete(Request $request) {
$string_typed = $request->query->get('q');
// The passed string may be comma or space separated.
$string_typed = Tags::explode($string_typed);
// Take the last result and lowercase it.
$string = Unicode::strtolower(array_pop($string_typed));
$matches = array();
if ($string) {
$plugin_ui = $this->container->get('plugin.manager.system.plugin_ui')->getDefinition($plugin_id);
$manager = $this->container->get($plugin_ui['manager']);
$titles = array();
foreach($manager->getDefinitions() as $plugin_id => $plugin) {
$titles[$plugin_id] = $plugin[$plugin_ui['title_attribute']];
// Gather all block plugins and their admin titles.
foreach($this->manager->getDefinitions() as $plugin_id => $plugin) {
$titles[$plugin_id] = $plugin['admin_label'];
}
// Find any matching block plugin IDs.
$matches = preg_grep("/\b". $string . "/i", $titles);
}
......
......@@ -18,17 +18,15 @@ class BlockListController extends EntityListController {
/**
* Shows the block administration page.
*
* @param string $entity_type
* Entity type of list page.
* @param string|null $theme
* Theme key of block list.
*
* @return array
* A render array as expected by drupal_render().
*/
public function listing($entity_type, $theme = NULL) {
$default_theme = $theme ?: $this->config('system.theme')->get('default');
return $this->entityManager()->getListController($entity_type)->render($default_theme);
public function listing($theme = NULL) {
$theme = $theme ?: $this->config('system.theme')->get('default');
return $this->entityManager()->getListController('block')->render($theme);
}
}
<?php
/**
* @file
* Contains \Drupal\block\Form\PlaceBlocksForm.
*/
namespace Drupal\block\Form;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\String;
use Drupal\Core\Controller\ControllerInterface;
use Drupal\Core\Form\FormInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the 'Place blocks' form.
*/
class PlaceBlocksForm implements FormInterface, ControllerInterface {
/**
* The block plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $manager;
/**
* The theme this block will be placed into.
*
* @var string
*/
protected $theme;
/**
* Constructs a new PlaceBlocksForm object.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface $manager
* The block plugin manager.
*/
public function __construct(PluginManagerInterface $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.block')
);
}
/**
* {@inheritdoc}
*/
public function getFormID() {
return 'block_plugin_ui';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state, $theme = NULL, $category = NULL) {
$this->theme = $theme;
$form['#theme'] = 'system_plugin_ui_form';
$rows = array();
$categories = array();
foreach ($this->manager->getDefinitions() as $plugin_id => $plugin_definition) {
if (empty($category) || $plugin_definition['category'] == $category) {
$rows[$plugin_id] = $this->row($plugin_id, $plugin_definition);
}
$categories[$plugin_definition['category']] = array(
'title' => $plugin_definition['category'],
'href' => 'admin/structure/block/list/' . $this->theme . '/add/' . $plugin_definition['category'],
);
}
$form['right']['block'] = array(
'#type' => 'textfield',
'#title' => t('Search'),
'#autocomplete_path' => 'block/autocomplete',
);
$form['right']['submit'] = array(
'#type' => 'submit',
'#button_type' => 'primary',
'#value' => t('Next'),
);
$form['right']['all_plugins'] = array(
'#type' => 'link',
'#title' => t('All blocks'),
'#href' => 'admin/structure/block/list/' . $this->theme . '/add',
);
if (!empty($categories)) {
$form['right']['categories'] = array(
'#theme' => 'links',
'#heading' => array(
'text' => t('Categories'),
'level' => 'h3',
),
'#links' => $categories,
);
}
// Sort rows alphabetically.
asort($rows);
$form['left']['plugin_library'] = array(
'#theme' => 'table',
'#header' => array(t('Subject'), t('Operations')),
'#rows' => $rows,
);
return $form;
}
/**
* Generates the row data for a single block plugin.
*
* @param string $plugin_id
* The plugin ID.
* @param array $plugin_definition
* The plugin definition.
*
* @return array
* The row data for a single block plugin.
*/
protected function row($plugin_id, array $plugin_definition) {
$row = array();
$row[] = String::checkPlain($plugin_definition['admin_label']);
$row[] = array('data' => array(
'#type' => 'operations',
'#links' => array(
'configure' => array(
'title' => t('Place block'),
'href' => 'admin/structure/block/add/' . $plugin_id . '/' . $this->theme,
),
),
));
return $row;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, array &$form_state) {
if (!$this->manager->getDefinition($form_state['values']['block'])) {
form_set_error('block', t('You must select a valid block.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
$form_state['redirect'] = 'admin/structure/block/add/' . $form_state['values']['block'] . '/' . $this->theme;
}
}
<?php
/**
* Contains \Drupal\block\Plugin\Derivative\BlockPluginUI.
*/
namespace Drupal\block\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DerivativeBase;
/**
* Provides block plugin UI plugin definitions for all themes.
*
* @todo Add documentation to this class.
*
* @see \Drupal\block\Plugin\system\plugin_ui\BlockPluginUI
*/
class BlockPluginUI extends DerivativeBase {
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions(array $base_plugin_definition) {
// Provide a derivative of the plugin UI for each theme.
foreach (list_themes() as $key => $theme) {
$this->derivatives[$key] = $base_plugin_definition;
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}
<?php
/**
* Contains \Drupal\block\Plugin\PluginUI\BlockPluginUI.
*/
namespace Drupal\block\Plugin\PluginUI;
use Drupal\system\Plugin\PluginUIBase;
use Drupal\Component\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
/**
* Defines an overrideable UI for block selection, configuration, and placement.
*
* @Plugin(
* id = "block_plugin_ui",
* module = "block",
* all_plugins = @Translation("All Blocks"),
* config_path = "admin/structure/block/add",
* default_task = TRUE,
* derivative = "Drupal\block\Plugin\Derivative\BlockPluginUI",
* facets = {
* "module" = @Translation("Modules")
* },
* link_title = @Translation("Place block"),
* manager = "plugin.manager.block",
* menu = TRUE,
* path = "admin/structure/block/list",
* suffix = "add",
* task_suffix = "library",
* task_title = @Translation("Library"),
* title = @Translation("Place blocks"),
* title_attribute = "admin_label",
* type = MENU_LOCAL_ACTION
* )
*/
class BlockPluginUI extends PluginUIBase {
/**
* Overrides \Drupal\system\Plugin\PluginUIBase::form().
*
* @todo Add inline documentation to this method.
*/
public function form($form, &$form_state, $facet = NULL) {
// @todo Add an inline comment here.
list($plugin, $theme) = explode(':', $this->getPluginId());
$plugin_definition = $this->getPluginDefinition();
// @todo Find out how to let the manager be injected into the class.
$manager = drupal_container()->get($plugin_definition['manager']);
$plugins = $manager->getDefinitions();
$form['#theme'] = 'system_plugin_ui_form';
$form['theme'] = array(
'#type' => 'value',
'#value' => $theme,
);
$form['manager'] = array(
'#type' => 'value',
'#value' => $manager,
);
$form['instance'] = array(
'#type' => 'value',
'#value' => $this,
);
$form['right']['block'] = array(
'#type' => 'textfield',
'#title' => t('Search'),
'#autocomplete_path' => 'system/autocomplete/' . $this->getPluginId(),
);
$form['right']['submit'] = array(
'#type' => 'submit',
'#value' => t('Next'),
);
$rows = array();
foreach ($plugins as $plugin_id => $display_plugin_definition) {
if (empty($facet) || $this->facetCompare($facet, $display_plugin_definition)) {
$rows[$plugin_id] = $this->row($plugin_id, $display_plugin_definition);
}
foreach ($plugin_definition['facets'] as $key => $title) {
$facets[$key][$display_plugin_definition[$key]] = $this->facetLink($key, $plugin_id, $display_plugin_definition);
}
$form['right']['all_plugins'] = array(
'#type' => 'link',
'#title' => $plugin_definition['all_plugins'],
'#href' => $this->allPluginsUrl($plugin_id, $display_plugin_definition),
);
foreach ($facets as $group => $values) {
$form['right'][$group] = array(
'#theme' => 'links',
'#heading' => array(
'text' => $plugin_definition['facets'][$group],
'level' => 'h3',
),
'#links' => $values,
);
}
}
// Sort rows alphabetically.
asort($rows);
$form['left']['plugin_library'] = array(
'#theme' => 'table',
'#header' => $this->tableHeader(),
'#rows' => $rows,
);
return $form;
}
/**
* Overrides \Drupal\system\Plugin\PluginUIBase::formValidate().
*/
public function formValidate($form, &$form_state) {
$definitions = $form_state['values']['manager']->getDefinitions();
if (!isset($definitions[$form_state['values']['block']])) {
form_set_error('block', t('You must select a valid block.'));
}
}
/**
* Overrides \Drupal\system\Plugin\PluginUIBase::formSubmit().
*/
public function formSubmit($form, &$form_state) {
$form_state['redirect'] = 'admin/structure/block/add/' . $form_state['values']['block'] . '/' . $form_state['values']['theme'];
}
/**
* Overrides \Drupal\system\Plugin\PluginUIBase::access().
*/
public function access() {
list($plugin, $theme) = explode(':', $this->getPluginId());
return _block_themes_access($theme);
}
/**
* Overrides \Drupal\system\Plugin\PluginUIBase::tableHeader().
*/
public function tableHeader() {
return array(t('Subject'), t('Operations'));
}
/**
* Overrides \Drupal\system\Plugin\PluginUIBase::row().
*/
public function row($display_plugin_id, array $display_plugin_definition) {
$plugin_definition = $this->getPluginDefinition();
list($plugin, $theme) = explode(':', $this->getPluginId());
$row = array();
$row[] = check_plain($display_plugin_definition['admin_label']);
$row[] = array('data' => array(
'#type' => 'operations',
'#links' => array(
'configure' => array(
'title' => $plugin_definition['link_title'],
'href' => $plugin_definition['config_path'] . '/' . $display_plugin_id . '/' . $theme,
),
),
));
return $row;
}
/**
* Creates a facet link for a given facet of a display plugin.
*
* Provides individually formatted links for the faceting that happens within
* the user interface. Since this is a faceting style procedure, each plugin
* may be parsed multiple times in order to extract all facets and their
* appropriate labels.
*
* The $display_plugin_id and $display_plugin_definition are provided for
* convenience when overriding this method.
*
* @param string $facet
* A simple string indicating what element of the $display_plugin_definition
* to utilize for faceting.
* @param string $display_plugin_id
* The plugin ID of the plugin we are currently parsing a facet link from.
* @param array $display_plugin_definition
* The plugin definition we are parsing.
*
* @return array
* Returns a row array comaptible with theme_links().
*/
protected function facetLink($facet, $display_plugin_id, array $display_plugin_definition) {
$plugin_definition = $this->getPluginDefinition();
return array(
'title' => $display_plugin_definition[$facet],
'href' => $plugin_definition['path'] . '/' . $this->getPluginId() . '/' . $facet . ':' . $display_plugin_definition[$facet],
);
}
/**
* Determines whether a given facet should be displayed for a plugin.
*
* Compares a given plugin definition with the selected facet to determine if
* the plugin should be displayed in the user interface.
*
* @param string $facet
* A colon separated string representing the key/value paring of a selected
* facet.
* @param array $display_plugin_definition
* The plugin definition to be compared.
*
* @return bool
* Returns TRUE if the selected facet matches this plugin.
*/
protected function facetCompare($facet, $display_plugin_definition) {
list($facet_type, $option) = explode(':', $facet);
return $option == $display_plugin_definition[$facet_type];
}
/**
* Provides an "all" style link to reset the facets.
*
* The $display_plugin_id and $display_plugin_definition are provided for
* convenience when overriding this method.
*
* @param string $display_plugin_id
* The plugin ID of the plugin we are currently parsing a facet link from.
* @param array $display_plugin_definition
* The plugin definition we are parsing.
*
* @return string
* Returns a simple URL string for use within l().
*/
protected function allPluginsUrl($display_plugin_id, $display_plugin_definition) {
$plugin_definition = $this->getPluginDefinition();
return $plugin_definition['path'] . '/' . $this->getPluginId() . '/add';
}
}
<?php
/**
* @file
* Contains \Drupal\block\Plugin\Type\BlockManager.
*/
......@@ -10,6 +11,7 @@
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Manages discovery and instantiation of block plugins.
*
......@@ -37,4 +39,17 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
$this->alterInfo($module_handler, 'block');
$this->setCacheBackend($cache_backend, $language_manager, 'block_plugins');
}
/**
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
// Ensure that every block has a category.
$definition += array(
'category' => $definition['provider'],
);
}
}
<?php
/**
* @file
* Contains \Drupal\block\Routing\RouteSubscriber.
......@@ -11,31 +12,12 @@
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
/**
* Listens to the dynamic route events.
*/
class RouteSubscriber implements EventSubscriberInterface {
/**
* The injection plugin manager that should be passed into the route.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface