Commit 6ab60f77 authored by alexpott's avatar alexpott

Issue #2012130 by smiletrl, dawehner, damiankloip, larowlan, webflo, pcambra,...

Issue #2012130 by smiletrl, dawehner, damiankloip, larowlan, webflo, pcambra, dashaforbes, mgifford, effulgentsia: Regression: Views integration for "list" field types is broken
parent fcb679d8
<?php
/**
* @file
* Contains \Drupal\field\Views\FieldAPIHandlerTrait.
*/
namespace Drupal\field\Views;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* A trait containing helper methods for field definitions.
*/
trait FieldAPIHandlerTrait {
/**
* The field definition.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface
*/
protected $fieldDefinition;
/**
* The field storage definition.
*
* @var \Drupal\field\FieldStorageConfigInterface
*/
protected $fieldStorageDefinition;
/**
* Gets the field definition.
*
* A View works on an entity type across bundles, and thus only has access to
* field storage definitions. In order to be able to use widgets and
* formatters, we create a generic field definition out of that storage
* definition.
*
* @see BaseFieldDefinition::createFromFieldStorageDefinition()
*
* @return \Drupal\Core\Field\FieldDefinitionInterface
* The field definition used by this handler.
*/
protected function getFieldDefinition() {
if (!$this->fieldDefinition) {
$field_storage_config = $this->getFieldStorageDefinition();
$this->fieldDefinition = BaseFieldDefinition::createFromFieldStorageDefinition($field_storage_config);
}
return $this->fieldDefinition;
}
/**
* Gets the field storage configuration.
*
* @return \Drupal\field\FieldStorageConfigInterface
* The field storage definition used by this handler
*/
protected function getFieldStorageDefinition() {
if (!$this->fieldStorageDefinition) {
$field_storage_definitions = $this->getEntityManager()->getFieldStorageDefinitions($this->definition['entity_type']);
$this->fieldStorageDefinition = $field_storage_definitions[$this->definition['field_name']];
}
return $this->fieldStorageDefinition;
}
/**
* Returns the entity manager.
*
* @return \Drupal\Core\Entity\EntityManagerInterface
* The entity manager service.
*/
protected function getEntityManager() {
if (!isset($this->entityManager)) {
$this->entityManager = \Drupal::entityManager();
}
return $this->entityManager;
}
}
......@@ -114,3 +114,44 @@ field.widget.settings.options_buttons:
field.widget.settings.options_select:
type: mapping
label: 'Select list format settings'
views.argument.number_list_field:
type: views.argument.numeric
mapping:
summary:
type: mapping
label: 'Display a summary'
mapping:
sort_order:
type: string
label: 'Sort order'
number_of_records:
type: integer
label: 'Sort by'
format:
type: string
label: 'Format'
human:
type: boolean
views.argument.string_list_field:
type: views.argument.string
mapping:
summary:
type: mapping
label: 'Display a summary'
mapping:
sort_order:
type: string
label: 'Sort order'
number_of_records:
type: integer
label: 'Sort by'
format:
type: string
label: 'Format'
human:
type: boolean
views.filter.list_field:
type: views.filter.many_to_one
<?php
/**
* @file
* Provide Views data for options.module.
*
* @ingroup views_module_handlers
*/
/**
* Implements hook_field_views_data().
*
* Views integration for list fields. Have a different filter handler and
* argument handlers for list fields. This should allow to select values of
* the list.
*/
function options_field_views_data($field) {
$data = views_field_default_views_data($field);
$function = $field->getSetting('allowed_values_function');
// If this field makes use of dynamic allowed options, we ignore the views
// setting.
if (!empty($function)) {
return $data;
}
foreach ($data as $table_name => $table_data) {
foreach ($table_data as $field_name => $field_data) {
if (isset($field_data['filter']) && $field_name != 'delta') {
$data[$table_name][$field_name]['filter']['id'] = 'list_field';
}
if (isset($field_data['argument']) && $field_name != 'delta') {
if ($field->type == 'list_string') {
$data[$table_name][$field_name]['argument']['id'] = 'string_list_field';
}
else {
$data[$table_name][$field_name]['argument']['id'] = 'number_list_field';
}
}
}
}
return $data;
}
<?php
/**
* @file
* Contains \Drupal\options\Plugin\views\argument\NumberListField.
*/
namespace Drupal\options\Plugin\views\argument;
use Drupal\Component\Utility\String;
use Drupal\Core\Field\AllowedTagsXssTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\Views\FieldAPIHandlerTrait;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\argument\Numeric;
/**
* Argument handler for list field to show the human readable name in the
* summary.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("number_list_field")
*/
class NumberListField extends Numeric {
use AllowedTagsXssTrait;
use FieldAPIHandlerTrait;
/**
* Stores the allowed values of this field.
*
* @var array
*/
protected $allowedValues = NULL;
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$field_storage = $this->getFieldStorageDefinition();
$this->allowedValues = options_allowed_values($field_storage);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['summary']['contains']['human'] = ['default' => FALSE];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['summary']['human'] = [
'#title' => $this->t('Display list value as human readable'),
'#type' => 'checkbox',
'#default_value' => $this->options['summary']['human'],
'#states' => [
'visible' => [
':input[name="options[default_action]"]' => ['value' => 'summary'],
],
],
];
}
/**
* {@inheritdoc}
*/
public function summaryName($data) {
$value = $data->{$this->name_alias};
// If the list element has a human readable name show it.
if (isset($this->allowedValues[$value]) && !empty($this->options['summary']['human'])) {
return $this->fieldFilterXss($this->allowedValues[$value]);
}
// Else, fallback to the key.
else {
return String::checkPlain($value);
}
}
}
<?php
/**
* @file
* Contains \Drupal\options\Plugin\views\argument\StringListField.
*/
namespace Drupal\options\Plugin\views\argument;
use Drupal\Core\Field\AllowedTagsXssTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\Views\FieldAPIHandlerTrait;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\argument\String;
use Drupal\Component\Utility\String as StringUtility;
/**
* Argument handler for list field to show the human readable name in summary.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("string_list_field")
*/
class StringListField extends String {
use AllowedTagsXssTrait;
use FieldAPIHandlerTrait;
/**
* Stores the allowed values of this field.
*
* @var array
*/
protected $allowedValues = NULL;
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$field_storage = $this->getFieldStorageDefinition();
$this->allowedValues = options_allowed_values($field_storage);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['summary']['contains']['human'] = ['default' => FALSE];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['summary']['human'] = [
'#title' => $this->t('Display list value as human readable'),
'#type' => 'checkbox',
'#default_value' => $this->options['summary']['human'],
'#states' => [
'visible' => [
':input[name="options[default_action]"]' => ['value' => 'summary'],
],
],
];
}
/**
* {@inheritdoc}
*/
public function summaryName($data) {
$value = $data->{$this->name_alias};
// If the list element has a human readable name show it.
if (isset($this->allowedValues[$value]) && !empty($this->options['summary']['human'])) {
return $this->caseTransform($this->fieldFilterXss($this->allowedValues[$value]), $this->options['case']);
}
// Else, fallback to the key.
else {
return $this->caseTransform(StringUtility::checkPlain($value), $this->options['case']);
}
}
}
<?php
/**
* @file
* Contains \Drupal\field\Plugin\views\filter\ListField.
*/
namespace Drupal\options\Plugin\views\filter;
use Drupal\field\Views\FieldAPIHandlerTrait;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\filter\ManyToOne;
use Drupal\views\ViewExecutable;
/**
* Filter handler which uses list-fields as options.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("list_field")
*/
class ListField extends ManyToOne {
use FieldAPIHandlerTrait;
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$field_storage = $this->getFieldStorageDefinition();
// Set valueOptions here so getValueOptions() will just return it.
$this->valueOptions = options_allowed_values($field_storage);
}
}
<?php
/**
* @file
* Contains \Drupal\options\Tests\Views\OptionsListArgumentTest.
*/
namespace Drupal\options\Tests\Views;
use Drupal\views\Views;
/**
* Tests options list argument for views.
*
* @see \Drupal\options\Plugin\views\argument\NumberListField.
* @group views
*/
class OptionsListArgumentTest extends OptionsTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_options_list_argument_numeric', 'test_options_list_argument_string'];
/**
* Tests the options field argument.
*/
public function testViewsTestOptionsListArgument() {
$view = Views::getView('test_options_list_argument_numeric');
$this->executeView($view, [1]);
$resultset = [
['nid' => $this->nodes[0]->nid->value],
['nid' => $this->nodes[1]->nid->value],
];
$column_map = ['nid' => 'nid'];
$this->assertIdenticalResultset($view, $resultset, $column_map);
$view = Views::getView('test_options_list_argument_string');
$this->executeView($view, ['man', 'woman']);
$resultset = [
['nid' => $this->nodes[0]->nid->value],
['nid' => $this->nodes[1]->nid->value],
];
$column_map = ['nid' => 'nid'];
$this->assertIdenticalResultset($view, $resultset, $column_map);
}
}
<?php
/**
* @file
* Contains \Drupal\options\Tests\Views\OptionsListFilterTest.
*/
namespace Drupal\options\Tests\Views;
use Drupal\views\Views;
/**
* Tests options list filter for views.
*
* @see \Drupal\field\Plugin\views\filter\ListField.
* @group views
*/
class OptionsListFilterTest extends OptionsTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_options_list_filter'];
/**
* Tests options list field filter.
*/
public function testViewsTestOptionsListFilter() {
$view = Views::getView('test_options_list_filter');
$this->executeView($view);
$resultset = [
['nid' => $this->nodes[0]->nid->value],
['nid' => $this->nodes[1]->nid->value],
];
$column_map = ['nid' => 'nid'];
$this->assertIdenticalResultset($view, $resultset, $column_map);
}
}
<?php
/**
* @file
* Contains \Drupal\options\Tests\Views\OptionsTestBase.
*/
namespace Drupal\options\Tests\Views;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Tests\ViewUnitTestBase;
/**
* Base class for options views tests.
*/
abstract class OptionsTestBase extends ViewUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['options', 'options_test_views', 'node', 'user', 'field'];
/**
* Stores the nodes used for the different tests.
*
* @var array
*/
protected $nodes = [];
/**
* Stores the field values used for the different tests.
*
* @var array
*/
protected $fieldValues = [];
/**
* The used field names.
*
* @var string[]
*/
protected $fieldNames;
protected function setUp() {
parent::setUp();
$this->mockStandardInstall();
ViewTestData::createTestViews(get_class($this), ['options_test_views']);
$settings = [];
$settings['type'] = 'article';
$settings['field_test_list_string'][]['value'] = $this->fieldValues[0];
$settings['field_test_list_integer'][]['value'] = 0;
$node = Node::create($settings);
$node->save();
$this->nodes[] = $node;
$node = $node->createDuplicate();
$node->save();
$this->nodes[] = $node;
}
/**
* Provides a workaround for the inability to use the standard profile.
*
* @see http://drupal.org/node/1708692
*/
protected function mockStandardInstall() {
$this->installEntitySchema('user');
$this->installEntitySchema('node');
NodeType::create(
['type' => 'article']
)->save();
$this->fieldValues = [
$this->randomMachineName(),
$this->randomMachineName(),
];
$this->fieldNames = ['field_test_list_string', 'field_test_list_integer'];
// Create two field entities.
FieldStorageConfig::create([
'field_name' => $this->fieldNames[0],
'entity_type' => 'node',
'type' => 'list_string',
'cardinality' => 1,
'settings' => [
'allowed_values' => [
$this->fieldValues[0] => $this->fieldValues[0],
$this->fieldValues[1] => $this->fieldValues[1],
],
],
])->save();
FieldStorageConfig::create([
'field_name' => $this->fieldNames[1],
'entity_type' => 'node',
'type' => 'list_integer',
'cardinality' => 1,
'settings' => [
'allowed_values' => [
$this->fieldValues[0],
$this->fieldValues[1],
],
],
])->save();
foreach ($this->fieldNames as $field_name) {
FieldConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'label' => 'Test options list field',
'bundle' => 'article',
])->save();
}
}
}
name: 'Options test views'
type: module
description: 'Provides default views for views options tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- options
- views
langcode: en
status: true
dependencies:
config:
- node.type.article
module:
- node
- user
id: test_options_list_argument_numeric
label: 'test options list argument (numeric)'
module: views
description: ''
tag: ''
base_table: node
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 1
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: none
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: false
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: some
options:
items_per_page: 5
offset: 0
style:
type: default
row:
type: fields
fields:
title:
id: title
table: node_field_data
field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
link_to_node: true
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''