Update to Drupal 8.0.3. For more information, see https://www.drupal.org/drupal-8.0.3-release-notes

This commit is contained in:
Pantheon Automation 2016-02-03 14:56:31 -08:00 committed by Greg Anderson
parent 10f9f7fbde
commit 9db4fae9a7
202 changed files with 3806 additions and 760 deletions

View file

@ -12,6 +12,8 @@ use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
@ -161,7 +163,15 @@ class ViewAjaxController implements ContainerInjectionInterface {
// Overwrite the destination.
// @see the redirect.destination service.
$origin_destination = $path;
$query = UrlHelper::buildQuery($request->query->all());
// Remove some special parameters you never want to have part of the
// destination query.
$used_query_parameters = $request->query->all();
// @todo Remove this parsing once these are removed from the request in
// https://www.drupal.org/node/2504709.
unset($used_query_parameters[FormBuilderInterface::AJAX_FORM_REQUEST], $used_query_parameters[MainContentViewSubscriber::WRAPPER_FORMAT], $used_query_parameters['ajax_page_state']);
$query = UrlHelper::buildQuery($used_query_parameters);
if ($query != '') {
$origin_destination .= '?' . $query;
}

View file

@ -236,7 +236,7 @@ class ManyToOneHelper {
$join = $this->getJoin();
$join->type = 'LEFT';
$join->extra = array();
$join->extra_type = 'OR';
$join->extraOperator = 'OR';
foreach ($this->handler->value as $value) {
$join->extra[] = array(
'field' => $this->handler->realField,
@ -311,10 +311,21 @@ class ManyToOneHelper {
$placeholder = $this->placeholder();
if (count($this->handler->value) > 1) {
$placeholder .= '[]';
$this->handler->query->addWhereExpression(0, "$field $operator($placeholder)", array($placeholder => $value));
if ($operator == 'IS NULL') {
$this->handler->query->addWhereExpression(0, "$field $operator");
}
else {
$this->handler->query->addWhereExpression(0, "$field $operator($placeholder)", array($placeholder => $value));
}
}
else {
$this->handler->query->addWhereExpression(0, "$field $operator $placeholder", array($placeholder => $value));
if ($operator == 'IS NULL') {
$this->handler->query->addWhereExpression(0, "$field $operator");
}
else {
$this->handler->query->addWhereExpression(0, "$field $operator $placeholder", array($placeholder => $value));
}
}
}
}

View file

@ -2335,13 +2335,17 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
'#cache_properties' => ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'],
];
// When something passes $cache = FALSE, they're asking us not to create our
// own render cache for it. However, we still need to include certain pieces
// of cacheability metadata (e.g.: cache contexts), so they can bubble up.
// Thus, we add the cacheability metadata first, then modify / remove the
// cache keys depending on the $cache argument.
$this->applyDisplayCachablityMetadata($this->view->element);
if ($cache) {
$this->view->element['#cache'] += ['keys' => []];
// Places like \Drupal\views\ViewExecutable::setCurrentPage() set up an
// additional cache context.
$this->view->element['#cache']['keys'] = array_merge(['views', 'display', $this->view->element['#name'], $this->view->element['#display_id']], $this->view->element['#cache']['keys']);
$this->applyDisplayCachablityMetadata($this->view->element);
}
else {
// Remove the cache keys, to ensure render caching is not triggered. We

View file

@ -138,7 +138,8 @@ class EntityReference extends DisplayPluginBase {
foreach ($style_options['options']['search_fields'] as $field_id) {
if (!empty($field_id)) {
// Get the table and field names for the checked field.
$field_alias = $this->view->query->addField($this->view->field[$field_id]->table, $field_id);
$field_handler = $this->view->field[$field_id];
$field_alias = $this->view->query->addField($field_handler->table, $field_handler->realField);
$field = $this->view->query->fields[$field_alias];
// Add an OR condition for the field.
$conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE');

View file

@ -22,6 +22,7 @@ use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\views\FieldAPIHandlerTrait;
use Drupal\views\Entity\Render\EntityFieldRenderer;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
@ -820,9 +821,10 @@ class Field extends FieldPluginBase implements CacheableDependencyInterface, Mul
'settings' => $this->options['settings'],
'label' => 'hidden',
];
$build_list = $this->createEntityForGroupBy($this->getEntity($values), $values)
->{$this->definition['field_name']}
->view($display);
// Some bundles might not have a specific field, in which case the faked
// entity doesn't have it either.
$entity = $this->createEntityForGroupBy($this->getEntity($values), $values);
$build_list = isset($entity->{$this->definition['field_name']}) ? $entity->{$this->definition['field_name']}->view($display) : NULL;
}
if (!$build_list) {
@ -932,8 +934,10 @@ class Field extends FieldPluginBase implements CacheableDependencyInterface, Mul
if (is_object($raw)) {
$property = $raw->get($id);
if (!empty($property)) {
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = Xss::filterAdmin($property->getValue());
// Check if TypedDataInterface is implemented so we know how to render
// the item as a string.
if (!empty($property) && $property instanceof TypedDataInterface) {
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = Xss::filterAdmin($property->getString());
}
else {
// Make sure that empty values are replaced as well.
@ -1002,8 +1006,17 @@ class Field extends FieldPluginBase implements CacheableDependencyInterface, Mul
* {@inheritdoc}
*/
public function getValue(ResultRow $values, $field = NULL) {
$entity = $this->getEntity($values);
// Some bundles might not have a specific field, in which case the entity
// (potentially a fake one) doesn't have it either.
/** @var \Drupal\Core\Field\FieldItemListInterface $field_item_list */
$field_item_list = $this->getEntity($values)->{$this->definition['field_name']};
$field_item_list = isset($entity->{$this->definition['field_name']}) ? $entity->{$this->definition['field_name']} : NULL;
if (!isset($field_item_list)) {
// There isn't anything we can do without a valid field.
return NULL;
}
$field_item_definition = $field_item_list->getFieldDefinition();
if ($field_item_definition->getFieldStorageDefinition()->getCardinality() == 1) {

View file

@ -482,9 +482,17 @@ abstract class StylePluginBase extends PluginBase {
* grouping.
*
* @param $sets
* Array containing the grouping sets to render.
* An array keyed by group content containing the grouping sets to render.
* Each set contains the following associative array:
* - group: The group content.
* - level: The hierarchical level of the grouping.
* - rows: The result rows to be rendered in this group..
* @param $level
* Integer indicating the hierarchical level of the grouping.
* (deprecated) This is no longer used and will be removed in Drupal 9. The
* 'level' key in $sets is used to indicate the hierarchical level of the
* grouping.
*
* @todo Remove the $level parameter in https://www.drupal.org/node/2633890.
*
* @return string
* Rendered output of given grouping sets.
@ -493,16 +501,16 @@ abstract class StylePluginBase extends PluginBase {
$output = array();
$theme_functions = $this->view->buildThemeFunctions($this->groupingTheme);
foreach ($sets as $set) {
$level = isset($set['level']) ? $set['level'] : 0;
$row = reset($set['rows']);
// Render as a grouping set.
if (is_array($row) && isset($row['group'])) {
$output[] = array(
$single_output = array(
'#theme' => $theme_functions,
'#view' => $this->view,
'#grouping' => $this->options['grouping'][$level],
'#grouping_level' => $level,
'#rows' => $set['rows'],
'#title' => $set['group'],
);
}
// Render as a record set.
@ -515,10 +523,11 @@ abstract class StylePluginBase extends PluginBase {
}
$single_output = $this->renderRowGroup($set['rows']);
$single_output['#grouping_level'] = $level;
$single_output['#title'] = $set['group'];
$output[] = $single_output;
}
$single_output['#grouping_level'] = $level;
$single_output['#title'] = $set['group'];
$output[] = $single_output;
}
unset($this->view->row_index);
return $output;
@ -546,9 +555,11 @@ abstract class StylePluginBase extends PluginBase {
* array(
* 'grouping_field_1:grouping_1' => array(
* 'group' => 'grouping_field_1:content_1',
* 'level' => 0,
* 'rows' => array(
* 'grouping_field_2:grouping_a' => array(
* 'group' => 'grouping_field_2:content_a',
* 'level' => 1,
* 'rows' => array(
* $row_index_1 => $row_1,
* $row_index_2 => $row_2,
@ -580,7 +591,7 @@ abstract class StylePluginBase extends PluginBase {
// hierarchically positioned set where the current row belongs to.
// While iterating, parent groups, that do not exist yet, are added.
$set = &$sets;
foreach ($groupings as $info) {
foreach ($groupings as $level => $info) {
$field = $info['field'];
$rendered = isset($info['rendered']) ? $info['rendered'] : $group_rendered;
$rendered_strip = isset($info['rendered_strip']) ? $info['rendered_strip'] : FALSE;
@ -613,6 +624,7 @@ abstract class StylePluginBase extends PluginBase {
// Create the group if it does not exist yet.
if (empty($set[$grouping])) {
$set[$grouping]['group'] = $group_content;
$set[$grouping]['level'] = $level;
$set[$grouping]['rows'] = array();
}

View file

@ -84,4 +84,15 @@ class CacheWebTest extends PluginTestBase {
$this->assertCacheTags($cache_tags);
}
/**
* Tests that a display without caching still contains the cache metadata.
*/
public function testDisplayWithoutCacheStillBubblesMetadata() {
$view = Views::getView('test_display');
$uncached_block = $view->buildRenderable('block_1', [], FALSE);
$cached_block = $view->buildRenderable('block_1', [], TRUE);
$this->assertEqual($uncached_block['#cache']['contexts'], $cached_block['#cache']['contexts'], 'Cache contexts are the same when you render the view cached and uncached.');
}
}

View file

@ -0,0 +1,134 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Plugin\DisplayEntityReferenceTest.
*/
namespace Drupal\views\Tests\Plugin;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Views;
/**
* Tests the entity reference display plugin.
*
* @group views
*
* @see \Drupal\views\Plugin\views\display\EntityReference
*/
class DisplayEntityReferenceTest extends PluginTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_display_entity_reference');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_test', 'field', 'views_ui');
/**
* The used field name in the test.
*
* @var string
*/
protected $fieldName;
/**
* The field storage.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field config.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(array('administer views')));
// Create the text field.
$this->fieldName = 'field_test_entity_ref_display';
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'entity_test',
'type' => 'text',
]);
$this->fieldStorage->save();
// Create an instance of the text field on the content type.
$this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
]);
$this->field->save();
// Create some entities to search. Add a common string to the name and
// the text field in two entities so we can test that we can search in both.
for ($i = 0; $i < 5; $i++) {
EntityTest::create([
'bundle' => 'entity_test',
'name' => 'name' . $i,
$this->fieldName => 'text',
])->save();
EntityTest::create([
'bundle' => 'entity_test',
'name' => 'name',
$this->fieldName => 'text' . $i,
])->save();
}
}
/**
* Tests the entity reference display plugin.
*/
public function testEntityReferenceDisplay() {
// Add the new field to the fields.
$this->drupalPostForm('admin/structure/views/nojs/add-handler/test_display_entity_reference/default/field', ['name[entity_test__' . $this->fieldName . '.' . $this->fieldName . ']' => TRUE], t('Add and configure fields'));
$this->drupalPostForm(NULL, [], t('Apply'));
// Test that the right fields are shown on the display settings form.
$this->drupalGet('admin/structure/views/nojs/display/test_display_entity_reference/entity_reference_1/style_options');
$this->assertText('Test entity: Name');
$this->assertText('Test entity: ' . $this->field->label());
// Add the new field to the search fields.
$this->drupalPostForm(NULL, ['style_options[search_fields][' . $this->fieldName . ']' => $this->fieldName], t('Apply'));
$this->drupalPostForm(NULL, [], t('Save'));
$view = Views::getView('test_display_entity_reference');
$view->setDisplay('entity_reference_1');
// Add the required settings to test a search operation.
$options = [
'match' => '1',
'match_operator' => 'CONTAINS',
'limit' => 0,
'ids' => NULL,
];
$view->display_handler->setOption('entity_reference_options', $options);
$this->executeView($view);
// Test that we have searched in both fields.
$this->assertEqual(count($view->result), 2, 'Search returned two rows');
}
}

View file

@ -149,8 +149,10 @@ class StyleTest extends ViewTestBase {
$expected = array();
$expected['Job: Singer'] = array();
$expected['Job: Singer']['group'] = 'Job: Singer';
$expected['Job: Singer']['level'] = 0;
$expected['Job: Singer']['rows']['Age: 25'] = array();
$expected['Job: Singer']['rows']['Age: 25']['group'] = 'Age: 25';
$expected['Job: Singer']['rows']['Age: 25']['level'] = 1;
$expected['Job: Singer']['rows']['Age: 25']['rows'][0] = new ResultRow(['index' => 0]);
$expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_data_name = 'John';
$expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_data_job = 'Singer';
@ -158,6 +160,7 @@ class StyleTest extends ViewTestBase {
$expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_data_id = '1';
$expected['Job: Singer']['rows']['Age: 27'] = array();
$expected['Job: Singer']['rows']['Age: 27']['group'] = 'Age: 27';
$expected['Job: Singer']['rows']['Age: 27']['level'] = 1;
$expected['Job: Singer']['rows']['Age: 27']['rows'][1] = new ResultRow(['index' => 1]);
$expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_data_name = 'George';
$expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_data_job = 'Singer';
@ -165,8 +168,10 @@ class StyleTest extends ViewTestBase {
$expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_data_id = '2';
$expected['Job: Drummer'] = array();
$expected['Job: Drummer']['group'] = 'Job: Drummer';
$expected['Job: Drummer']['level'] = 0;
$expected['Job: Drummer']['rows']['Age: 28'] = array();
$expected['Job: Drummer']['rows']['Age: 28']['group'] = 'Age: 28';
$expected['Job: Drummer']['rows']['Age: 28']['level'] = 1;
$expected['Job: Drummer']['rows']['Age: 28']['rows'][2] = new ResultRow(['index' => 2]);
$expected['Job: Drummer']['rows']['Age: 28']['rows'][2]->views_test_data_name = 'Ringo';
$expected['Job: Drummer']['rows']['Age: 28']['rows'][2]->views_test_data_job = 'Drummer';

View file

@ -25,7 +25,7 @@ class QueryGroupByTest extends ViewKernelTestBase {
*
* @var array
*/
public static $testViews = array('test_group_by_in_filters', 'test_aggregate_count', 'test_group_by_count', 'test_group_by_count_multicardinality');
public static $testViews = array('test_group_by_in_filters', 'test_aggregate_count', 'test_group_by_count', 'test_group_by_count_multicardinality', 'test_group_by_field_not_within_bundle');
/**
* Modules to enable.
@ -292,4 +292,48 @@ class QueryGroupByTest extends ViewKernelTestBase {
$this->assertEqual('6', $view->getStyle()->getField(5, 'field_test'));
}
/**
* Tests groupby with a field not existing on some bundle.
*/
public function testGroupByWithFieldsNotExistingOnBundle() {
$field_storage = FieldStorageConfig::create([
'type' => 'integer',
'field_name' => 'field_test',
'cardinality' => 4,
'entity_type' => 'entity_test_mul',
]);
$field_storage->save();
$field = FieldConfig::create([
'field_name' => 'field_test',
'entity_type' => 'entity_test_mul',
'bundle' => 'entity_test_mul',
]);
$field->save();
$entities = [];
$entity = EntityTestMul::create([
'field_test' => [1],
'type' => 'entity_test_mul',
]);
$entity->save();
$entities[] = $entity;
$entity = EntityTestMul::create([
'type' => 'entity_test_mul2',
]);
$entity->save();
$entities[] = $entity;
$view = Views::getView('test_group_by_field_not_within_bundle');
$this->executeView($view);
$this->assertEqual(2, count($view->result));
// The first result is coming from entity_test_mul2, so no field could be
// rendered.
$this->assertEqual('', $view->getStyle()->getField(0, 'field_test'));
// The second result is coming from entity_test_mul, so its field value
// could be rendered.
$this->assertEqual('1', $view->getStyle()->getField(1, 'field_test'));
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\RenderCacheWebTest.
*/
namespace Drupal\views\Tests;
use Drupal\node\Entity\Node;
/**
* Tests render caching of blocks provided by views.
*
* @group views
*/
class RenderCacheWebTest extends ViewTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'block'];
/**
* {@inheritdoc}
*/
public static $testViews = ['node_id_argument'];
/**
* The created nodes.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$node_type = $this->drupalCreateContentType(['type' => 'test_type']);
$node = Node::create([
'title' => 'test title 1',
'type' => $node_type->id(),
]);
$node->save();
$this->nodes[] = $node;
$node = Node::create([
'title' => 'test title 2',
'type' => $node_type->id(),
]);
$node->save();
$this->nodes[] = $node;
$this->placeBlock('views_block:node_id_argument-block_1', ['region' => 'header']);
}
/**
* Tests rendering caching of a views block with arguments.
*/
public function testEmptyView() {
$this->drupalGet('<front>');
$this->assertEqual([], $this->cssSelect('div.region-header div.views-field-title'));
$this->drupalGet($this->nodes[0]->toUrl());
$result = (string) $this->cssSelect('div.region-header div.views-field-title')[0]->span;
$this->assertEqual('test title 1', $result);
$this->drupalGet($this->nodes[1]->toUrl());
$result = (string) $this->cssSelect('div.region-header div.views-field-title')[0]->span;
$this->assertEqual('test title 2', $result);
$this->drupalGet($this->nodes[0]->toUrl());
$result = (string) $this->cssSelect('div.region-header div.views-field-title')[0]->span;
$this->assertEqual('test title 1', $result);
}
}

View file

@ -77,4 +77,23 @@ class TokenReplaceTest extends ViewKernelTestBase {
}
}
/**
* Tests core token replacements generated from a view without results.
*/
function testTokenReplacementNoResults() {
$token_handler = \Drupal::token();
$view = Views::getView('test_tokens');
$view->setDisplay('page_2');
$this->executeView($view);
$expected = array(
'[view:page-count]' => '1',
);
foreach ($expected as $token => $expected_output) {
$output = $token_handler->replace($token, array('view' => $view));
$this->assertIdentical($output, $expected_output, format_string('Token %token replaced correctly.', array('%token' => $token)));
}
}
}