Commit 73050fbb authored by webchick's avatar webchick

Issue #2071019 by tim.plunkett, olli: Allow the block category for Views block...

Issue #2071019 by tim.plunkett, olli: Allow the block category for Views block displays to be edited.
parent f503d228
......@@ -28,3 +28,10 @@ block_admin_add:
_title: 'Configure block'
requirements:
_permission: 'administer blocks'
block.category_autocomplete:
pattern: '/block-category/autocomplete'
defaults:
_controller: '\Drupal\block\Controller\CategoryAutocompleteController::autocomplete'
requirements:
_permission: 'administer blocks'
......@@ -9,6 +9,7 @@
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\Json;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\Entity\ConfigEntityListController;
use Drupal\Core\Entity\EntityControllerInterface;
use Drupal\Core\Entity\EntityInterface;
......@@ -340,7 +341,7 @@ public function buildForm(array $form, array &$form_state) {
return strnatcasecmp($a['admin_label'], $b['admin_label']);
});
foreach ($plugins as $plugin_id => $plugin_definition) {
$category = $plugin_definition['category'];
$category = String::checkPlain($plugin_definition['category']);
if (!isset($form['place_blocks']['list'][$category])) {
$form['place_blocks']['list'][$category] = array(
'#type' => 'details',
......
<?php
/**
* @file
* Contains \Drupal\block\Controller\CategoryAutocompleteController.
*/
namespace Drupal\block\Controller;
use Drupal\block\Plugin\Type\BlockManager;
use Drupal\Component\Utility\String;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Returns autocomplete responses for block categories.
*/
class CategoryAutocompleteController implements ContainerInjectionInterface {
/**
* The block manager.
*
* @var \Drupal\block\Plugin\Type\BlockManager
*/
protected $blockManager;
/**
* Constructs a new CategoryAutocompleteController.
*
* @param \Drupal\block\Plugin\Type\BlockManager $block_manager
* The block manager.
*/
public function __construct(BlockManager $block_manager) {
$this->blockManager = $block_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.block')
);
}
/**
* Retrieves suggestions for block category autocompletion.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON response containing autocomplete suggestions.
*/
public function autocomplete(Request $request) {
$typed_category = $request->query->get('q');
$matches = array();
foreach ($this->blockManager->getCategories() as $category) {
if (stripos($category, $typed_category) === 0) {
$category = String::checkPlain($category);
$matches[$category] = $category;
}
}
return new JsonResponse($matches);
}
}
......@@ -108,4 +108,18 @@ protected function t($string, array $args = array(), array $options = array()) {
return $this->translationManager->translate($string, $args, $options);
}
/**
* Gets the names of all block categories.
*
* @return array
* An array of translated categories, sorted alphabetically.
*/
public function getCategories() {
$categories = array_unique(array_values(array_map(function ($definition) {
return $definition['category'];
}, $this->getDefinitions())));
natcasesort($categories);
return $categories;
}
}
......@@ -8,10 +8,12 @@
namespace Drupal\block\Plugin\views\display;
use Drupal\Component\Utility\String;
use Drupal\views\Annotation\ViewsDisplay;
use Drupal\Core\Annotation\Translation;
use Drupal\views\Plugin\Block\ViewsBlock;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Views;
/**
* The plugin that handles a block.
......@@ -45,6 +47,7 @@ protected function defineOptions() {
$options = parent::defineOptions();
$options['block_description'] = array('default' => '', 'translatable' => TRUE);
$options['block_category'] = array('default' => 'Views', 'translatable' => TRUE);
$options['block_caching'] = array('default' => DRUPAL_NO_CACHE);
$options['allow'] = array(
......@@ -107,12 +110,18 @@ public function optionsSummary(&$categories, &$options) {
if (empty($block_description)) {
$block_description = t('None');
}
$block_category = String::checkPlain($this->getOption('block_category'));
$options['block_description'] = array(
'category' => 'block',
'title' => t('Block name'),
'value' => views_ui_truncate($block_description, 24),
);
$options['block_category'] = array(
'category' => 'block',
'title' => t('Block category'),
'value' => views_ui_truncate($block_category, 24),
);
$filtered_allow = array_filter($this->getOption('allow'));
......@@ -172,6 +181,15 @@ public function buildOptionsForm(&$form, &$form_state) {
'#default_value' => $this->getOption('block_description'),
);
break;
case 'block_category':
$form['#title'] .= t('Block category');
$form['block_category'] = array(
'#type' => 'textfield',
'#autocomplete_route_name' => 'block.category_autocomplete',
'#description' => t('The category this block will appear under on the <a href="@href">blocks placement page</a>.', array('@href' => url('admin/structure/block'))),
'#default_value' => $this->getOption('block_category'),
);
break;
case 'block_caching':
$form['#title'] .= t('Block caching type');
......@@ -216,6 +234,7 @@ public function submitOptionsForm(&$form, &$form_state) {
parent::submitOptionsForm($form, $form_state);
switch ($form_state['section']) {
case 'block_description':
case 'block_category':
case 'block_caching':
case 'allow':
$this->setOption($form_state['section'], $form_state['values'][$form_state['section']]);
......@@ -326,6 +345,17 @@ public function usesExposed() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function newDisplay() {
$base_tables = Views::viewsData()->fetchBaseTables();
$base_table = $this->view->storage->get('base_table');
if (isset($base_tables[$base_table]['title'])) {
$this->setOption('block_category', $base_tables[$base_table]['title']);
}
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::remove().
*/
......
......@@ -7,6 +7,7 @@
namespace Drupal\block\Tests\Views;
use Drupal\Component\Utility\String;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
......@@ -46,6 +47,79 @@ protected function setUp() {
$this->enableViewsTestModule();
}
/**
* Tests default and custom block categories.
*/
public function testBlockCategory() {
$this->drupalLogin($this->drupalCreateUser(array('administer views', 'administer blocks')));
// Create a new view in the UI.
$edit = array();
$edit['label'] = $this->randomString();
$edit['id'] = strtolower($this->randomName());
$edit['show[wizard_key]'] = 'standard:views_test_data';
$edit['description'] = $this->randomString();
$edit['block[create]'] = TRUE;
$edit['block[style][row_plugin]'] = 'fields';
$this->drupalPost('admin/structure/views/add', $edit, t('Save and edit'));
// Test that the block was given a default category corresponding to its
// base table.
$arguments = array(
':id' => 'edit-views-test-data',
':li_class' => 'views-block' . drupal_html_class($edit['id']) . '-block-1',
':href' => url('admin/structure/block/add/views_block:' . $edit['id'] . '-block_1/stark'),
':text' => 'View: ' . $edit['label'],
);
$this->drupalGet('admin/structure/block');
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the category for its base table.');
// Clone the block before changing the category.
$this->drupalPost('admin/structure/views/view/' . $edit['id'] . '/edit/block_1', array(), t('Clone @display_title', array('@display_title' => 'Block')));
$this->assertUrl('admin/structure/views/view/' . $edit['id'] . '/edit/block_2');
// Change the block category to a random string.
$this->drupalGet('admin/structure/views/view/' . $edit['id'] . '/edit/block_1');
$label = t('Views test data');
$link = $this->xpath('//a[@id="views-block-1-block-category" and normalize-space(text())=:label]', array(':label' => $label));
$this->assertTrue(!empty($link));
$this->clickLink($label);
$category = $this->randomString();
$this->drupalPost(NULL, array('block_category' => $category), t('Apply'));
// Clone the block after changing the category.
$this->drupalPost(NULL, array(), t('Clone @display_title', array('@display_title' => 'Block')));
$this->assertUrl('admin/structure/views/view/' . $edit['id'] . '/edit/block_3');
$this->drupalPost(NULL, array(), t('Save'));
// Test that the blocks are listed under the correct categories.
$category_id = 'edit-' . drupal_html_id(String::checkPlain($category));
$arguments[':id'] = $category_id;
$this->drupalGet('admin/structure/block');
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the custom category.');
$arguments = array(
':id' => 'edit-views-test-data',
':li_class' => 'views-block' . drupal_html_class($edit['id']) . '-block-2',
':href' => url('admin/structure/block/add/views_block:' . $edit['id'] . '-block_2/stark'),
':text' => 'View: ' . $edit['label'],
);
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->assertTrue(!empty($elements), 'The first cloned test block remains in the original category.');
$arguments = array(
':id' => $category_id,
':li_class' => 'views-block' . drupal_html_class($edit['id']) . '-block-3',
':href' => url('admin/structure/block/add/views_block:' . $edit['id'] . '-block_3/stark'),
':text' => 'View: ' . $edit['label'],
);
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->assertTrue(!empty($elements), 'The second cloned test block appears in the custom category.');
}
/**
* Tests removing a block display.
*/
......
<?php
/**
* @file
* Contains \Drupal\block\Tests\CategoryAutocompleteTest.
*/
namespace Drupal\block\Tests;
use Drupal\block\Controller\CategoryAutocompleteController;
use Drupal\Component\Utility\MapArray;
use Drupal\Component\Utility\String;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests the block category autocomplete.
*
* @group Drupal
*
* @see \Drupal\block\Controller\CategoryAutocompleteController
*/
class CategoryAutocompleteTest extends UnitTestCase {
/**
* The autocomplete controller.
*
* @var \Drupal\block\Controller\CategoryAutocompleteController
*/
protected $autocompleteController;
public static function getInfo() {
return array(
'name' => 'Block category autocomplete',
'description' => 'Tests the block category autocomplete.',
'group' => 'Block',
);
}
public function setUp() {
$block_manager = $this->getMockBuilder('Drupal\block\Plugin\Type\BlockManager')
->disableOriginalConstructor()
->getMock();
$block_manager->expects($this->any())
->method('getCategories')
->will($this->returnValue(array('Comment', 'Node', 'None & Such', 'User')));
$this->autocompleteController = new CategoryAutocompleteController($block_manager);
}
/**
* Tests the autocomplete method.
*
* @param string $string
* The string entered into the autocomplete.
* @param array $suggestions
* The array of expected suggestions.
*
* @see \Drupal\block\Controller\CategoryAutocompleteController::autocomplete()
*
* @dataProvider providerTestAutocompleteSuggestions
*/
public function testAutocompleteSuggestions($string, $suggestions) {
$suggestions = array_map(function ($suggestion) {
return String::checkPlain($suggestion);
}, $suggestions);
$result = $this->autocompleteController->autocomplete(new Request(array('q' => $string)));
$this->assertSame(MapArray::copyValuesToKeys($suggestions), json_decode($result->getContent(), TRUE));
}
/**
* Data provider for testAutocompleteSuggestions().
*
* @return array
*/
public function providerTestAutocompleteSuggestions() {
$test_parameters = array();
$test_parameters[] = array(
'string' => 'Com',
'suggestions' => array(
'Comment',
),
);
$test_parameters[] = array(
'string' => 'No',
'suggestions' => array(
'Node',
'None & Such',
),
);
$test_parameters[] = array(
'string' => 'us',
'suggestions' => array(
'User',
),
);
$test_parameters[] = array(
'string' => 'Banana',
'suggestions' => array(),
);
return $test_parameters;
}
}
......@@ -60,6 +60,9 @@ views.display.block:
block_description:
type: text
label: 'Block name'
block_category:
type: text
label: 'Block category'
block_caching:
type: boolean
label: 'Block caching'
......
......@@ -100,6 +100,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
}
}
$this->derivatives[$delta] = array(
'category' => $display->getOption('block_category'),
'admin_label' => $desc,
'cache' => $display->getCacheType()
);
......
......@@ -105,8 +105,8 @@ public static function create(ContainerInterface $container, array $configuratio
* The options configured for this plugin.
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
$this->setOptionDefaults($this->options, $this->defineOptions());
$this->view = $view;
$this->setOptionDefaults($this->options, $this->defineOptions());
$this->displayHandler = $display;
$this->unpackOptions($this->options, $options);
......
......@@ -115,8 +115,8 @@ public function __construct(array $configuration, $plugin_id, array $plugin_defi
}
public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
$this->setOptionDefaults($this->options, $this->defineOptions());
$this->view = $view;
$this->setOptionDefaults($this->options, $this->defineOptions());
$this->display = &$display;
// Load extenders as soon as possible.
......@@ -2677,6 +2677,14 @@ public function validate() {
return $errors;
}
/**
* Reacts on adding a display.
*
* @see \Drupal\views\Entity\View::newDisplay()
*/
public function newDisplay() {
}
/**
* Reacts on deleting a display.
*/
......
......@@ -722,7 +722,9 @@ public function newDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
$id = $this->storage->addDisplay($plugin_id, $title, $id);
$this->displayHandlers->addInstanceID($id);
return $this->displayHandlers->get($id);
$display = $this->displayHandlers->get($id);
$display->newDisplay();
return $display;
}
/**
......
......@@ -775,7 +775,8 @@ public function submitDisplayDuplicate($form, &$form_state) {
// Create the new display.
$displays = $view->get('display');
$new_display_id = $view->addDisplay($displays[$display_id]['display_plugin']);
$display = $view->getExecutable()->newDisplay($displays[$display_id]['display_plugin']);
$new_display_id = $display->display['id'];
$displays[$new_display_id] = $displays[$display_id];
$displays[$new_display_id]['id'] = $new_display_id;
$view->set('display', $displays);
......@@ -797,7 +798,8 @@ public function submitDisplayAdd($form, &$form_state) {
// Create the new display.
$parents = $form_state['triggering_element']['#parents'];
$display_type = array_pop($parents);
$display_id = $view->addDisplay($display_type);
$display = $view->getExecutable()->newDisplay($display_type);
$display_id = $display->display['id'];
// A new display got added so the asterisks symbol should appear on the new
// display.
$view->getExecutable()->current_display = $display_id;
......@@ -817,7 +819,8 @@ public function submitCloneDisplayAsType($form, &$form_state) {
// Create the new display.
$parents = $form_state['triggering_element']['#parents'];
$display_type = array_pop($parents);
$new_display_id = $view->addDisplay($display_type);
$display = $view->getExecutable()->newDisplay($display_type);
$new_display_id = $display->display['id'];
$displays = $view->get('display');
// Let the display title be generated by the addDisplay method and set the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment