Commit 73312ef0 authored by alexpott's avatar alexpott

Issue #2287073 by Berdir, slashrsm, Denchev, jibran, Thew, dasjo, andypost,...

Issue #2287073 by Berdir, slashrsm, Denchev, jibran, Thew, dasjo, andypost, Arla, zaporylie, marvin_B8, piyuesh23, EclipseGc: Allow views contextual filters to expose the context using argument validation plugins
parent 24a4d4b6
......@@ -5,6 +5,7 @@
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Element\View;
use Drupal\Core\Entity\EntityInterface;
/**
* Provides a generic Views block.
......@@ -23,10 +24,31 @@ class ViewsBlock extends ViewsBlockBase {
public function build() {
$this->view->display_handler->preBlockBuild($this);
$args = [];
foreach ($this->view->display_handler->getHandlers('argument') as $argument_name => $argument) {
// Initialize the argument value. Work around a limitation in
// \Drupal\views\ViewExecutable::_buildArguments() that skips processing
// later arguments if an argument with default action "ignore" and no
// argument is provided.
$args[$argument_name] = $argument->options['default_action'] == 'ignore' ? 'all' : NULL;
if (!empty($this->context[$argument_name])) {
if ($value = $this->context[$argument_name]->getContextValue()) {
// Context values are often entities, but views arguments expect to
// receive just the entity ID, convert it.
if ($value instanceof EntityInterface) {
$value = $value->id();
}
$args[$argument_name] = $value;
}
}
}
// We ask ViewExecutable::buildRenderable() to avoid creating a render cache
// entry for the view output by passing FALSE, because we're going to cache
// the whole block instead.
if ($output = $this->view->buildRenderable($this->displayID, [], FALSE)) {
if ($output = $this->view->buildRenderable($this->displayID, array_values($args), FALSE)) {
// Before returning the block output, convert it to a renderable array
// with contextual links.
$this->addContextualLinks($output);
......
......@@ -82,6 +82,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
$executable = $view->getExecutable();
$executable->initDisplay();
foreach ($executable->displayHandlers as $display) {
/** @var \Drupal\views\Plugin\views\display\DisplayPluginInterface $display */
// Add a block plugin definition for each block display.
if (isset($display) && !empty($display->definition['uses_hook_block'])) {
$delta = $view->id() . '-' . $display->display['id'];
......@@ -106,9 +107,18 @@ public function getDerivativeDefinitions($base_plugin_definition) {
'config_dependencies' => array(
'config' => array(
$view->getConfigDependencyName(),
)
)
),
),
);
// Look for arguments and expose them as context.
foreach ($display->getHandlers('argument') as $argument_name => $argument) {
/** @var \Drupal\views\Plugin\views\argument\ArgumentPluginBase $argument */
if ($context_definition = $argument->getContextDefinition()) {
$this->derivatives[$delta]['context'][$argument_name] = $context_definition;
}
}
$this->derivatives[$delta] += $base_plugin_definition;
}
}
......
......@@ -1326,6 +1326,17 @@ public function calculateDependencies() {
return $dependencies;
}
/**
* Returns a context definition for this argument.
*
* @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface|null
* A context definition that represents the argument or NULL if that is
* not possible.
*/
public function getContextDefinition() {
return $this->getPlugin('argument_validator')->getContextDefinition();
}
}
/**
......
......@@ -3,6 +3,7 @@
namespace Drupal\views\Plugin\views\argument;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\ContextDefinition;
/**
* Basic argument handler for arguments that are numeric. Incorporates
......@@ -124,4 +125,17 @@ public function getSortName() {
return $this->t('Numerical', array(), array('context' => 'Sort order'));
}
/**
* {@inheritdoc}
*/
public function getContextDefinition() {
if ($context_definition = parent::getContextDefinition()) {
return $context_definition;
}
// If the parent does not provide a context definition through the
// validation plugin, fall back to the integer type.
return new ContextDefinition('integer', $this->adminLabel(), FALSE);
}
}
......@@ -5,6 +5,7 @@
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\Database;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ManyToOneHelper;
......@@ -319,4 +320,17 @@ public function summaryName($data) {
return $this->caseTransform(parent::summaryName($data), $this->options['case']);
}
/**
* {@inheritdoc}
*/
public function getContextDefinition() {
if ($context_definition = parent::getContextDefinition()) {
return $context_definition;
}
// If the parent does not provide a context definition through the
// validation plugin, fall back to the string type.
return new ContextDefinition('string', $this->adminLabel(), FALSE);
}
}
......@@ -104,6 +104,15 @@ public function validateArgument($arg) { return TRUE; }
*/
public function processSummaryArguments(&$args) { }
/**
* Returns a context definition for this argument.
*
* @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface|null
* A context definition that represents the argument or NULL if that is
* not possible.
*/
public function getContextDefinition() { }
}
/**
......
......@@ -5,6 +5,7 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -228,4 +229,11 @@ public function calculateDependencies() {
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function getContextDefinition() {
return new ContextDefinition('entity:' . $this->definition['entity_type'], $this->argument->adminLabel(), FALSE);
}
}
......@@ -2,6 +2,8 @@
namespace Drupal\views\Plugin\views\argument_validator;
use Drupal\Core\Plugin\Context\ContextDefinition;
/**
* Validate whether an argument is numeric or not.
*
......@@ -18,4 +20,11 @@ public function validateArgument($argument) {
return is_numeric($argument);
}
/**
* {@inheritdoc}
*/
public function getContextDefinition() {
return new ContextDefinition('integer', $this->argument->adminLabel(), FALSE);
}
}
<?php
namespace Drupal\views\Tests\Plugin;
use Drupal\Core\Plugin\Context\ContextDefinitionInterface;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Tests\ViewTestBase;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
/**
* A test for contextual filters exposed as block context.
*
* @group views
*/
class ContextualFiltersBlockContextTest extends ViewTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'block_test_views', 'views_ui', 'node'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view_block_with_context'];
/**
* Test node type.
*
* @var \Drupal\node\NodeTypeInterface
*/
protected $nodeType;
/**
* Test nodes.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), ['block_test_views']);
$this->enableViewsTestModule();
$this->nodeType = $this->container->get('entity_type.manager')
->getStorage('node_type')
->create([
'name' => 'Test node type',
'type' => 'test',
]);
$this->nodeType->save();
$this->nodes[0] = $this->container->get('entity_type.manager')
->getStorage('node')
->create(['type' => $this->nodeType->id(), 'title' => 'First test node']);
$this->nodes[0]->save();
$this->nodes[1] = $this->container->get('entity_type.manager')
->getStorage('node')
->create(['type' => $this->nodeType->id(), 'title' => 'Second test node']);
$this->nodes[1]->save();
}
/**
* Tests exposed context.
*/
public function testBlockContext() {
$this->drupalLogin($this->drupalCreateUser(['administer views', 'administer blocks']));
// Check if context was correctly propagated to the block.
$definition = $this->container->get('plugin.manager.block')
->getDefinition('views_block:test_view_block_with_context-block_1');
$this->assertTrue($definition['context']['nid'] instanceof ContextDefinitionInterface);
/** @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context */
$context = $definition['context']['nid'];
$this->assertEqual($context->getDataType(), 'entity:node', 'Context definition data type is correct.');
$this->assertEqual($context->getLabel(), 'Content: ID', 'Context definition label is correct.');
$this->assertFalse($context->isRequired(), 'Context is not required.');
// Place test block via block UI to check if contexts are correctly exposed.
$this->drupalGet(
'admin/structure/block/add/views_block:test_view_block_with_context-block_1/classy',
['query' => ['region' => 'content']]
);
$edit = [
'settings[context_mapping][nid]' => '@node.node_route_context:node',
];
$this->drupalPostForm(NULL, $edit, 'Save block');
// Check if mapping saved correctly.
/** @var \Drupal\block\BlockInterface $block */
$block = $this->container->get('entity_type.manager')
->getStorage('block')
->load('views_block__test_view_block_with_context_block_1');
$expected_settings = [
'id' => 'views_block:test_view_block_with_context-block_1',
'label' => '',
'provider' => 'views',
'label_display' => 'visible',
'views_label' => '',
'items_per_page' => 'none',
'context_mapping' => ['nid' => '@node.node_route_context:node']
];
$this->assertEqual($block->getPlugin()->getConfiguration(), $expected_settings, 'Block settings are correct.');
// Make sure view behaves as expected.
$this->drupalGet('<front>');
$this->assertText('Test view: No results found.');
$this->drupalGet($this->nodes[0]->toUrl());
$this->assertText('Test view row: First test node');
$this->drupalGet($this->nodes[1]->toUrl());
$this->assertText('Test view row: Second test node');
// Check the second block which should expose two integer contexts, one
// based on the numeric plugin and the other based on numeric validation.
$definition = $this->container->get('plugin.manager.block')
->getDefinition('views_block:test_view_block_with_context-block_2');
$this->assertTrue($definition['context']['created'] instanceof ContextDefinitionInterface);
/** @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context */
$context = $definition['context']['created'];
$this->assertEqual($context->getDataType(), 'integer', 'Context definition data type is correct.');
$this->assertEqual($context->getLabel(), 'Content: Authored on', 'Context definition label is correct.');
$this->assertFalse($context->isRequired(), 'Context is not required.');
$this->assertTrue($definition['context']['vid'] instanceof ContextDefinitionInterface);
/** @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context */
$context = $definition['context']['vid'];
$this->assertEqual($context->getDataType(), 'integer', 'Context definition data type is correct.');
$this->assertEqual($context->getLabel(), 'Content: Revision ID', 'Context definition label is correct.');
$this->assertFalse($context->isRequired(), 'Context is not required.');
$this->assertTrue($definition['context']['title'] instanceof ContextDefinitionInterface);
/** @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context */
$context = $definition['context']['title'];
$this->assertEqual($context->getDataType(), 'string', 'Context definition data type is correct.');
$this->assertEqual($context->getLabel(), 'Content: Title', 'Context definition label is correct.');
$this->assertFalse($context->isRequired(), 'Context is not required.');
}
}
......@@ -111,6 +111,11 @@ protected function setUp() {
$this->displayHandler->expects($this->any())
->method('getPluginId')
->willReturn('block');
$this->displayHandler->expects($this->any())
->method('getHandlers')
->willReturn([]);
$this->executable->display_handler = $this->displayHandler;
$this->storage = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityStorage')
......
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