Commit 041511a7 authored by alexpott's avatar alexpott

Issue #2397727 by jibran, dawehner: Remove the SafeMarkup::set() call in field/Field.php

parent d5eb64ce
......@@ -20,10 +20,12 @@
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\CacheablePluginInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Plugin\views\field\MultiItemsFieldHandlerInterface;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
......@@ -36,7 +38,7 @@
*
* @ViewsField("field")
*/
class Field extends FieldPluginBase implements CacheablePluginInterface {
class Field extends FieldPluginBase implements CacheablePluginInterface, MultiItemsFieldHandlerInterface {
/**
* An array to store field renderable arrays for use by renderItems().
......@@ -112,6 +114,13 @@ class Field extends FieldPluginBase implements CacheablePluginInterface {
*/
protected $languageManager;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a \Drupal\field\Plugin\views\field\Field object.
*
......@@ -127,13 +136,17 @@ class Field extends FieldPluginBase implements CacheablePluginInterface {
* The field formatter plugin manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager, LanguageManagerInterface $language_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->formatterPluginManager = $formatter_plugin_manager;
$this->languageManager = $language_manager;
$this->renderer = $renderer;
}
/**
......@@ -146,7 +159,8 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_definition,
$container->get('entity.manager'),
$container->get('plugin.manager.field.formatter'),
$container->get('language_manager')
$container->get('language_manager'),
$container->get('renderer')
);
}
......@@ -689,34 +703,25 @@ public function submitGroupByForm(&$form, FormStateInterface $form_state) {
* When using advanced render, each possible item in the list is rendered
* individually. Then the items are all pasted together.
*/
protected function renderItems($items) {
public function renderItems($items) {
if (!empty($items)) {
$output = '';
if (!$this->options['group_rows']) {
foreach ($items as $item) {
$output .= SafeMarkup::escape($item);
}
return SafeMarkup::set($output);
}
if ($this->options['multi_type'] == 'separator') {
$output = '';
$separator = '';
$escaped_separator = Xss::filterAdmin($this->options['separator']);
foreach ($items as $item) {
$output .= $separator . SafeMarkup::escape($item);
$separator = $escaped_separator;
}
return SafeMarkup::set($output);
if ($this->options['multi_type'] == 'separator' || !$this->options['group_rows']) {
$separator = $this->options['multi_type'] == 'separator' ? SafeMarkup::checkAdminXss($this->options['separator']) : '';
$build = [
'#type' => 'inline_template',
'#template' => '{{ items | safe_join(separator) }}',
'#context' => ['separator' => $separator, 'items' => $items],
];
}
else {
$item_list = array(
$build = array(
'#theme' => 'item_list',
'#items' => $items,
'#title' => NULL,
'#list_type' => $this->options['multi_type'],
);
return drupal_render($item_list);
}
return $this->renderer->render($build);
}
}
......
......@@ -288,6 +288,26 @@ public function _testMultipleFieldRender() {
}
$this->assertEqual($rendered_field, implode(':', $items), 'Make sure that the amount of items is limited.');
}
$view->destroy();
// Test separator with HTML, ensure it is escaped.
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3;
$view->displayHandlers->get('default')->options['fields'][$field_name]['separator'] = '<h2>test</h2>';
$this->executeView($view);
for ($i = 0; $i < 3; $i++) {
$rendered_field = $view->style_plugin->getField($i, $field_name);
$items = [];
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
$pure_items = array_splice($pure_items, 0, 3);
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
$this->assertEqual($rendered_field, implode('<h2>test</h2>', $items), 'Make sure that the amount of items is limited.');
}
$view->destroy();
}
}
<?php
/**
* @file
* Contains \Drupal\views\Plugin\views\Field\FieldHandlerInterface
*/
namespace Drupal\views\Plugin\views\field;
use Drupal\views\ResultRow;
use Drupal\views\Plugin\views\ViewsHandlerInterface;
/**
* Base field handler that has no options and renders an unformatted field.
*/
interface FieldHandlerInterface extends ViewsHandlerInterface {
/**
* Adds an ORDER BY clause to the query for click sort columns.
*
* @param string $order
* Either ASC or DESC
*/
public function clickSort($order);
/**
* Determines if this field is click sortable.
*
* @return bool
* The value of 'click sortable' from the plugin definition, this defaults
* to TRUE if not set.
*/
public function clickSortable();
/**
* Gets this field's label.
*/
public function label();
/**
* Returns an HTML element based upon the field's element type.
*
* @param bool $none_supported
* Whether or not this HTML element is supported.
* @param bool $default_empty
* Whether or not this HTML element is empty by default.
* @param bool $inline
* Whether or not this HTML element is inline.
*/
public function elementType($none_supported = FALSE, $default_empty = FALSE, $inline = FALSE);
/**
* Returns an HTML element for the label based upon the field's element type.
*
* @param bool $none_supported
* Whether or not this HTML element is supported.
* @param bool $default_empty
* Whether or not this HTML element is empty by default.
*/
public function elementLabelType($none_supported = FALSE, $default_empty = FALSE);
/**
* Returns an HTML element for the wrapper based upon the field's element type.
*
* @param bool $none_supported
* Whether or not this HTML element is supported.
* @param bool $default_empty
* Whether or not this HTML element is empty by default.
*/
public function elementWrapperType($none_supported = FALSE, $default_empty = FALSE);
/**
* Provides a list of elements valid for field HTML.
*
* This function can be overridden by fields that want more or fewer
* elements available, though this seems like it would be an incredibly
* rare occurrence.
*/
public function getElements();
/**
* Returns the class of the field.
*
* @param bool $row_index
* The index of current row.
*/
public function elementClasses($row_index = NULL);
/**
* Replaces a value with tokens from the last field.
*
* This function actually figures out which field was last and uses its
* tokens so they will all be available.
*
* @param string $value
* The raw string.
* @param bool $row_index
* The index of current row.
*/
public function tokenizeValue($value, $row_index = NULL);
/**
* Returns the class of the field's label.
*
* @param bool $row_index
* The index of current row.
*/
public function elementLabelClasses($row_index = NULL);
/**
* Returns the class of the field's wrapper.
*
* @param bool $row_index
* The index of current row.
*/
public function elementWrapperClasses($row_index = NULL);
/**
* Gets the entity matching the current row and relationship.
*
* @param \Drupal\views\ResultRow $values
* An object containing all retrieved values.
*
* @return \Drupal\Core\Entity\EntityInterface
* Returns the entity matching the values.
*/
public function getEntity(ResultRow $values);
/**
* Gets the value that's supposed to be rendered.
*
* This api exists so that other modules can easy set the values of the field
* without having the need to change the render method as well.
*
* @param \Drupal\views\ResultRow $values
* An object containing all retrieved values.
* @param string $field
* Optional name of the field where the value is stored.
*
*/
public function getValue(ResultRow $values, $field = NULL);
/**
* Determines if this field will be available as an option to group the result
* by in the style settings.
*
* @return bool
* TRUE if this field handler is groupable, otherwise FALSE.
*/
public function useStringGroupBy();
/**
* Runs before any fields are rendered.
*
* This gives the handlers some time to set up before any handler has
* been rendered.
*
* @param \Drupal\views\ResultRow[] $values
* An array of all ResultRow objects returned from the query.
*
*/
public function preRender(&$values);
/**
* Renders the field.
*
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
*
* @return string
* The rendered output.
*
*/
public function render(ResultRow $values);
/**
* Renders a field using advanced settings.
*
* This renders a field normally, then decides if render-as-link and
* text-replacement rendering is necessary.
*
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
*
* @return string
* The advanced rendered output.
*
*/
public function advancedRender(ResultRow $values);
/**
* Checks if a field value is empty.
*
* @param $value
* The field value.
* @param bool $empty_zero
* Whether or not this field is configured to consider 0 as empty.
* @param bool $no_skip_empty
* Whether or not to use empty() to check the value.
*
* @return bool
* TRUE if the value is considered empty, FALSE otherwise.
*/
public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE);
/**
* Performs an advanced text render for the item.
*
* This is separated out as some fields may render lists, and this allows
* each item to be handled individually.
*
* @param array $alter
* The alter array of options to use.
* - max_length: Maximum length of the string, the rest gets truncated.
* - word_boundary: Trim only on a word boundary.
* - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
* - html: Make sure that the html is correct.
*
* @return string
* The rendered string.
*/
public function renderText($alter);
/**
* Trims the field down to the specified length.
*
* @param array $alter
* The alter array of options to use.
* - max_length: Maximum length of the string, the rest gets truncated.
* - word_boundary: Trim only on a word boundary.
* - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
* - html: Make sure that the html is correct.
*
* @param string $value
* The string which should be trimmed.
*
* @return string
* The rendered trimmed string.
*/
public function renderTrimText($alter, $value);
/**
* Gets the 'render' tokens to use for advanced rendering.
*
* This runs through all of the fields and arguments that
* are available and gets their values. This will then be
* used in one giant str_replace().
*
* @param mixed $item
* The item to render.
*
* @return array
* An array of available tokens
*/
public function getRenderTokens($item);
/**
* Passes values to drupal_render() using $this->themeFunctions() as #theme.
*
* @param \Drupal\views\ResultRow $values
* Holds single row of a view's result set.
*
* @return string|false
* Returns rendered output of the given theme implementation.
*/
function theme(ResultRow $values);
}
......@@ -47,11 +47,7 @@
* @ingroup views_plugins
* @see plugin_api
*/
/**
* Base field handler that has no options and renders an unformatted field.
*/
abstract class FieldPluginBase extends HandlerBase {
abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterface {
/**
* Indicator of the renderText() method for rendering a single item.
......@@ -197,7 +193,7 @@ protected function addAdditionalFields($fields = NULL) {
}
/**
* Called to determine what to tell the clicksorter.
* {@inheritdoc}
*/
public function clickSort($order) {
if (isset($this->field_alias)) {
......@@ -209,18 +205,14 @@ public function clickSort($order) {
}
/**
* Determine if this field is click sortable.
*
* @return bool
* The value of 'click sortable' from the plugin definition, this defaults
* to TRUE if not set.
* {@inheritdoc}
*/
public function clickSortable() {
return isset($this->definition['click sortable']) ? $this->definition['click sortable'] : TRUE;
}
/**
* Get this field's label.
* {@inheritdoc}
*/
public function label() {
if (!isset($this->options['label'])) {
......@@ -230,7 +222,7 @@ public function label() {
}
/**
* Return an HTML element based upon the field's element type.
* {@inheritdoc}
*/
public function elementType($none_supported = FALSE, $default_empty = FALSE, $inline = FALSE) {
if ($none_supported) {
......@@ -258,7 +250,7 @@ public function elementType($none_supported = FALSE, $default_empty = FALSE, $in
}
/**
* Return an HTML element for the label based upon the field's element type.
* {@inheritdoc}
*/
public function elementLabelType($none_supported = FALSE, $default_empty = FALSE) {
if ($none_supported) {
......@@ -278,7 +270,7 @@ public function elementLabelType($none_supported = FALSE, $default_empty = FALSE
}
/**
* Return an HTML element for the wrapper based upon the field's element type.
* {@inheritdoc}
*/
public function elementWrapperType($none_supported = FALSE, $default_empty = FALSE) {
if ($none_supported) {
......@@ -298,11 +290,7 @@ public function elementWrapperType($none_supported = FALSE, $default_empty = FAL
}
/**
* Provide a list of elements valid for field HTML.
*
* This function can be overridden by fields that want more or fewer
* elements available, though this seems like it would be an incredibly
* rare occurrence.
* {@inheritdoc}
*/
public function getElements() {
static $elements = NULL;
......@@ -319,7 +307,7 @@ public function getElements() {
}
/**
* Return the class of the field.
* {@inheritdoc}
*/
public function elementClasses($row_index = NULL) {
$classes = explode(' ', $this->options['element_class']);
......@@ -331,10 +319,7 @@ public function elementClasses($row_index = NULL) {
}
/**
* Replace a value with tokens from the last field.
*
* This function actually figures out which field was last and uses its
* tokens so they will all be available.
* {@inheritdoc}
*/
public function tokenizeValue($value, $row_index = NULL) {
if (strpos($value, '[') !== FALSE || strpos($value, '!') !== FALSE || strpos($value, '%') !== FALSE) {
......@@ -369,7 +354,7 @@ public function tokenizeValue($value, $row_index = NULL) {
}
/**
* Return the class of the field's label.
* {@inheritdoc}
*/
public function elementLabelClasses($row_index = NULL) {
$classes = explode(' ', $this->options['element_label_class']);
......@@ -381,7 +366,7 @@ public function elementLabelClasses($row_index = NULL) {
}
/**
* Return the class of the field's wrapper.
* {@inheritdoc}
*/
public function elementWrapperClasses($row_index = NULL) {
$classes = explode(' ', $this->options['element_wrapper_class']);
......@@ -393,13 +378,7 @@ public function elementWrapperClasses($row_index = NULL) {
}
/**
* Gets the entity matching the current row and relationship.
*
* @param \Drupal\views\ResultRow $values
* An object containing all retrieved values.
*
* @return \Drupal\Core\Entity\EntityInterface
* Returns the entity matching the values.
* {@inheritdoc}
*/
public function getEntity(ResultRow $values) {
$relationship_id = $this->options['relationship'];
......@@ -412,15 +391,7 @@ public function getEntity(ResultRow $values) {
}
/**
* Get the value that's supposed to be rendered.
*
* This api exists so that other modules can easy set the values of the field
* without having the need to change the render method as well.
*
* @param \Drupal\views\ResultRow $values
* An object containing all retrieved values.
* @param string $field
* Optional name of the field where the value is stored.
* {@inheritdoc}
*/
public function getValue(ResultRow $values, $field = NULL) {
$alias = isset($field) ? $this->aliases[$field] : $this->field_alias;
......@@ -430,11 +401,7 @@ public function getValue(ResultRow $values, $field = NULL) {
}
/**
* Determines if this field will be available as an option to group the result
* by in the style settings.
*
* @return bool
* TRUE if this field handler is groupable, otherwise FALSE.
* {@inheritdoc}
*/
public function useStringGroupBy() {
return TRUE;
......@@ -1119,21 +1086,12 @@ public function adminSummary() {
}
/**
* Run before any fields are rendered.
*
* This gives the handlers some time to set up before any handler has
* been rendered.
*
* @param \Drupal\views\ResultRow[] $values
* An array of all ResultRow objects returned from the query.
* {@inheritdoc}
*/
public function preRender(&$values) { }
/**
* Renders the field.
*
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$value = $this->getValue($values);
......@@ -1141,16 +1099,10 @@ public function render(ResultRow $values) {
}
/**
* Render a field using advanced settings.
*
* This renders a field normally, then decides if render-as-link and
* text-replacement rendering is necessary.
*
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
* {@inheritdoc}
*/
public function advancedRender(ResultRow $values) {
if ($this->allowAdvancedRender() && method_exists($this, 'render_item')) {
if ($this->allowAdvancedRender() && $this instanceof MultiItemsFieldHandlerInterface) {
$raw_items = $this->getItems($values);
// If there are no items, set the original value to NULL.
if (empty($raw_items)) {
......@@ -1168,7 +1120,7 @@ public function advancedRender(ResultRow $values) {
if ($this->allowAdvancedRender()) {
$tokens = NULL;
if (method_exists($this, 'render_item')) {
if ($this instanceof MultiItemsFieldHandlerInterface) {
$items = array();
foreach ($raw_items as $count => $item) {
$value = $this->render_item($count, $item);
......@@ -1215,17 +1167,7 @@ public function advancedRender(ResultRow $values) {
}
/**
* Checks if a field value is empty.
*
* @param $value
* The field value.
* @param bool $empty_zero
* Whether or not this field is configured to consider 0 as empty.
* @param bool $no_skip_empty
* Whether or not to use empty() to check the value.
*
* @return bool
* TRUE if the value is considered empty, FALSE otherwise.
* {@inheritdoc}
*/
public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) {
if (!isset($value)) {
......@@ -1242,10 +1184,7 @@ public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) {
}
/**
* Perform an advanced text render for the item.
*
* This is separated out as some fields may render lists, and this allows
* each item to be handled individually.
* {@inheritdoc}
*/
public function renderText($alter) {
$value = $this->last_render;
......@@ -1332,7 +1271,7 @@ protected function renderAltered($alter, $tokens) {
}
/**
* Trim the field down to the specified length.
* {@inheritdoc}
*/
public function renderTrimText($alter, $value) {
if (!empty($alter['strip_tags'])) {
......@@ -1516,11 +1455,7 @@ protected function renderAsLink($alter, $text, $tokens) {
}
/**
* Get the 'render' tokens to use for advanced rendering.
*
* This runs through all of the fields and arguments that
* are available and gets their values. This will then be
* used in one giant str_replace().
* {@inheritdoc}
*/
public function getRenderTokens($item) {
$tokens = array();
......@@ -1647,13 +1582,7 @@ protected function addSelfTokens(&$tokens, $item) { }
protected function documentSelfTokens(&$tokens) { }
/**
* Pass values to $this->getRenderer()->render() using $this->themeFunctions() as #theme.
*
* @param \Drupal\views\ResultRow $values
* Holds single row of a view's result set.
*
* @return string|false
* Returns rendered output of the given theme implementation.
* {@inheritdoc}
*/
function theme(ResultRow $values) {
$build = array(
......
<?php
/**
* @file
* Contains \Drupal\views\Plugin\views\field\MultiItemsFieldHandlerInterface.
*/
namespace Drupal\views\Plugin\views\field;
use Drupal\views\ResultRow;
/**
* Defines a field hander which renders multiple items per row.
*/
interface MultiItemsFieldHandlerInterface extends FieldHandlerInterface {
/**
* Renders a single item of a row.
*
* @param int $count
* The index of the item inside the row.
* @param mixed $item
* The item for the field to render.
*
* @return string
* The rendered output.
*/
public function render_item($count, $item);
/**
* Gets an array of items for the field.
*
* @param \Drupal\views\ResultRow $values
* The result row object containing the values.
*
* @return array
* An array of items for the field.
*/
public function getItems(ResultRow $values);
/**