From 9a5eb867cd95bb0de6850216c948ad1a553deb7c Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Fri, 30 Jan 2015 10:37:56 +0000 Subject: [PATCH] Issue #2341357 by xjm, dawehner, larowlan, Wim Leers: Views entity area config is not deployable and missing dependencies --- .../Core/Config/Entity/ConfigEntityBase.php | 11 + core/lib/Drupal/Core/Entity/Entity.php | 9 + .../Drupal/Core/Entity/EntityInterface.php | 11 + core/lib/Drupal/Core/Entity/EntityManager.php | 23 ++ .../Core/Entity/EntityManagerInterface.php | 20 ++ .../Core/Entity/EntityStorageInterface.php | 2 +- .../install/views.view.taxonomy_term.yml | 2 +- .../views/config/schema/views.area.schema.yml | 4 +- core/modules/views/src/Entity/View.php | 1 + .../views/src/Plugin/views/area/Entity.php | 130 ++++++- .../views/area/TokenizeAreaPluginBase.php | 2 +- .../src/Tests/Handler/AreaEntityTest.php | 84 ++++- .../views.view.test_entity_area.yml | 15 +- .../tests/src/Unit/Plugin/area/EntityTest.php | 327 ++++++++++++++++++ .../views_ui/src/Tests/AreaEntityUITest.php | 106 ++++++ core/modules/views_ui/src/ViewUI.php | 7 + 16 files changed, 723 insertions(+), 31 deletions(-) create mode 100644 core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php create mode 100644 core/modules/views_ui/src/Tests/AreaEntityUITest.php diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index b943d175da99..12a454321757 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -415,6 +415,17 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface return $this->getEntityType()->getConfigPrefix() . '.' . $this->id(); } + /** + * {@inheritdoc} + */ + public function getConfigTarget() { + // For configuration entities, use the config ID for the config target + // identifier. This ensures that default configuration (which does not yet + // have UUIDs) can be provided and installed with references to the target, + // and also makes config dependencies more readable. + return $this->id(); + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 5f8483ddda32..64761a9a1246 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -569,4 +569,13 @@ abstract class Entity implements EntityInterface { return $this->getEntityTypeId() . ':' . $this->bundle() . ':' . $this->uuid(); } + /** + * {@inheritdoc} + */ + public function getConfigTarget() { + // For content entities, use the UUID for the config target identifier. + // This ensures that references to the target can be deployed reliably. + return $this->uuid(); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index 130612b85ef4..fac03809b7b5 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -429,4 +429,15 @@ interface EntityInterface extends AccessibleInterface { */ public function getConfigDependencyName(); + /** + * Gets the configuration target identifier for the entity. + * + * Used to supply the correct format for storing a reference targeting this + * entity in configuration. + * + * @return string + * The configuration target identifier. + */ + public function getConfigTarget(); + } diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 96365cc90118..5ba64f2d3a87 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -12,6 +12,7 @@ use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Utility\String; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\Entity\ConfigEntityType; use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\Entity\Exception\AmbiguousEntityClassException; use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException; @@ -978,6 +979,28 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa return reset($entities); } + /** + * {@inheritdoc} + */ + public function loadEntityByConfigTarget($entity_type_id, $target) { + $entity_type = $this->getDefinition($entity_type_id); + + // For configuration entities, the config target is given by the entity ID. + // @todo Consider adding a method to allow entity types to indicate the + // target identifier key rather than hard-coding this check. Issue: + // https://www.drupal.org/node/2412983. + if ($entity_type instanceof ConfigEntityType) { + $entity = $this->getStorage($entity_type_id)->load($target); + } + + // For content entities, the config target is given by the UUID. + else { + $entity = $this->loadEntityByUuid($entity_type_id, $target); + } + + return $entity; + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php index d60c47d7366d..f1e692115287 100644 --- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php @@ -469,6 +469,26 @@ interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListe */ public function loadEntityByUuid($entity_type_id, $uuid); + /** + * Loads an entity by the config target identifier. + * + * @param string $entity_type_id + * The entity type ID to load from. + * @param string $target + * The configuration target to load, as returned from + * \Drupal\Core\Entity\EntityInterface::getConfigTarget(). + * + * @return \Drupal\Core\Entity\EntityInterface|FALSE + * The entity object, or FALSE if there is no entity with the given UUID. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * Thrown if the target identifier is a UUID but the entity type does not + * support UUIDs. + * + * @see \Drupal\Core\Entity\EntityInterface::getConfigTarget() + */ + public function loadEntityByConfigTarget($entity_type_id, $target); + /** * Returns the entity type ID based on the class that is called on. * diff --git a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php index b4c897ea59f2..387e0fbd2678 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php @@ -104,7 +104,7 @@ interface EntityStorageInterface { * An associative array where the keys are the property names and the * values are the values those properties must have. * - * @return array + * @return \Drupal\Core\Entity\EntityInterface[] * An array of entity objects indexed by their ids. */ public function loadByProperties(array $values = array()); diff --git a/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml b/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml index 15f80ec43511..885d416c7c30 100644 --- a/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml +++ b/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml @@ -227,7 +227,7 @@ display: admin_label: '' empty: true tokenize: true - entity_id: '!1' + target: '!1' view_mode: full bypass_access: false plugin_id: entity diff --git a/core/modules/views/config/schema/views.area.schema.yml b/core/modules/views/config/schema/views.area.schema.yml index 1262a89db5c1..a7d5aa2c4cd6 100644 --- a/core/modules/views/config/schema/views.area.schema.yml +++ b/core/modules/views/config/schema/views.area.schema.yml @@ -8,9 +8,9 @@ views.area.entity: type: views_area label: 'Entity' mapping: - entity_id: + target: type: string - label: 'ID' + label: 'The target entity' view_mode: type: string label: 'View mode' diff --git a/core/modules/views/src/Entity/View.php b/core/modules/views/src/Entity/View.php index 472ce7974a34..ba3d0eeb8c42 100644 --- a/core/modules/views/src/Entity/View.php +++ b/core/modules/views/src/Entity/View.php @@ -266,6 +266,7 @@ class View extends ConfigEntityBase implements ViewEntityInterface { $executable = $this->getExecutable(); $executable->initDisplay(); + $executable->initStyle(); $handler_types = array_keys(Views::getHandlerTypes()); foreach ($executable->displayHandlers as $display) { diff --git a/core/modules/views/src/Plugin/views/area/Entity.php b/core/modules/views/src/Plugin/views/area/Entity.php index 93d0ad7ec53a..df5e25ea037d 100644 --- a/core/modules/views/src/Plugin/views/area/Entity.php +++ b/core/modules/views/src/Plugin/views/area/Entity.php @@ -7,9 +7,12 @@ namespace Drupal\views\Plugin\views\area; +use Drupal\Component\Uuid\Uuid; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\ViewExecutable; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides an area handler which renders an entity in a certain view mode. @@ -27,6 +30,43 @@ class Entity extends TokenizeAreaPluginBase { */ protected $entityType; + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * Constructs a new Entity instance. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->entityManager = $entity_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager') + ); + } + /** * Overrides \Drupal\views\Plugin\views\area\AreaPluginBase::init(). */ @@ -45,9 +85,10 @@ class Entity extends TokenizeAreaPluginBase { // this handler. $options['tokenize']['default'] = TRUE; - $options['entity_id'] = array('default' => ''); - $options['view_mode'] = array('default' => 'default'); - $options['bypass_access'] = array('default' => FALSE); + // Contains the config target identifier for the entity. + $options['target'] = ['default' => '']; + $options['view_mode'] = ['default' => 'default']; + $options['bypass_access'] = ['default' => FALSE]; return $options; } @@ -60,16 +101,31 @@ class Entity extends TokenizeAreaPluginBase { $form['view_mode'] = array( '#type' => 'select', - '#options' => \Drupal::entityManager()->getViewModeOptions($this->entityType), + '#options' => $this->entityManager->getViewModeOptions($this->entityType), '#title' => $this->t('View mode'), '#default_value' => $this->options['view_mode'], ); - $form['entity_id'] = array( - '#title' => $this->t('ID'), + $label = $this->entityManager->getDefinition($this->entityType)->getLabel(); + $target = $this->options['target']; + + // If the target does not contain tokens, try to load the entity and + // display the entity ID to the admin form user. + // @todo Use a method to check for tokens in + // https://www.drupal.org/node/2396607. + if (strpos($this->options['target'], '{{') === FALSE && strpos($this->options['target'], '!') === FALSE && strpos($this->options['target'], '%') === FALSE && strpos($this->options['target'], '[') === FALSE) { + // @todo If the entity does not exist, this will will show the config + // target identifier. Decide if this is the correct behavior in + // https://www.drupal.org/node/2415391. + if ($target_entity = $this->entityManager->loadEntityByConfigTarget($this->entityType, $this->options['target'])) { + $target = $target_entity->id(); + } + } + $form['target'] = [ + '#title' => $this->t('@entity_type_label ID', ['@entity_type_label' => $label]), '#type' => 'textfield', - '#default_value' => $this->options['entity_id'], - ); + '#default_value' => $target, + ]; $form['bypass_access'] = array( '#type' => 'checkbox', @@ -79,19 +135,67 @@ class Entity extends TokenizeAreaPluginBase { ); } + /** + * {@inheritdoc} + */ + public function submitOptionsForm(&$form, FormStateInterface $form_state) { + parent::submitOptionsForm($form, $form_state); + + // Load the referenced entity and store its config target identifier if + // the target does not contains tokens. + // @todo Use a method to check for tokens in + // https://www.drupal.org/node/2396607. + $options = $form_state->getValue('options'); + if (strpos($options['target'], '{{') === FALSE && strpos($options['target'], '!') === FALSE && strpos($options['target'], '%') === FALSE && strpos($options['target'], '[') === FALSE) { + if ($entity = $this->entityManager->getStorage($this->entityType)->load($options['target'])) { + $options['target'] = $entity->getConfigTarget(); + } + $form_state->setValue('options', $options); + } + } + /** * {@inheritdoc} */ public function render($empty = FALSE) { if (!$empty || !empty($this->options['empty'])) { - $entity_id = $this->tokenizeValue($this->options['entity_id']); - $entity = entity_load($this->entityType, $entity_id); - if ($entity && (!empty($this->options['bypass_access']) || $entity->access('view'))) { - return entity_view($entity, $this->options['view_mode']); + // @todo Use a method to check for tokens in + // https://www.drupal.org/node/2396607. + if (strpos($this->options['target'], '{{') !== FALSE || strpos($this->options['target'], '!') !== FALSE || strpos($this->options['target'], '%') !== FALSE || strpos($this->options['target'], '[') !== FALSE) { + $target_id = $this->tokenizeValue($this->options['target']); + if ($entity = $this->entityManager->getStorage($this->entityType)->load($target_id)) { + $target_entity = $entity; + } + } + else { + if ($entity = $this->entityManager->loadEntityByConfigTarget($this->entityType, $this->options['target'])) { + $target_entity = $entity; + } + } + if (isset($target_entity) && (!empty($this->options['bypass_access']) || $target_entity->access('view'))) { + $view_builder = $this->entityManager->getViewBuilder($this->entityType); + return $view_builder->view($target_entity, $this->options['view_mode']); } } - return array(); + return []; } + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + $dependencies = parent::calculateDependencies(); + + // Ensure that we don't add dependencies for placeholders. + // @todo Use a method to check for tokens in + // https://www.drupal.org/node/2396607. + if (strpos($this->options['target'], '{{') === FALSE && strpos($this->options['target'], '!') === FALSE && strpos($this->options['target'], '%') === FALSE && strpos($this->options['target'], '[') === FALSE) { + if ($entity = $this->entityManager->loadEntityByConfigTarget($this->entityType, $this->options['target'])) { + $dependencies[$this->entityManager->getDefinition($this->entityType)->getConfigDependencyKey()][] = $entity->getConfigDependencyName(); + } + } + + return $dependencies; + } } diff --git a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php index bf6fbf6d8440..4da1200725c7 100644 --- a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php +++ b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php @@ -106,7 +106,7 @@ abstract class TokenizeAreaPluginBase extends AreaPluginBase { */ public function tokenizeValue($value) { if ($this->options['tokenize']) { - $value = $this->view->style_plugin->tokenizeValue($value, 0); + $value = $this->view->getStyle()->tokenizeValue($value, 0); } // As we add the globalTokenForm() we also should replace the token here. return $this->globalTokenReplace($value); diff --git a/core/modules/views/src/Tests/Handler/AreaEntityTest.php b/core/modules/views/src/Tests/Handler/AreaEntityTest.php index af44b44dd451..bc3fe6ed6d8b 100644 --- a/core/modules/views/src/Tests/Handler/AreaEntityTest.php +++ b/core/modules/views/src/Tests/Handler/AreaEntityTest.php @@ -7,9 +7,11 @@ namespace Drupal\views\Tests\Handler; +use Drupal\block\Entity\Block; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormState; -use Drupal\views\Tests\ViewTestBase; +use Drupal\views\Entity\View; +use Drupal\views\Tests\ViewUnitTestBase; use Drupal\views\Views; /** @@ -18,14 +20,14 @@ use Drupal\views\Views; * @group views * @see \Drupal\views\Plugin\views\area\Entity */ -class AreaEntityTest extends ViewTestBase { +class AreaEntityTest extends ViewUnitTestBase { /** * Modules to enable. * * @var array */ - public static $modules = array('entity_test'); + public static $modules = ['entity_test', 'user', 'block']; /** * Views used by this test. @@ -34,10 +36,27 @@ class AreaEntityTest extends ViewTestBase { */ public static $testViews = array('test_entity_area'); + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); + } - $this->enableViewsTestModule(); + /** + * {@inheritdoc} + */ + protected function setUpFixtures() { + $this->installEntitySchema('user'); + $this->installEntitySchema('entity_test'); + $this->installConfig(['entity_test']); + + Block::create([ + 'id' => 'test_block', + 'plugin' => 'system_main_block', + ])->save(); + + parent::setUpFixtures(); } /** @@ -72,20 +91,51 @@ class AreaEntityTest extends ViewTestBase { * Tests the area handler. */ public function testEntityArea() { - + /** @var \Drupal\Core\Entity\EntityInterface[] $entities */ $entities = array(); for ($i = 0; $i < 3; $i++) { $random_label = $this->randomMachineName(); $data = array('bundle' => 'entity_test', 'name' => $random_label); - $entity_test = $this->container->get('entity.manager')->getStorage('entity_test')->create($data); + $entity_test = $this->container->get('entity.manager') + ->getStorage('entity_test') + ->create($data); + + $uuid_map[0] = 'aa0c61cb-b7bb-4795-972a-493dabcf529c'; + $uuid_map[1] = '62cef0ff-6f30-4f7a-b9d6-a8ed5a3a6bf3'; + $uuid_map[2] = '3161d6e9-3326-4719-b513-8fa68a731ba2'; + $entity_test->uuid->value = $uuid_map[$i]; + $entity_test->save(); $entities[] = $entity_test; - \Drupal::state()->set('entity_test_entity_access.view.' . $entity_test->id(), $i != 2); + \Drupal::state() + ->set('entity_test_entity_access.view.' . $entity_test->id(), $i != 2); } + $this->doTestCalculateDependencies(); + $this->doTestRender($entities); + } + + /** + * Tests rendering the entity area handler. + * + * @param \Drupal\Core\Entity\EntityInterface[] $entities + * The entities. + */ + public function doTestRender($entities) { $view = Views::getView('test_entity_area'); + $preview = $view->preview('default', [$entities[1]->id()]); + $this->setRawContent(\Drupal::service('renderer')->render($preview)); + + $result = $this->xpath('//div[@class = "view-header"]'); + $this->assertTrue(strpos(trim((string) $result[0]), $entities[0]->label()) !== FALSE, 'The rendered entity appears in the header of the view.'); + $this->assertTrue(strpos(trim((string) $result[0]), 'full') !== FALSE, 'The rendered entity appeared in the right view mode.'); + + $result = $this->xpath('//div[@class = "view-footer"]'); + $this->assertTrue(strpos(trim((string) $result[0]), $entities[1]->label()) !== FALSE, 'The rendered entity appears in the footer of the view.'); + $this->assertTrue(strpos(trim((string) $result[0]), 'full') !== FALSE, 'The rendered entity appeared in the right view mode.'); + $preview = $view->preview('default', array($entities[1]->id())); - $this->drupalSetContent(drupal_render($preview)); + $this->setRawContent(drupal_render($preview)); $result = $this->xpath('//div[@class = "view-header"]'); $this->assertTrue(strpos(trim((string) $result[0]), $entities[0]->label()) !== FALSE, 'The rendered entity appears in the header of the view.'); @@ -107,7 +157,7 @@ class AreaEntityTest extends ViewTestBase { $view->setHandler('default', 'header', 'entity_entity_test', $item); $preview = $view->preview('default', array($entities[1]->id())); - $this->drupalSetContent(drupal_render($preview)); + $this->setRawContent(drupal_render($preview)); $result = $this->xpath('//div[@class = "view-header"]'); $this->assertTrue(strpos(trim((string) $result[0]), $entities[0]->label()) !== FALSE, 'The rendered entity appears in the header of the view.'); @@ -116,7 +166,7 @@ class AreaEntityTest extends ViewTestBase { // Test entity access. $view = Views::getView('test_entity_area'); $preview = $view->preview('default', array($entities[2]->id())); - $this->drupalSetContent(drupal_render($preview)); + $this->setRawContent(drupal_render($preview)); $result = $this->xpath('//div[@class = "view-footer"]'); $this->assertTrue(strpos($result[0], $entities[2]->label()) === FALSE, 'The rendered entity does not appear in the footer of the view.'); @@ -129,4 +179,18 @@ class AreaEntityTest extends ViewTestBase { $this->assertTrue(isset($form['view_mode']['#options']['default']), 'Ensure that the default view mode is available.'); } + /** + * Tests the calculation of the rendered dependencies. + */ + public function doTestCalculateDependencies() { + $view = View::load('test_entity_area'); + + $dependencies = $view->calculateDependencies(); + // Ensure that both config and content entity dependencies are calculated. + $this->assertEqual([ + 'config' => ['block.block.test_block'], + 'content' => ['entity_test:entity_test:aa0c61cb-b7bb-4795-972a-493dabcf529c'], + ], $dependencies); + } + } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml index 38bbf787bcb0..d3e98c1d9cc1 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml @@ -1,6 +1,8 @@ langcode: und status: true -dependencies: { } +dependencies: + content: + - entity_test:entity_test:aa0c61cb-b7bb-4795-972a-493dabcf529c id: test_entity_area label: '' module: views @@ -21,7 +23,7 @@ display: field: entity_entity_test id: entity_entity_test table: views - entity_id: '1' + target: 'aa0c61cb-b7bb-4795-972a-493dabcf529c' view_mode: full plugin_id: entity footer: @@ -29,7 +31,14 @@ display: field: entity_entity_test id: entity_entity_test table: views - entity_id: '!1' + target: '!1' + view_mode: full + plugin_id: entity + entity_block: + field: entity_block + id: entity_block + table: views + target: 'test_block' view_mode: full plugin_id: entity fields: diff --git a/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php b/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php new file mode 100644 index 000000000000..bb8928146f3e --- /dev/null +++ b/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php @@ -0,0 +1,327 @@ +entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $this->entityStorage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $this->entityViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface'); + + $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $this->display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') + ->disableOriginalConstructor() + ->getMock(); + $this->stylePlugin = $this->getMockBuilder('Drupal\views\Plugin\views\style\StylePluginBase') + ->disableOriginalConstructor() + ->getMock(); + $this->executable->style_plugin = $this->stylePlugin; + + $this->entityHandler = new Entity(array(), 'entity', array('entity_type' => 'entity_test'), $this->entityManager); + + $this->display->expects($this->any()) + ->method('getPlugin') + ->with('style') + ->willReturn($this->stylePlugin); + $this->executable->expects($this->any()) + ->method('getStyle') + ->willReturn($this->stylePlugin); + + + $token = $this->getMockBuilder('Drupal\Core\Utility\Token') + ->disableOriginalConstructor() + ->getMock(); + $token->expects($this->any()) + ->method('replace') + ->willReturnArgument(0); + $container = new ContainerBuilder(); + $container->set('token', $token); + \Drupal::setContainer($container); + } + + /** + * Ensures that the entity manager returns an entity storage. + */ + protected function setupEntityManager() { + $this->entityManager->expects($this->any()) + ->method('getStorage') + ->with('entity_test') + ->willReturn($this->entityStorage); + $this->entityManager->expects($this->any()) + ->method('getViewBuilder') + ->with('entity_test') + ->willReturn($this->entityViewBuilder); + } + + /** + * Data provider for testing different types of tokens. + * + * @return array + */ + public function providerTestTokens() { + return [ + ['!1', 5], + ['%2', 6], + ['{{ test_render_token }}', 7], + ['[test:global_token]', 8], + ]; + } + + /** + * @covers ::render + * @covers ::defineOptions + * @covers ::init + */ + public function testRenderWithId() { + $this->setupEntityManager(); + $options = [ + 'target' => 1, + 'tokenize' => FALSE, + ]; + + /** @var \Drupal\Core\Entity\EntityInterface $entity */ + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity->expects($this->once()) + ->method('access') + ->willReturn(TRUE); + + $this->entityStorage->expects($this->never()) + ->method('loadByProperties'); + $this->entityManager->expects($this->any()) + ->method('loadEntityByConfigTarget') + ->willReturn($entity); + $this->entityViewBuilder->expects($this->once()) + ->method('view') + ->with($entity, 'default') + ->willReturn(['#markup' => 'hallo']); + + $this->entityHandler->init($this->executable, $this->display, $options); + + $result = $this->entityHandler->render(); + $this->assertEquals(['#markup' => 'hallo'], $result); + } + + /** + * @covers ::render + * @covers ::defineOptions + * @covers ::init + * + * @dataProvider providerTestTokens + */ + public function testRenderWithIdAndToken($token, $id) { + $this->setupEntityManager(); + $options = [ + 'target' => $token, + 'tokenize' => TRUE, + ]; + + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity->expects($this->once()) + ->method('access') + ->willReturn(TRUE); + + $this->stylePlugin->expects($this->once()) + ->method('tokenizeValue') + ->with($token, 0) + ->willReturn($id); + + $this->entityStorage->expects($this->never()) + ->method('loadByProperties'); + $this->entityStorage->expects($this->once()) + ->method('load') + ->with($id) + ->willReturn($entity); + $this->entityViewBuilder->expects($this->once()) + ->method('view') + ->with($entity, 'default') + ->willReturn(['#markup' => 'hallo']); + + $this->entityHandler->init($this->executable, $this->display, $options); + + $result = $this->entityHandler->render(); + $this->assertEquals(['#markup' => 'hallo'], $result); + } + + /** + * @covers ::render + * @covers ::defineOptions + * @covers ::init + */ + public function testRenderWithUuid() { + $this->setupEntityManager(); + $uuid = '1d52762e-b9d8-4177-908f-572d1a5845a4'; + $options = [ + 'target' => $uuid, + 'tokenize' => FALSE, + ]; + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity->expects($this->once()) + ->method('access') + ->willReturn(TRUE); + + $this->entityStorage->expects($this->never()) + ->method('load'); + $this->entityManager->expects($this->once()) + ->method('loadEntityByConfigTarget') + ->willReturn($entity); + $this->entityViewBuilder->expects($this->once()) + ->method('view') + ->with($entity, 'default') + ->willReturn(['#markup' => 'hallo']); + + $this->entityHandler->init($this->executable, $this->display, $options); + + $result = $this->entityHandler->render(); + $this->assertEquals(['#markup' => 'hallo'], $result); + } + + /** + * @covers ::calculateDependencies + * + * @dataProvider providerTestTokens + */ + public function testCalculateDependenciesWithPlaceholder($token, $id) { + $this->setupEntityManager(); + + $options = [ + 'target' => $token, + ]; + $this->entityHandler->init($this->executable, $this->display, $options); + + $this->assertEquals([], $this->entityHandler->calculateDependencies()); + } + + /** + * @covers ::calculateDependencies + */ + public function testCalculateDependenciesWithUuid() { + $this->setupEntityManager(); + + $uuid = '1d52762e-b9d8-4177-908f-572d1a5845a4'; + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity->expects($this->once()) + ->method('getConfigDependencyName') + ->willReturn('entity_test:test-bundle:1d52762e-b9d8-4177-908f-572d1a5845a4'); + $this->entityStorage->expects($this->never()) + ->method('load'); + $this->entityManager->expects($this->once()) + ->method('loadEntityByConfigTarget') + ->willReturn($entity); + $entity_type->expects($this->once()) + ->method('getConfigDependencyKey') + ->willReturn('content'); + $this->entityManager->expects($this->once()) + ->method('getDefinition') + ->willReturn($entity_type); + + $options = [ + 'target' => $uuid, + ]; + $this->entityHandler->init($this->executable, $this->display, $options); + + $this->assertEquals(['content' => ['entity_test:test-bundle:1d52762e-b9d8-4177-908f-572d1a5845a4']], $this->entityHandler->calculateDependencies()); + } + + /** + * @covers ::calculateDependencies + */ + public function testCalculateDependenciesWithEntityId() { + $this->setupEntityManager(); + + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity->expects($this->once()) + ->method('getConfigDependencyName') + ->willReturn('entity_test:test-bundle:1d52762e-b9d8-4177-908f-572d1a5845a4'); + $this->entityManager->expects($this->once()) + ->method('loadEntityByConfigTarget') + ->willReturn($entity); + $this->entityStorage->expects($this->never()) + ->method('loadByProperties'); + $entity_type->expects($this->once()) + ->method('getConfigDependencyKey') + ->willReturn('content'); + $this->entityManager->expects($this->once()) + ->method('getDefinition') + ->willReturn($entity_type); + + $options = [ + 'target' => 1, + ]; + $this->entityHandler->init($this->executable, $this->display, $options); + + $this->assertEquals(['content' => ['entity_test:test-bundle:1d52762e-b9d8-4177-908f-572d1a5845a4']], $this->entityHandler->calculateDependencies()); + } + +} diff --git a/core/modules/views_ui/src/Tests/AreaEntityUITest.php b/core/modules/views_ui/src/Tests/AreaEntityUITest.php new file mode 100644 index 000000000000..3de39d84521e --- /dev/null +++ b/core/modules/views_ui/src/Tests/AreaEntityUITest.php @@ -0,0 +1,106 @@ + 'test_id', 'plugin' => 'system_main_block']); + $block->save(); + + $entity_test = EntityTest::create(['bundle' => 'entity_test']); + $entity_test->save(); + + $default = $this->randomView([]); + $id = $default['id']; + $view = View::load($id); + + $this->drupalGet($view->urlInfo('edit-form')); + + // Add a global NULL argument to the view for testing argument placeholders. + $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/argument", ['name[views.null]' => 1], 'Add and configure contextual filters'); + $this->drupalPostForm(NULL, [], 'Apply'); + + // Configure both the entity_test area header and the block header to + // reference the given entities. + $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/header", ['name[views.entity_block]' => 1], 'Add and configure header'); + $this->drupalPostForm(NULL, ['options[target]' => $block->id()], 'Apply'); + + $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/header", ['name[views.entity_entity_test]' => 1], 'Add and configure header'); + $this->drupalPostForm(NULL, ['options[target]' => $entity_test->id()], 'Apply'); + + $this->drupalPostForm(NULL, [], 'Save'); + + // Confirm the correct target identifiers were saved for both entities. + $view = View::load($id); + $header = $view->getDisplay('default')['display_options']['header']; + $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header)); + + $this->assertEqual($block->id(), $header['entity_block']['target']); + $this->assertEqual($entity_test->uuid(), $header['entity_entity_test']['target']); + + // Confirm that the correct serial ID (for the entity_test) and config ID + // (for the block) are displayed in the form. + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_block"); + $this->assertFieldByName('options[target]', $block->id()); + + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test"); + $this->assertFieldByName('options[target]', $entity_test->id()); + + // Replace the header target entities with argument placeholders. + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_block", ['options[target]' => '!1'], 'Apply'); + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test", ['options[target]' => '!1'], 'Apply'); + $this->drupalPostForm(NULL, [], 'Save'); + + // Confirm that the argument placeholders are saved. + $view = View::load($id); + $header = $view->getDisplay('default')['display_options']['header']; + $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header)); + + $this->assertEqual('!1', $header['entity_block']['target']); + $this->assertEqual('!1', $header['entity_entity_test']['target']); + + // Confirm that the argument placeholders are still displayed in the form. + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_block"); + $this->assertFieldByName('options[target]', '!1'); + + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test"); + $this->assertFieldByName('options[target]', '!1'); + + // Change the targets for both headers back to the entities. + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_block", ['options[target]' => $block->id()], 'Apply'); + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test", ['options[target]' => $entity_test->id()], 'Apply'); + $this->drupalPostForm(NULL, [], 'Save'); + + // Confirm the targets were again saved correctly and not skipped based on + // the previous form value. + $view = View::load($id); + $header = $view->getDisplay('default')['display_options']['header']; + $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header)); + + $this->assertEqual($block->id(), $header['entity_block']['target']); + $this->assertEqual($entity_test->uuid(), $header['entity_entity_test']['target']); + } + +} diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index e8464fa31ecb..70241ef6919b 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -1151,6 +1151,13 @@ class ViewUI implements ViewEntityInterface { return $this->storage->getConfigDependencyName(); } + /** + * {@inheritdoc} + */ + public function getConfigTarget() { + return $this->storage->getConfigTarget(); + } + /** * {@inheritdoc} */