Commit e888be3f authored by alexpott's avatar alexpott

Issue #2714989 by dawehner, jibran: Views which load the same entity type as...

Issue #2714989 by dawehner, jibran: Views which load the same entity type as entity and non default revision cause fatal in Sql::getCacheTags()
parent 2bef9564
......@@ -5,6 +5,7 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\Core\Database\DatabaseExceptionWrapper;
......@@ -13,6 +14,7 @@
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Views query plugin for an SQL query.
......@@ -106,6 +108,40 @@ class Sql extends QueryPluginBase {
*/
protected $noDistinct;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a Sql object.
*
* @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\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
......@@ -1482,63 +1518,97 @@ public function loadEntities(&$results) {
foreach ($entity_information as $info) {
$entity_type = $info['entity_type'];
if (!isset($entity_types[$entity_type])) {
$entity_types[$entity_type] = \Drupal::entityManager()->getDefinition($entity_type);
$entity_types[$entity_type] = $this->entityTypeManager->getDefinition($entity_type);
}
}
// Assemble a list of entities to load.
$ids_by_type = array();
$entity_ids_by_type = [];
$revision_ids_by_type = [];
foreach ($entity_information as $info) {
$relationship_id = $info['relationship_id'];
$entity_type = $info['entity_type'];
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_info */
$entity_info = $entity_types[$entity_type];
$id_key = !$info['revision'] ? $entity_info->getKey('id') : $entity_info->getKey('revision');
$revision = $info['revision'];
$id_key = !$revision ? $entity_info->getKey('id') : $entity_info->getKey('revision');
$id_alias = $this->getFieldAlias($info['alias'], $id_key);
foreach ($results as $index => $result) {
// Store the entity id if it was found.
if (isset($result->{$id_alias}) && $result->{$id_alias} != '') {
$ids_by_type[$entity_type][$index][$relationship_id] = $result->$id_alias;
if ($revision) {
$revision_ids_by_type[$entity_type][$index][$relationship_id] = $result->$id_alias;
}
else {
$entity_ids_by_type[$entity_type][$index][$relationship_id] = $result->$id_alias;
}
}
}
}
// Load all entities and assign them to the correct result row.
foreach ($ids_by_type as $entity_type => $ids) {
foreach ($entity_ids_by_type as $entity_type => $ids) {
$entity_storage = $this->entityTypeManager->getStorage($entity_type);
$flat_ids = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($ids)), FALSE);
// Drupal core currently has no way to load multiple revisions. Sad.
if (isset($entity_information[$entity_type]['revision']) && $entity_information[$entity_type]['revision'] === TRUE) {
$entities = array();
foreach ($flat_ids as $revision_id) {
$entity = entity_revision_load($entity_type, $revision_id);
if ($entity) {
$entities[$revision_id] = $entity;
}
$entities = $entity_storage->loadMultiple(array_unique($flat_ids));
$results = $this->assignEntitiesToResult($ids, $entities, $results);
}
// Now load all revisions.
foreach ($revision_ids_by_type as $entity_type => $revision_ids) {
$entity_storage = $this->entityTypeManager->getStorage($entity_type);
$entities = [];
foreach ($revision_ids as $index => $revision_id_by_relationship) {
foreach ($revision_id_by_relationship as $revision => $revision_id) {
// Drupal core currently has no way to load multiple revisions.
$entity = $entity_storage->loadRevision($revision_id);
$entities[$revision_id] = $entity;
}
}
else {
$entities = entity_load_multiple($entity_type, $flat_ids);
}
foreach ($ids as $index => $relationships) {
foreach ($relationships as $relationship_id => $entity_id) {
if (isset($entities[$entity_id])) {
$entity = $entities[$entity_id];
}
else {
$entity = NULL;
}
$results = $this->assignEntitiesToResult($revision_ids, $entities, $results);
}
}
if ($relationship_id == 'none') {
$results[$index]->_entity = $entity;
}
else {
$results[$index]->_relationship_entities[$relationship_id] = $entity;
}
/**
* Sets entities onto the view result row objects.
*
* This method takes into account the relationship in which the entity was
* needed in the first place.
*
* @param mixed[][] $ids
* A two dimensional array of identifiers (entity ID / revision ID) keyed by
* relationship.
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entities keyed by their identified (entity ID / revision ID).
* @param \Drupal\views\ResultRow[] $results
* The entire views result.
*
* @return \Drupal\views\ResultRow[]
* The changed views results.
*/
protected function assignEntitiesToResult($ids, array $entities, array $results) {
foreach ($ids as $index => $relationships) {
foreach ($relationships as $relationship_id => $id) {
if (isset($entities[$id])) {
$entity = $entities[$id];
}
else {
$entity = NULL;
}
if ($relationship_id == 'none') {
$results[$index]->_entity = $entity;
}
else {
$results[$index]->_relationship_entities[$relationship_id] = $entity;
}
}
}
return $results;
}
/**
......
langcode: en
status: true
dependencies:
module:
- node
- user
id: base_and_revision
label: base_and_revision
module: views
description: ''
tag: ''
base_table: node_field_revision
base_field: vid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'view all revisions'
cache:
type: none
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
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: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ‹‹
next: ››
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
nid:
id: nid
table: node_field_revision
field: nid
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: nid
plugin_id: field
vid:
id: vid
table: node_field_revision
field: vid
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: vid
plugin_id: field
vid_1:
id: vid_1
table: node_field_data
field: vid
relationship: nid
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: vid
plugin_id: field
filters:
vid:
id: vid
table: node_field_revision
field: vid
relationship: none
group_type: group
admin_label: ''
operator: '='
value:
min: ''
max: ''
value: '3'
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: node
entity_field: vid
plugin_id: numeric
sorts: { }
header: { }
footer: { }
empty: { }
relationships:
nid:
id: nid
table: node_field_revision
field: nid
relationship: none
group_type: group
admin_label: Node
required: false
entity_type: node
entity_field: nid
plugin_id: standard
arguments: { }
display_extenders: { }
rendering_language: en
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
<?php
namespace Drupal\Tests\views\Kernel\Plugin;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Views;
/**
* Tests the loading of entities and entity revisions.
*
* @group views
*
* @see \Drupal\views\Plugin\views\query\Sql
*/
class SqlEntityLoadingTest extends ViewsKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'user'];
/**
* {@inheritdoc}
*/
public static $testViews = ['base_and_revision'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installSchema('node', 'node_access');
}
public function testViewWithNonDefaultForwardRevision() {
$node_type = NodeType::create([
'type' => 'page',
]);
$node_type->save();
$node = Node::create([
'type' => 'page',
'title' => 'test title',
]);
$node->save();
// Creates the first revision, which is set as default.
$revision = clone $node;
$revision->setNewRevision(TRUE);
$revision->isDefaultRevision(TRUE);
$revision->save();
// Creates the second revision, which is not set as default.
$revision2 = clone $node;
$revision2->setNewRevision(TRUE);
$revision2->isDefaultRevision(FALSE);
$revision2->save();
$view = Views::getView('base_and_revision');
$view->execute();
$expected = [
[
'nid' => $node->id(),
// The default revision ID.
'vid_1' => $revision->getRevisionId(),
// THe latest revision ID.
'vid' => $revision2->getRevisionId(),
],
];
$this->assertIdenticalResultset($view, $expected, [
'node_field_data_node_field_revision_nid' => 'nid',
'vid_1' => 'vid_1',
'vid' => 'vid',
]);
}
}
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