Commit ae2ee037 authored by catch's avatar catch
Browse files

Issue #2632068 by bojanz, dawehner, czigor, mglaman: Rendered entity views field handler

parent 6325f60a
......@@ -176,6 +176,13 @@ views.field.language:
type: boolean
label: 'Display in native language'
views.field.rendered_entity:
type: views_field
label: 'Rendered entity'
mapping:
view_mode:
type: string
label: 'View mode'
views.field.entity_link:
type: views_field
......
......@@ -182,6 +182,16 @@ public function getViewsData() {
);
}
if ($this->entityType->hasViewBuilderClass()) {
$data[$base_table]['rendered_entity'] = [
'field' => [
'title' => $this->t('Rendered entity'),
'help' => $this->t('Renders an entity in a view mode.'),
'id' => 'rendered_entity',
],
];
}
// Setup relations to the revisions/property data.
if ($data_table) {
$data[$base_table]['table']['join'][$data_table] = [
......
<?php
/**
* @file
* Contains \Drupal\views\Plugin\views\field\RenderedEntity.
*/
namespace Drupal\views\Plugin\views\field;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Cache\Cache;
/**
* Provides a field handler which renders an entity in a certain view mode.
*
* @ingroup views_field_handlers
*
* @ViewsField("rendered_entity")
*/
class RenderedEntity extends FieldPluginBase implements CacheableDependencyInterface {
use EntityTranslationRenderTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new RenderedEntity 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 array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function usesGroupBy() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function defineOptions() {
$options = parent::defineOptions();
$options['view_mode'] = ['default' => 'default'];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['view_mode'] = [
'#type' => 'select',
'#options' => $this->entityManager->getViewModeOptions($this->getEntityTypeId()),
'#title' => $this->t('View mode'),
'#default_value' => $this->options['view_mode'],
];
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$entity = $this->getEntityTranslation($this->getEntity($values), $values);
$build = [];
if (isset($entity)) {
$access = $entity->access('view', NULL, TRUE);
$build['#access'] = $access;
if ($access->isAllowed()) {
$view_builder = $this->entityManager->getViewBuilder($this->getEntityTypeId());
$build += $view_builder->view($entity, $this->options['view_mode']);
}
}
return $build;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
$view_display_storage = $this->entityManager->getStorage('entity_view_display');
$view_displays = $view_display_storage->loadMultiple($view_display_storage
->getQuery()
->condition('targetEntityType', $this->getEntityTypeId())
->execute());
$tags = [];
foreach ($view_displays as $view_display) {
$tags = array_merge($tags, $view_display->getCacheTags());
}
return $tags;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
}
/**
* {@inheritdoc}
*/
public function query() {
// We purposefully do not call parent::query() because we do not want the
// default query behavior for Views fields. Instead, let the entity
// translation renderer provide the correct query behavior.
if ($this->languageManager->isMultilingual()) {
$this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
}
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->getEntityType();
}
/**
* {@inheritdoc}
*/
protected function getEntityManager() {
return $this->entityManager;
}
/**
* {@inheritdoc}
*/
protected function getLanguageManager() {
return $this->languageManager;
}
/**
* {@inheritdoc}
*/
protected function getView() {
return $this->view;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$view_mode = $this->entityManager
->getStorage('entity_view_mode')
->load($this->getEntityTypeId() . '.' . $this->options['view_mode']);
if ($view_mode) {
$dependencies[$view_mode->getConfigDependencyKey()][] = $view_mode->getConfigDependencyName();
}
return $dependencies;
}
}
<?php
/**
* @file
* Contains \Drupal\views\Tests\Handler\FieldRenderedEntityTest.
*/
namespace Drupal\views\Tests\Handler;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\views\Entity\View;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
use Drupal\Core\Entity\Entity\EntityViewMode;
/**
* Tests the core Drupal\views\Plugin\views\field\RenderedEntity handler.
*
* @group views
*/
class FieldRenderedEntityTest extends ViewKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['entity_test', 'field'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_field_entity_test_rendered'];
/**
* The logged in user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
protected function setUpFixtures() {
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
$this->installConfig(['entity_test']);
EntityViewMode::create([
'id' => 'entity_test.foobar',
'targetEntityType' => 'entity_test',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'My view mode',
])->save();
$display = EntityViewDisplay::create([
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'foobar',
'label' => 'My view mode',
'status' => TRUE,
]);
$display->save();
$field_storage = FieldStorageConfig::create([
'field_name' => 'test_field',
'entity_type' => 'entity_test',
'type' => 'string',
]);
$field_storage->save();
$field_config = FieldConfig::create([
'field_name' => 'test_field',
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
]);
$field_config->save();
// Create some test entities.
for ($i = 1; $i <= 3; $i++) {
EntityTest::create([
'name' => "Article title $i",
'test_field' => "Test $i",
])->save();
}
$this->user = User::create([
'name' => 'test user',
]);
$this->user->save();
parent::setUpFixtures();
}
/**
* Tests the default rendered entity output.
*/
public function testRenderedEntityWithoutField() {
\Drupal::currentUser()->setAccount($this->user);
EntityViewDisplay::load('entity_test.entity_test.foobar')
->removeComponent('test_field')
->save();
$view = Views::getView('test_field_entity_test_rendered');
$build = [
'#type' => 'view',
'#name' => 'test_field_entity_test_rendered',
'#view' => $view,
'#display_id' => 'default',
];
$renderer = \Drupal::service('renderer');
$renderer->renderPlain($build);
for ($i = 1; $i <= 3; $i++) {
$view_field = $view->style_plugin->getField($i - 1, 'rendered_entity');
$search_result = strpos($view_field, "Test $i") !== FALSE;
$this->assertFalse($search_result, "The text 'Test $i' not found in the view.");
}
$this->assertConfigDependencies($view->storage);
$this->assertCacheabilityMetadata($build);
}
/**
* Ensures that the expected cacheability metadata is applied.
*
* @param array $build
* The render array
*/
protected function assertCacheabilityMetadata($build) {
$this->assertEqual([
'config:core.entity_view_display.entity_test.entity_test.foobar',
'config:views.view.test_field_entity_test_rendered',
'entity_test:1',
'entity_test:2',
'entity_test:3',
'entity_test_list',
'entity_test_view',
], $build['#cache']['tags']);
$this->assertEqual([
'entity_test_view_grants',
'languages:language_interface',
'theme',
'url.query_args',
'user.permissions',
], $build['#cache']['contexts']);
}
/**
* Ensures that the config dependencies are calculated the right way.
*
* @param \Drupal\views\Entity\View $storage
*/
protected function assertConfigDependencies(View $storage) {
$storage->calculateDependencies();
$this->assertEqual([
'config' => ['core.entity_view_mode.entity_test.foobar'],
'module' => ['entity_test'],
], $storage->getDependencies());
}
/**
* Tests the rendered entity output with the test field configured to show.
*/
public function testRenderedEntityWithField() {
\Drupal::currentUser()->setAccount($this->user);
// Show the test_field on the entity_test.entity_test.foobar view display.
EntityViewDisplay::load('entity_test.entity_test.foobar')->setComponent('test_field', ['type' => 'string', 'label' => 'above'])->save();
$view = Views::getView('test_field_entity_test_rendered');
$build = [
'#type' => 'view',
'#name' => 'test_field_entity_test_rendered',
'#view' => $view,
'#display_id' => 'default',
];
$renderer = \Drupal::service('renderer');
$renderer->renderPlain($build);
for ($i = 1; $i <= 3; $i++) {
$view_field = $view->style_plugin->getField($i - 1, 'rendered_entity');
$search_result = strpos($view_field, "Test $i") !== FALSE;
$this->assertTrue($search_result, "The text 'Test $i' found in the view.");
}
$this->assertConfigDependencies($view->storage);
$this->assertCacheabilityMetadata($build);
}
}
langcode: en
status: true
dependencies:
module:
- entity_test
- user
id: test_field_entity_test_rendered
label: 'Test Rendered entity test'
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
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: full
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: ' Previous'
next: 'Next ›'
first: '« First'
last: 'Last »'
quantity: 9
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:
rendered_entity:
id: rendered_entity
table: entity_test
field: rendered_entity
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
view_mode: foobar
entity_type: entity_test
plugin_id: rendered_entity
filters: { }
sorts:
id:
id: id
table: entity_test
field: id
relationship: none
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
entity_type: entity_test