Commit 71cb7bb5 authored by alexpott's avatar alexpott

Issue #2407801 by dawehner, Gábor Hojtsy, yched, jibran: Views generic field...

Issue #2407801 by dawehner, Gábor Hojtsy, yched, jibran: Views generic field handler does not work with base fields
parent dd3a5976
......@@ -22,6 +22,15 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
use ThirdPartySettingsTrait;
/**
* The 'mode' for runtime EntityDisplay objects used to render entities with
* arbitrary display options rather than a configured view mode or form mode.
*
* @todo Prevent creation of a mode with this ID
* https://www.drupal.org/node/2410727
*/
const CUSTOM_MODE = '_custom';
/**
* Unique ID for the config entity.
*
......@@ -55,7 +64,7 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
*
* @var string
*/
public $mode;
public $mode = self::CUSTOM_MODE;
/**
* Whether this display is enabled or not. If the entity (form) display
......@@ -112,7 +121,7 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
if (!isset($values['targetEntityType']) || !isset($values['bundle']) || !isset($values['mode'])) {
if (!isset($values['targetEntityType']) || !isset($values['bundle'])) {
throw new \InvalidArgumentException('Missing required properties for an EntityDisplay entity.');
}
......@@ -156,13 +165,13 @@ public function preSave(EntityStorageInterface $storage, $update = TRUE) {
*/
public function calculateDependencies() {
parent::calculateDependencies();
$target_entity_type = \Drupal::entityManager()->getDefinition($this->targetEntityType);
$target_entity_type = $this->entityManager()->getDefinition($this->targetEntityType);
$bundle_entity_type_id = $target_entity_type->getBundleEntityType();
if ($bundle_entity_type_id != 'bundle') {
// If the target entity type uses entities to manage its bundles then
// depend on the bundle entity.
if (!$bundle_entity = \Drupal::entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle)) {
if (!$bundle_entity = $this->entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle)) {
throw new \LogicException(String::format('Missing bundle entity, entity type %type, entity id %bundle.', array('%type' => $bundle_entity_type_id, '%bundle' => $this->bundle)));
}
$this->addDependency('config', $bundle_entity->getConfigDependencyName());
......@@ -181,7 +190,7 @@ public function calculateDependencies() {
}
// Depend on configured modes.
if ($this->mode != 'default') {
$mode_entity = \Drupal::entityManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode);
$mode_entity = $this->entityManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode);
$this->addDependency('config', $mode_entity->getConfigDependencyName());
}
return $this->dependencies;
......@@ -213,39 +222,42 @@ public function toArray() {
* - or that are not supposed to be configurable.
*/
protected function init() {
// Fill in defaults for extra fields.
$context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
$extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
$extra_fields = isset($extra_fields[$context]) ? $extra_fields[$context] : array();
foreach ($extra_fields as $name => $definition) {
if (!isset($this->content[$name]) && !isset($this->hidden[$name])) {
// Extra fields are visible by default unless they explicitly say so.
if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
$this->content[$name] = array(
'weight' => $definition['weight']
);
}
else {
$this->hidden[$name] = TRUE;
// Only populate defaults for "official" view modes and form modes.
if ($this->mode !== static::CUSTOM_MODE) {
// Fill in defaults for extra fields.
$context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
$extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
$extra_fields = isset($extra_fields[$context]) ? $extra_fields[$context] : array();
foreach ($extra_fields as $name => $definition) {
if (!isset($this->content[$name]) && !isset($this->hidden[$name])) {
// Extra fields are visible by default unless they explicitly say so.
if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
$this->content[$name] = array(
'weight' => $definition['weight']
);
}
else {
$this->hidden[$name] = TRUE;
}
}
}
}
// Fill in defaults for fields.
$fields = $this->getFieldDefinitions();
foreach ($fields as $name => $definition) {
if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
$options = $definition->getDisplayOptions($this->displayContext);
if (!empty($options['type']) && $options['type'] == 'hidden') {
$this->hidden[$name] = TRUE;
// Fill in defaults for fields.
$fields = $this->getFieldDefinitions();
foreach ($fields as $name => $definition) {
if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
$options = $definition->getDisplayOptions($this->displayContext);
if (!empty($options['type']) && $options['type'] == 'hidden') {
$this->hidden[$name] = TRUE;
}
elseif ($options) {
$this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options);
}
// Note: (base) fields that do not specify display options are not
// tracked in the display at all, in order to avoid cluttering the
// configuration that gets saved back.
}
elseif ($options) {
$this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options);
}
// Note: (base) fields that do not specify display options are not
// tracked in the display at all, in order to avoid cluttering the
// configuration that gets saved back.
}
}
}
......@@ -348,7 +360,12 @@ protected function getFieldDefinitions() {
if (!isset($this->fieldDefinitions)) {
$definitions = \Drupal::entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle);
$this->fieldDefinitions = array_filter($definitions, array($this, 'fieldHasDisplayOptions'));
// For "official" view modes and form modes, ignore fields whose
// definition states they should not be displayed.
if ($this->mode !== static::CUSTOM_MODE) {
$definitions = array_filter($definitions, array($this, 'fieldHasDisplayOptions'));
}
$this->fieldDefinitions = $definitions;
}
return $this->fieldDefinitions;
......
......@@ -475,7 +475,6 @@ protected function getSingleFieldDisplay($entity, $field_name, $display_options)
$this->singleFieldDisplays[$key] = EntityViewDisplay::create(array(
'targetEntityType' => $entity_type_id,
'bundle' => $bundle,
'mode' => '_custom',
'status' => TRUE,
))->setComponent($field_name, $display_options);
}
......
......@@ -92,7 +92,6 @@ public static function createFromFieldStorageDefinition(FieldStorageDefinitionIn
->setName($definition->getName())
->setProvider($definition->getProvider())
->setQueryable($definition->isQueryable())
->setRequired($definition->isRequired())
->setRevisionable($definition->isRevisionable())
->setSettings($definition->getSettings())
->setTargetEntityTypeId($definition->getTargetEntityTypeId())
......
......@@ -18,7 +18,7 @@ views.argument.field_list_string:
views.field.field:
type: views_field
label: 'Log event message'
label: 'Views entity field handler'
mapping:
click_sort_column:
type: string
......@@ -27,11 +27,8 @@ views.field.field:
type: string
label: 'Formatter'
settings:
type: sequence
label: 'Settings'
sequence:
- type: string
label: 'Setting'
type: field.formatter.settings.[%parent.type]
group_column:
type: string
label: 'Group by column'
......
......@@ -29,7 +29,7 @@
* "delete" = "Drupal\entity_test\EntityTestDeleteForm"
* },
* "translation" = "Drupal\content_translation\ContentTranslationHandler",
* "views_data" = "Drupal\views\EntityViewsData"
* "views_data" = "Drupal\entity_test\EntityTestViewsData"
* },
* base_table = "entity_test",
* persistent_cache = FALSE,
......
<?php
/**
* @file
* Contains Drupal\entity_test\EntityTestViewsData.
*/
namespace Drupal\entity_test;
use Drupal\Component\Utility\NestedArray;
use Drupal\views\EntityViewsData;
/**
* Provides a view to override views data for test entity types.
*/
class EntityTestViewsData extends EntityViewsData {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$views_data = parent::getViewsData();
if ($this->entityType->id() != 'entity_test') {
return $views_data;
}
$views_data = NestedArray::mergeDeep($views_data, \Drupal::state()->get('entity_test.views_data', []));
return $views_data;
}
}
......@@ -91,7 +91,7 @@ public function getHandler($item, $override = NULL) {
if (isset($data[$field][$this->handlerType])) {
$definition = $data[$field][$this->handlerType];
foreach (array('group', 'title', 'title short', 'help', 'real field', 'real table', 'entity field') as $key) {
foreach (array('group', 'title', 'title short', 'help', 'real field', 'real table', 'entity type', 'entity field') as $key) {
if (!isset($definition[$key])) {
// First check the field level.
if (!empty($data[$field][$key])) {
......@@ -99,7 +99,8 @@ public function getHandler($item, $override = NULL) {
}
// Then if that doesn't work, check the table level.
elseif (!empty($data['table'][$key])) {
$definition[$key] = $data['table'][$key];
$definition_key = $key === 'entity type' ? 'entity_type' : $key;
$definition[$definition_key] = $data['table'][$key];
}
}
}
......
<?php
/**
* @file
* Contains Drupal\views\Tests\Handler\FieldFieldTest.
*/
namespace Drupal\views\Tests\Handler;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Plugin\views\field\Field;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Views;
/**
* Provides some integration tests for the Field handler.
*
* @see \Drupal\views\Plugin\views\field\Field
* @group views
*/
class FieldFieldTest extends ViewUnitTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['field', 'entity_test', 'user'];
/**
* {@inheritdoc}
*/
public static $testViews = ['test_field_field_test'];
/**
* The stored test entities.
*
* @var \Drupal\entity_test\Entity\EntityTest[]
*/
protected $entities;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test');
// Setup a field storage and field, but also change the views data for the
// entity_test entity type.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_test',
'type' => 'integer',
'entity_type' => 'entity_test',
]);
$field_storage->save();
$field = FieldConfig::create([
'field_name' => 'field_test',
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
]);
$field->save();
$random_number = (string) 30856;
for ($i = 0; $i < 5; $i++) {
$this->entities[$i] = $entity = EntityTest::create([
'bundle' => 'entity_test',
'field_test' => $random_number[$i],
]);
$entity->save();
}
\Drupal::state()->set('entity_test.views_data', [
'entity_test' => [
'id' => [
'field' => [
'id' => 'field',
],
],
],
]);
Views::viewsData()->clear();
}
/**
* Tests the result of a view with base fields and configurable fields.
*/
public function testSimpleExecute() {
$executable = Views::getView('test_field_field_test');
$executable->execute();
$this->assertTrue($executable->field['id'] instanceof Field);
$this->assertTrue($executable->field['field_test'] instanceof Field);
$this->assertIdenticalResultset($executable, [
['id' => 1, 'field_test' => 3],
['id' => 2, 'field_test' => 0],
['id' => 3, 'field_test' => 8],
['id' => 4, 'field_test' => 5],
['id' => 5, 'field_test' => 6],
],
['id' => 'id', 'field_test' => 'field_test']
);
}
/**
* Tests the output of a view with base fields and configurable fields.
*/
public function testSimpleRender() {
$executable = Views::getView('test_field_field_test');
$executable->execute();
$this->assertEqual(1, $executable->getStyle()->getField(0, 'id'));
$this->assertEqual(3, $executable->getStyle()->getField(0, 'field_test'));
$this->assertEqual(2, $executable->getStyle()->getField(1, 'id'));
$this->assertEqual(0, $executable->getStyle()->getField(1, 'field_test'));
$this->assertEqual(3, $executable->getStyle()->getField(2, 'id'));
$this->assertEqual(8, $executable->getStyle()->getField(2, 'field_test'));
$this->assertEqual(4, $executable->getStyle()->getField(3, 'id'));
$this->assertEqual(5, $executable->getStyle()->getField(3, 'field_test'));
$this->assertEqual(5, $executable->getStyle()->getField(4, 'id'));
$this->assertEqual(6, $executable->getStyle()->getField(4, 'field_test'));
}
}
......@@ -7,6 +7,7 @@
namespace Drupal\views\Tests;
use Drupal\views\Plugin\views\field\Field;
use Drupal\views\ViewExecutable;
/**
......@@ -90,8 +91,15 @@ protected function assertIdenticalResultsetHelper($view, $expected_result, $colu
foreach ($view->result as $key => $value) {
$row = array();
foreach ($column_map as $view_column => $expected_column) {
if (property_exists($value, $view_column)) {
$row[$expected_column] = (string) $value->$view_column;
}
// The comparison will be done on the string representation of the value.
$row[$expected_column] = (string) $value->$view_column;
// For entity fields we don't have the raw value. Let's try to fetch it
// using the entity itself.
elseif (empty($value->$view_column) && isset($view->field[$expected_column]) && ($field = $view->field[$expected_column]) && $field instanceof Field) {
$row[$expected_column] = $field->getEntity($value)->{$field->definition['field_name']}->value;
}
}
$result[$key] = $row;
}
......
langcode: und
status: true
dependencies: { }
id: test_field_field_test
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: '8'
display:
default:
display_options:
access:
type: none
cache:
type: none
fields:
id:
id: id
table: entity_test
field: id
plugin_id: field
entity_type: entity_test
entity_field: id
field_test:
id: field_test
table: entity_test__field_test
field: field_test
plugin_id: field
entity_type: entity_test
entity_field: field_test
style:
type: html_list
display_plugin: default
display_title: Master
id: default
position: 0
......@@ -87,7 +87,7 @@ display:
click_sort_column: value
type: string
settings:
link_to_entity: '0'
link_to_entity: false
group_column: value
group_columns: { }
group_rows: true
......
<?php
/**
* @file
* Contains \Drupal\Tests\views\Unit\Plugin\HandlerBaseTest.
*/
namespace Drupal\Tests\views\Unit\Plugin;
use Drupal\Tests\UnitTestCase;
use Drupal\views\Plugin\views\HandlerBase;
/**
* @coversDefaultClass \Drupal\views\Plugin\views\HandlerBase
* @group Views
*/
class HandlerBaseTest extends UnitTestCase {
use HandlerTestTrait;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->setupViewsData();
$this->setupExecutableAndView();
$this->setupDisplay();
}
/**
* @covers ::getEntityType
*/
public function testGetEntityTypeForFieldOnBaseTable() {
$handler = new TestHandler([], 'test_handler', []);
$handler->init($this->executable, $this->display);
$this->view->expects($this->any())
->method('get')
->with('base_table')
->willReturn('test_entity_type_table');
$this->viewsData->expects($this->any())
->method('get')
->with('test_entity_type_table')
->willReturn([
'table' => ['entity type' => 'test_entity_type']
]);
$handler->setViewsData($this->viewsData);
$this->assertEquals('test_entity_type', $handler->getEntityType());
}
/**
* @covers ::getEntityType
*/
public function testGetEntityTypeForFieldWithRelationship() {
$handler = new TestHandler([], 'test_handler', []);
$options = ['relationship' => 'test_relationship'];
$handler->init($this->executable, $this->display, $options);
$this->display->expects($this->atLeastOnce())
->method('getOption')
->with('relationships')
->willReturn(['test_relationship' => ['table' => 'test_entity_type_table', 'id' => 'test_relationship', 'field' => 'test_relationship']]);
$this->view->expects($this->any())
->method('get')
->with('base_table')
->willReturn('test_entity_type_table');
$this->viewsData->expects($this->any())
->method('get')
->willReturnMap([
['test_entity_type_table', [
'table' => ['entity type' => 'test_entity_type'],
'test_relationship' => [
'relationship' => [
'base' => 'test_other_entity_type_table',
'base field' => 'id',
],
],
]],
['test_other_entity_type_table', [
'table' => ['entity type' => 'test_other_entity_type'],
]],
]);
$handler->setViewsData($this->viewsData);
$this->assertEquals('test_other_entity_type', $handler->getEntityType());
}
}
/**
* Allow testing base handler implementation by extending the abstract class.
*/
class TestHandler extends HandlerBase {
}
<?php
/**
* @file
* Contains \Drupal\Tests\views\Unit\Plugin\HandlerTestTrait.
*/
namespace Drupal\Tests\views\Unit\Plugin;
/**
* Test trait to mock dependencies of a handler.
*/
trait HandlerTestTrait {
/**
* The mocked view entity.
*
* @var \Drupal\views\Entity\View|\PHPUnit_Framework_MockObject_MockObject
*/
protected $view;
/**
* The mocked view executable.
*
* @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject
*/
protected $executable;
/**
* The mocked views data.
*
* @var \Drupal\views\ViewsData|\PHPUnit_Framework_MockObject_MockObject
*/
protected $viewsData;
/**
* The mocked display.
*
* @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject
*/
protected $display;
/**
* Sets up a view executable and a view entity.
*/
protected function setupExecutableAndView() {
$this->view = $this->getMockBuilder('Drupal\views\Entity\View')
->disableOriginalConstructor()
->getMock();
$this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->getMock();
$this->executable->storage = $this->view;
}
/**
* Sets up a mocked views data object.
*/
protected function setupViewsData() {
$this->viewsData = $this->getMockBuilder('Drupal\views\ViewsData')
->disableOriginalConstructor()
->getMock();
}
/**
* Sets up a mocked display object.
*/
protected function setupDisplay() {
$this->display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
->disableOriginalConstructor()
->getMock();
}
}
This diff is collapsed.
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