Commit 28d5e269 authored by webchick's avatar webchick

Issue #2134887 by zwischenzug, yched, plopesc: Move field_view_field() /...

Issue #2134887 by zwischenzug, yched, plopesc: Move field_view_field() / field_view_value() to methods.
parent 5715be72
......@@ -9,6 +9,8 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\TypedData\TranslatableInterface;
......@@ -278,6 +280,67 @@ public function resetCache(array $entities = NULL) {
}
/**
* {@inheritdoc}
*/
public function viewField(FieldItemListInterface $items, $display_options = array()) {
$output = array();
$entity = $items->getEntity();
$field_name = $items->getFieldDefinition()->getName();
// Get the display object.
if (is_string($display_options)) {
$view_mode = $display_options;
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
foreach ($entity as $name => $items) {
if ($name != $field_name) {
$display->removeComponent($name);
}
}
}
else {
$view_mode = '_custom';
$display = entity_create('entity_view_display', array(
'targetEntityType' => $entity->getEntityTypeId(),
'bundle' => $entity->bundle(),
'mode' => $view_mode,
'status' => TRUE,
));
$display->setComponent($field_name, $display_options);
}
$build = $display->build($entity);
if (isset($build[$field_name])) {
$output = $build[$field_name];
}
return $output;
}
/**
* {@inheritdoc}
*/
public function viewFieldItem(FieldItemInterface $item, $display = array()) {
$entity = $item->getEntity();
$field_name = $item->getFieldDefinition()->getName();
// Clone the entity since we are going to modify field values.
$clone = clone $entity;
// Push the item as the single value for the field, and defer to viewField()
// to build the render array for the whole list.
$clone->{$field_name}->setValue(array($item->getValue()));
$elements = $this->viewField($clone->{$field_name}, $display);
// Extract the part of the render array we need.
$output = isset($elements[0]) ? $elements[0] : array();
if (isset($elements['#access'])) {
$output['#access'] = $elements['#access'];
}
return $output;
}
/*
* Returns TRUE if the view mode is cacheable.
*
* @param string $view_mode
......
......@@ -7,6 +7,9 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Defines a common interface for entity view controller classes.
*/
......@@ -84,4 +87,66 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
*/
public function resetCache(array $entities = NULL);
/**
* Returns a renderable array for the value of a single field in an entity.
*
* The resulting output is a fully themed field with label and multiple
* values.
*
* This function can be used by third-party modules that need to output an
* isolated field.
* - Do not use inside node (or any other entity) templates; use
* render($content[FIELD_NAME]) instead.
* - Do not use to display all fields in an entity; use
* field_attach_prepare_view() and field_attach_view() instead.
* - The FieldItemInterface::view() method can be used to output a single
* formatted field value, without label or wrapping field markup.
*
* The function takes care of invoking the prepare_view steps. It also
* respects field access permissions.
*
* @param \Drupal\Core\Field\FieldItemListInterface $items
* FieldItemList containing the values to be displayed.
* @param array $display_options
* Can be either:
* - The name of a view mode. The field will be displayed according to the
* display settings specified for this view mode in the $instance
* definition for the field in the entity's bundle. If no display settings
* are found for the view mode, the settings for the 'default' view mode
* will be used.
* - An array of display options. The following key/value pairs are allowed:
* - label: (string) Position of the label. The default 'field' theme
* implementation supports the values 'inline', 'above' and 'hidden'.
* Defaults to 'above'.
* - type: (string) The formatter to use. Defaults to the
* 'default_formatter' for the field type. The default formatter will
* also be used if the requested formatter is not available.
* - settings: (array) Settings specific to the formatter. Defaults to the
* formatter's default settings.
* - weight: (float) The weight to assign to the renderable element.
* Defaults to 0.
*
* @return array
* A renderable array for the field values.
*
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewFieldItem()
*/
public function viewField(FieldItemListInterface $items, $display_options = array());
/**
* Returns a renderable array for a single field item.
*
* @param \Drupal\Core\Field\FieldItemInterface $item
* FieldItem to be displayed.
* @param array $display_options
* Can be either the name of a view mode, or an array of display settings.
* See EntityViewBuilderInterface::viewField() for more information.
*
* @return array
* A renderable array for the field item.
*
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewField()
*/
public function viewFieldItem(FieldItemInterface $item, $display_options = array());
}
......@@ -193,6 +193,14 @@ public function onChange($property_name) {
}
}
/**
* {@inheritdoc}
*/
public function view($display_options = array()) {
$view_builder = \Drupal::entityManager()->getViewBuilder($this->getEntity()->getEntityTypeId());
return $view_builder->viewFieldItem($this, $display_options);
}
/**
* {@inheritdoc}
*/
......
......@@ -155,6 +155,22 @@ public function __isset($property_name);
*/
public function __unset($property_name);
/**
* Returns a renderable array for a single field item.
*
* @param array $display_options
* Can be either the name of a view mode, or an array of display settings.
* See EntityViewBuilderInterface::viewField() for more information.
*
* @return array
* A renderable array for the field item.
*
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewField()
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewFieldItem()
* @see \Drupal\Core\Field\FieldItemListInterface::view()
*/
public function view($display_options = array());
/**
* Defines custom presave behavior for field values.
*
......
......@@ -278,4 +278,12 @@ protected function delegateMethod($method) {
}
}
/**
* {@inheritdoc}
*/
public function view($display_options = array()) {
$view_builder = \Drupal::entityManager()->getViewBuilder($this->getEntity()->getEntityTypeId());
return $view_builder->viewField($this, $display_options);
}
}
......@@ -164,4 +164,19 @@ public function delete();
*/
public function deleteRevision();
/**
* Returns a renderable array for the field items.
*
* @param array $display_options
* Can be either the name of a view mode, or an array of display settings.
* See EntityViewBuilderInterface::viewField() for more information.
*
* @return array
* A renderable array for the field values.
*
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewField()
* @see \Drupal\Core\Field\FieldItemInterface::view()
*/
public function view($display_options = array());
}
......@@ -32,9 +32,9 @@ function hook_edit_editor_alter(&$editors) {
* Returns a renderable array for the value of a single field in an entity.
*
* To integrate with in-place field editing when a non-standard render pipeline
* is used (field_view_field() is not sufficient to render back the field
* following in-place editing in the exact way it was displayed originally),
* implement this hook.
* is used (FieldItemListInterface::view() is not sufficient to render back the
* field following in-place editing in the exact way it was displayed
* originally), implement this hook.
*
* Edit module integrates with HTML elements with data-edit-field-id attributes.
* For example:
......@@ -67,12 +67,12 @@ function hook_edit_editor_alter(&$editors) {
* @return
* A renderable array for the field value.
*
* @see field_view_field()
* @see \Drupal\Core\Field\FieldItemListInterface::view()
*/
function hook_edit_render_field(Drupal\Core\Entity\EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
return array(
'#prefix' => '<div class="example-markup">',
'field' => field_view_field($entity, $field_name, $view_mode_id, $langcode),
'field' => $entity->getTranslation($langcode)->get($field_name)->view($view_mode_id),
'#suffix' => '</div>',
);
}
......
......@@ -123,8 +123,9 @@ function edit_preprocess_field(&$variables) {
$entity = $element['#object'];
// Edit module only supports view modes, not dynamically defined "display
// options" (which field_view_field() always names the "_custom" view mode).
// @see field_view_field()
// options" (which \Drupal\Core\Field\FieldItemListInterface::view() always
// names the "_custom" view mode).
// @see \Drupal\Core\Field\FieldItemListInterface::view()
// @see https://drupal.org/node/2120335
if ($element['#view_mode'] === '_custom') {
return;
......
......@@ -267,7 +267,8 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
protected function renderField(EntityInterface $entity, $field_name, $langcode, $view_mode_id) {
$entity_view_mode_ids = array_keys(entity_get_view_modes($entity->getEntityTypeId()));
if (in_array($view_mode_id, $entity_view_mode_ids)) {
$output = field_view_field($entity, $field_name, $view_mode_id, $langcode);
$entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
$output = $entity->get($field_name)->view($view_mode_id);
}
else {
// Each part of a custom (non-Entity Display) view mode ID is separated
......
......@@ -394,7 +394,7 @@ public function testDisplayOptions() {
$display_settings = array(
'label' => 'inline',
);
$build = field_view_field($node, 'body', $display_settings);
$build = $node->body->view($display_settings);
$output = drupal_render($build);
$this->assertFalse(strpos($output, 'data-edit-field-id'), 'data-edit-field-id attribute not added when rendering field using dynamic display options.');
}
......
......@@ -41,10 +41,11 @@ function edit_test_entity_view_alter(&$build, EntityInterface $entity, EntityVie
/**
* Implements hook_edit_render_field().
*/
function edit_test_edit_render_field(Drupal\Core\Entity\EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
function edit_test_edit_render_field(EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
$entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
return array(
'#prefix' => '<div class="edit-test-wrapper">',
'field' => field_view_field($entity, $field_name),
'field' => $entity->get($field_name)->view($view_mode_id),
'#suffix' => '</div>',
);
}
......
......@@ -314,138 +314,6 @@ function _field_filter_xss_display_allowed_tags() {
return '<' . implode('> <', _field_filter_xss_allowed_tags()) . '>';
}
/**
* Returns a renderable array for a single field value.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity containing the field to display. Must at least contain the ID
* key and the field data to display.
* @param $field_name
* The name of the field to display.
* @param $item
* The field item value to display.
* @param $display
* Can be either the name of a view mode, or an array of display settings. See
* field_view_field() for more information.
* @param $langcode
* (Optional) The language of the value in $item. If not provided, the current
* language will be assumed.
*
* @return
* A renderable array for the field value.
*/
function field_view_value(ContentEntityInterface $entity, $field_name, $item, $display = array(), $langcode = NULL) {
$output = array();
if ($entity->hasField($field_name)) {
// Clone the entity since we are going to modify field values.
$clone = clone $entity;
// Apply language fallback.
$clone = \Drupal::entityManager()->getTranslationFromContext($clone, $langcode);
// Push the item as the single value for the field, and defer to
// field_view_field() to build the render array for the whole field.
$clone->{$field_name}->setValue(array($item));
$elements = field_view_field($clone, $field_name, $display, $langcode);
// Extract the part of the render array we need.
$output = isset($elements[0]) ? $elements[0] : array();
if (isset($elements['#access'])) {
$output['#access'] = $elements['#access'];
}
}
return $output;
}
/**
* Returns a renderable array for the value of a single field in an entity.
*
* The resulting output is a fully themed field with label and multiple values.
*
* This function can be used by third-party modules that need to output an
* isolated field.
* - Do not use inside node (or any other entity) templates; use
* render($content[FIELD_NAME]) instead.
* - Do not use to display all fields in an entity; use EntityDisplay::build()
* instead.
* - The field_view_value() function can be used to output a single formatted
* field value, without label or wrapping field markup.
*
* The function takes care of invoking the prepare_view steps. It also respects
* field access permissions.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity containing the field to display.
* @param $field_name
* The name of the field to display.
* @param $display_options
* Can be either:
* - The name of a view mode. The field will be displayed according to the
* display settings specified for this view mode in the $instance
* definition for the field in the entity's bundle. If no display settings
* are found for the view mode, the settings for the 'default' view mode
* will be used.
* - An array of display options. The following key/value pairs are allowed:
* - label: (string) Position of the label. The default 'field' theme
* implementation supports the values 'inline', 'above' and 'hidden'.
* Defaults to 'above'.
* - type: (string) The formatter to use. Defaults to the
* 'default_formatter' for the field type. The default formatter will also
* be used if the requested formatter is not available.
* - settings: (array) Settings specific to the formatter. Defaults to the
* formatter's default settings.
* - weight: (float) The weight to assign to the renderable element.
* Defaults to 0.
* @param $langcode
* (Optional) The language code the field values are to be shown in. The
* site's current language fallback logic will be applied when no values are
* available for the given language code. If no language code is provided the
* current language will be used.
*
* @return
* A renderable array for the field value.
*
* @see field_view_value()
*/
function field_view_field(ContentEntityInterface $entity, $field_name, $display_options = array(), $langcode = NULL) {
$output = array();
// Return nothing if the field doesn't exist.
if (!$entity->hasField($field_name)) {
return $output;
}
// Get the display object.
if (is_string($display_options)) {
$view_mode = $display_options;
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
foreach ($entity as $name => $items) {
if ($name != $field_name) {
$display->removeComponent($name);
}
}
}
else {
$view_mode = '_custom';
$display = entity_create('entity_view_display', array(
'targetEntityType' => $entity->getEntityTypeId(),
'bundle' => $entity->bundle(),
'mode' => $view_mode,
'status' => TRUE,
));
$display->setComponent($field_name, $display_options);
}
$build = $display->build($entity);
if (isset($build[$field_name])) {
$output = $build[$field_name];
}
return $output;
}
/**
* Implements hook_page_build().
*/
......
......@@ -685,9 +685,7 @@ public function getItems($values) {
'views_field' => $this,
'views_row_id' => $this->view->row_index,
);
$langcode = $this->field_langcode($entity);
$render_array = field_view_field($entity, $this->definition['field_name'], $display, $langcode);
$render_array = $entity->get($this->definition['field_name'])->view($display);
$items = array();
if ($this->options['field_api_classes']) {
......@@ -696,10 +694,10 @@ public function getItems($values) {
foreach (element_children($render_array) as $count) {
$items[$count]['rendered'] = $render_array[$count];
// field_view_field() adds an #access property to the render array that
// determines whether or not the current user is allowed to view the
// field in the context of the current entity. We need to respect this
// parameter when we pull out the children of the field array for
// FieldItemListInterface::view() adds an #access property to the render
// array that determines whether or not the current user is allowed to
// view the field in the context of the current entity. We need to respect
// this parameter when we pull out the children of the field array for
// rendering.
if (isset($render_array['#access'])) {
$items[$count]['rendered']['#access'] = $render_array['#access'];
......@@ -727,7 +725,10 @@ public function getItems($values) {
*/
function process_entity(EntityInterface $entity) {
$processed_entity = clone $entity;
$langcode = $this->field_langcode($processed_entity);
$processed_entity = $processed_entity->getTranslation($langcode);
// If we are grouping, copy our group fields into the cloned entity.
// It's possible this will cause some weirdness, but there's only
// so much we can hope to do.
......@@ -752,10 +753,10 @@ function process_entity(EntityInterface $entity) {
if ($data) {
// Now, overwrite the original value with our aggregated value.
// This overwrites it so there is always just one entry.
$processed_entity->getTranslation($langcode)->{$this->definition['field_name']} = array($base_value);
$processed_entity->{$this->definition['field_name']} = array($base_value);
}
else {
$processed_entity->getTranslation($langcode)->{$this->definition['field_name']} = array();
$processed_entity->{$this->definition['field_name']} = array();
}
}
......@@ -766,7 +767,7 @@ function process_entity(EntityInterface $entity) {
// We are supposed to show only certain deltas.
if ($this->limit_values && !empty($processed_entity->{$this->definition['field_name']})) {
$all_values = !empty($processed_entity->getTranslation($langcode)->{$this->definition['field_name']}) ? $processed_entity->getTranslation($langcode)->{$this->definition['field_name']}->getValue() : array();
$all_values = !empty($processed_entity->{$this->definition['field_name']}) ? $processed_entity->{$this->definition['field_name']}->getValue() : array();
if ($this->options['delta_reversed']) {
$all_values = array_reverse($all_values);
}
......@@ -812,7 +813,7 @@ function process_entity(EntityInterface $entity) {
}
}
}
$processed_entity->getTranslation($langcode)->{$this->definition['field_name']} = $new_values;
$processed_entity->{$this->definition['field_name']} = $new_values;
}
return $processed_entity;
......@@ -866,9 +867,9 @@ function field_langcode(EntityInterface $entity) {
// Give the Entity Field API a chance to fallback to a different language
// (or Language::LANGCODE_NOT_SPECIFIED), in case the field has no data
// for the selected language. field_view_field() does this as well, but
// since the returned language code is used before calling it, the
// fallback needs to happen explicitly.
// for the selected language. FieldItemListInterface::view() does this as
// well, but since the returned language code is used before calling it,
// the fallback needs to happen explicitly.
$langcode = $this->entityManager->getTranslationFromContext($entity, $langcode)->language()->id;
return $langcode;
......
......@@ -116,11 +116,13 @@ function setUp() {
}
/**
* Test the field_view_field() function.
* Tests the FieldItemListInterface::view() method.
*/
function testFieldViewField() {
function testFieldItemListView() {
$items = $this->entity->get($this->field_name);
// No display settings: check that default display settings are used.
$output = field_view_field($this->entity, $this->field_name);
$output = $items->view();
$this->content = drupal_render($output);
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('field_test_default');
$setting = $settings['test_formatter_setting'];
......@@ -138,7 +140,7 @@ function testFieldViewField() {
'alter' => TRUE,
),
);
$output = field_view_field($this->entity, $this->field_name, $display);
$output = $items->view($display);
$this->content = drupal_render($output);
$setting = $display['settings']['test_formatter_setting_multiple'];
$this->assertNoText($this->label, 'Label was not displayed.');
......@@ -158,7 +160,7 @@ function testFieldViewField() {
'test_formatter_setting_additional' => $this->randomName(),
),
);
$output = field_view_field($this->entity, $this->field_name, $display);
$output = $items->view($display);
$view = drupal_render($output);
$this->content = $view;
$setting = $display['settings']['test_formatter_setting_additional'];
......@@ -170,7 +172,7 @@ function testFieldViewField() {
// View mode: check that display settings specified in the display object
// are used.
$output = field_view_field($this->entity, $this->field_name, 'teaser');
$output = $items->view('teaser');
$this->content = drupal_render($output);
$setting = $this->display_options['teaser']['settings']['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
......@@ -180,7 +182,7 @@ function testFieldViewField() {
// Unknown view mode: check that display settings for 'default' view mode
// are used.
$output = field_view_field($this->entity, $this->field_name, 'unknown_view_mode');
$output = $items->view('unknown_view_mode');
$this->content = drupal_render($output);
$setting = $this->display_options['default']['settings']['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
......@@ -190,15 +192,15 @@ function testFieldViewField() {
}
/**
* Test the field_view_value() function.
* Tests the FieldItemInterface::view() method.
*/
function testFieldViewValue() {
function testFieldItemView() {
// No display settings: check that default display settings are used.
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('field_test_default');
$setting = $settings['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta]->getValue();
$output = field_view_value($this->entity, $this->field_name, $item);
$item = $this->entity->{$this->field_name}[$delta];
$output = $item->view();
$this->content = drupal_render($output);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
......@@ -212,8 +214,8 @@ function testFieldViewValue() {
);
$setting = $display['settings']['test_formatter_setting_multiple'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta]->getValue();
$output = field_view_value($this->entity, $this->field_name, $item, $display);
$item = $this->entity->{$this->field_name}[$delta];
$output = $item->view($display);
$this->content = drupal_render($output);
$this->assertText($setting . '|0:' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
......@@ -227,8 +229,8 @@ function testFieldViewValue() {
);
$setting = $display['settings']['test_formatter_setting_additional'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta]->getValue();
$output = field_view_value($this->entity, $this->field_name, $item, $display);
$item = $this->entity->{$this->field_name}[$delta];
$output = $item->view($display);
$this->content = drupal_render($output);
$this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
......@@ -237,8 +239,8 @@ function testFieldViewValue() {
// used.
$setting = $this->display_options['teaser']['settings']['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta]->getValue();
$output = field_view_value($this->entity, $this->field_name, $item, 'teaser');
$item = $this->entity->{$this->field_name}[$delta];
$output = $item->view('teaser');
$this->content = drupal_render($output);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
......@@ -247,8 +249,8 @@ function testFieldViewValue() {
// are used.
$setting = $this->display_options['default']['settings']['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta]->getValue();
$output = field_view_value($this->entity, $this->field_name, $item, 'unknown_view_mode');
$item = $this->entity->{$this->field_name}[$delta];
$output = $item->view('unknown_view_mode');
$this->content = drupal_render($output);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
......@@ -268,7 +270,7 @@ function testFieldEmpty() {
);
// $this->entity is set by the setUp() method and by default contains 4
// numeric values. We only want to test the display of this one field.
$output = field_view_field($this->entity, $this->field_name, $display);
$output = $this->entity->get($this->field_name)->view($display);
$view = drupal_render($output);
$this->content = $view;
// The test field by default contains values, so should not display the
......@@ -278,7 +280,7 @@ function testFieldEmpty() {
// Now remove the values from the test field and retest.
$this->entity->{$this->field_name} = array();
$this->entity->save();
$output = field_view_field($this->entity, $this->field_name, $display);
$output = $this->entity->get($this->field_name)->view($display);
$view = drupal_render($output);
$this->content = $view;
// This time, as the field values have been removed, we *should* show the
......
......@@ -37,12 +37,13 @@ public function testFormatter() {
$entity = entity_create('entity_test');
$entity->{$this->fieldName}->value = 1;