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];
}
}
}
......
......@@ -13,7 +13,9 @@
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldConfigInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Field\FormatterPluginManager;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Form\FormStateInterface;
......@@ -22,6 +24,7 @@
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\views\Plugin\CacheablePluginInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
......@@ -36,6 +39,8 @@
*
* @ingroup views_field_handlers
*
* @todo Rename the class https://www.drupal.org/node/2408667
*
* @ViewsField("field")
*/
class Field extends FieldPluginBase implements CacheablePluginInterface, MultiItemsFieldHandlerInterface {
......@@ -121,6 +126,13 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
*/
protected $renderer;
/**
* The field type plugin manager.
*
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypePluginManager;
/**
* Constructs a \Drupal\field\Plugin\views\field\Field object.
*
......@@ -134,19 +146,27 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
* The field formatter plugin manager.
* @param \Drupal\Core\Field\FormatterPluginManager $formatter_plugin_manager
* The field formatter plugin manager.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager
* The field plugin type manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->formatterPluginManager = $formatter_plugin_manager;
$this->fieldTypePluginManager = $field_type_plugin_manager;
$this->languageManager = $language_manager;
$this->renderer = $renderer;
// @todo Unify 'entity field'/'field_name' instead of converting back and
// forth. https://www.drupal.org/node/2410779
if (isset($this->definition['entity field'])) {
$this->definition['field_name'] = $this->definition['entity field'];
}
}
/**
......@@ -159,6 +179,7 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_definition,
$container->get('entity.manager'),
$container->get('plugin.manager.field.formatter'),
$container->get('plugin.manager.field.field_type'),
$container->get('language_manager'),
$container->get('renderer')
);
......@@ -172,7 +193,7 @@ public static function create(ContainerInterface $container, array $configuratio
*/
protected function getFieldDefinition() {
if (!$this->fieldDefinition) {
$field_storage_config = $this->getFieldStorageConfig();
$field_storage_config = $this->getFieldStorageDefinition();
$this->fieldDefinition = BaseFieldDefinition::createFromFieldStorageDefinition($field_storage_config);
}
return $this->fieldDefinition;
......@@ -228,38 +249,10 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
* {@inheritdoc}
*/
public function access(AccountInterface $account) {
$base_table = $this->get_base_table();
$access_control_handler = $this->entityManager->getAccessControlHandler($this->definition['entity_tables'][$base_table]);
$access_control_handler = $this->entityManager->getAccessControlHandler($this->getEntityType());
return $access_control_handler->fieldAccess('view', $this->getFieldDefinition(), $account);
}
/**
* Set the base_table and base_table_alias.
*
* @return string
* The base table which is used in the current view "context".
*/
function get_base_table() {
if (!isset($this->base_table)) {
// This base_table is coming from the entity not the field.
$this->base_table = $this->view->storage->get('base_table');
// If the current field is under a relationship you can't be sure that the
// base table of the view is the base table of the current field.
// For example a field from a node author on a node view does have users as base table.
if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
$relationships = $this->view->display_handler->getOption('relationships');
if (!empty($relationships[$this->options['relationship']])) {
$options = $relationships[$this->options['relationship']];
$data = Views::viewsData()->get($options['table']);
$this->base_table = $data[$options['field']]['relationship']['base'];
}
}
}
return $this->base_table;
}
/**
* Called to add the field to a query.
*
......@@ -267,9 +260,6 @@ function get_base_table() {
* plugin. Columns are added only if they are used in groupings.
*/
public function query($use_groupby = FALSE) {
$this->get_base_table();
$entity_type = $this->definition['entity_tables'][$this->base_table];
$fields = $this->additional_fields;
// No need to add the entity type.
$entity_type_key = array_search('entity_type', $fields);
......@@ -286,16 +276,10 @@ public function query($use_groupby = FALSE) {
}
$options += is_array($this->options['group_columns']) ? $this->options['group_columns'] : array();
$fields = array();
$rkey = $this->definition['is revision'] ? EntityStorageInterface::FIELD_LOAD_REVISION : EntityStorageInterface::FIELD_LOAD_CURRENT;
// Go through the list and determine the actual column name from field api.
$fields = array();
foreach ($options as $column) {
$name = $column;
if (isset($field_definition['storage_details']['sql'][$rkey][$this->table][$column])) {
$name = $field_definition['storage_details']['sql'][$rkey][$this->table][$column];
}
$fields[$column] = $name;
$fields[$column] = $this->getTableMapping()->getFieldColumnName($this->getFieldStorageDefinition(), $column);
}
$this->group_fields = $fields;
......@@ -370,12 +354,8 @@ public function clickSort($order) {
}
$this->ensureMyTable();
$entity_type_id = $this->definition['entity_type'];
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$field_storage = $field_storage_definitions[$this->definition['field_name']];
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $this->entityManager->getStorage($entity_type_id)->getTableMapping();
$column = $table_mapping->getFieldColumnName($field_storage, $this->options['click_sort_column']);
$field_storage_definition = $this->getFieldStorageDefinition();
$column = $this->getTableMapping()->getFieldColumnName($field_storage_definition, $this->options['click_sort_column']);
if (!isset($this->aliases[$column])) {
// Column is not in query; add a sort on it (without adding the column).
$this->aliases[$column] = $this->tableAlias . '.' . $column;
......@@ -383,13 +363,36 @@ public function clickSort($order) {
$this->query->addOrderBy(NULL, NULL, $order, $this->aliases[$column]);
}
/**
* Gets the field storage of the used field.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface
*/
protected function getFieldStorageDefinition() {
$entity_type_id = $this->definition['entity_type'];
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$field_storage = NULL;
// @todo Unify 'entity field'/'field_name' instead of converting back and
// forth. https://www.drupal.org/node/2410779
if (isset($this->definition['field_name'])) {
$field_storage = $field_storage_definitions[$this->definition['field_name']];
}
elseif (isset($this->definition['entity field'])) {
$field_storage = $field_storage_definitions[$this->definition['entity field']];
}
return $field_storage;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->definition['entity_type']);
$field_storage = $field_storage_definitions[$this->definition['field_name']];
$field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field_storage->getType());
$column_names = array_keys($field_storage->getColumns());
$field_storage_definition = $this->getFieldStorageDefinition();
$field_type = $this->fieldTypePluginManager->getDefinition($field_storage_definition->getType());
$column_names = array_keys($field_storage_definition->getColumns());
$default_column = '';
// Try to determine a sensible default.
if (count($column_names) == 1) {
......@@ -403,11 +406,13 @@ protected function defineOptions() {
$options['click_sort_column'] = array(
'default' => $default_column,
);
$options['type'] = array(
'default' => $field_type['default_formatter'],
'default' => isset($field_type['default_formatter']) ? $field_type['default_formatter'] : '',
);
$options['settings'] = array(
'default' => array(),
'default' => isset($this->definition['default_formatter_settings']) ? $this->definition['default_formatter_settings'] : [],
);
$options['group_column'] = array(
'default' => $default_column,
......@@ -423,7 +428,7 @@ protected function defineOptions() {
// If we know the exact number of allowed values, then that can be
// the default. Otherwise, default to 'all'.
$options['delta_limit'] = array(
'default' => ($field_storage->getCardinality() > 1) ? $field_storage->getCardinality() : 'all',
'default' => ($field_storage_definition->getCardinality() > 1) ? $field_storage_definition->getCardinality() : 'all',
);
$options['delta_offset'] = array(
'default' => 0,
......@@ -756,7 +761,7 @@ public function getItems(ResultRow $values) {
$items = array();
if ($this->options['field_api_classes']) {
return array(array('rendered' => drupal_render($render_array)));
return array(array('rendered' => $this->renderer->render($render_array)));
}
foreach (Element::children($render_array) as $count) {
......@@ -955,9 +960,11 @@ public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
// Add the module providing the configured field storage as a dependency.
$dependencies['config'][] = $this->getFieldStorageConfig()->getConfigDependencyName();
if (($field_storage_definition = $this->getFieldStorageDefinition()) && $field_storage_definition instanceof EntityInterface) {
$dependencies['config'][] = $field_storage_definition->getConfigDependencyName();
}
// Add the module providing the formatter.
if ($this->options['type']) {
if (!empty($this->options['type'])) {
$dependencies['module'][] = $this->formatterPluginManager->getDefinition($this->options['type'])['provider'];
}
......@@ -983,4 +990,25 @@ public function getCacheContexts() {
return $contexts;
}
/**
* Gets the table mapping for the entity type of the field.
*
* @return \Drupal\Core\Entity\Sql\DefaultTableMapping
* The table mapping.
*/
protected function getTableMapping() {
return $this->entityManager->getStorage($this->definition['entity_type'])->getTableMapping();
}
/**
* {@inheritdoc}
*/
public function getValue(ResultRow $values, $field = NULL) {
if ($field === NULL) {
return $this->getEntity($values)->{$this->definition['field_name']}->value;
}
return $this->getEntity($values)->{$this->definition['field_name']}->$field;
}
}
<?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();