Commit 568fef4c authored by webchick's avatar webchick

Issue #2109793 by damiankloip, dawehner, tim.plunkett: Convert element_*...

Issue #2109793 by damiankloip, dawehner, tim.plunkett: Convert element_* methods in common.inc to a class.
parent 7db0d8b8
<?php
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Json;
use Drupal\Component\Utility\Number;
use Drupal\Component\Utility\Settings;
......@@ -21,6 +20,7 @@
use Drupal\Core\Routing\GeneratorNotInitializedException;
use Drupal\Core\SystemListingInfo;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Render\Element;
/**
* @file
......@@ -3821,7 +3821,7 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
}
// Get the children of the element, sorted by weight.
$children = element_children($elements, TRUE);
$children = Element::children($elements, TRUE);
// Initialize this element's #children, unless a #pre_render callback already
// preset #children.
......@@ -4668,23 +4668,35 @@ function drupal_sort_title($a, $b) {
/**
* Checks if the key is a property.
*
* @see \Drupal\Core\Render\Element::property()
*
* @deprecated as of Drupal 8.0. Use Element::property() instead.
*/
function element_property($key) {
return $key[0] == '#';
return Element::property($key);
}
/**
* Gets properties of a structured array element (keys beginning with '#').
*
* @see \Drupal\Core\Render\Element::properties()
*
* @deprecated as of Drupal 8.0. Use Element::properties() instead.
*/
function element_properties($element) {
return array_filter(array_keys((array) $element), 'element_property');
return Element::properties($element);
}
/**
* Checks if the key is a child.
*
* @see \Drupal\Core\Render\Element::child()
*
* @deprecated as of Drupal 8.0. Use Element::child() instead.
*/
function element_child($key) {
return !isset($key[0]) || $key[0] != '#';
return Element::child($key);
}
/**
......@@ -4700,43 +4712,13 @@ function element_child($key) {
*
* @return
* The array keys of the element's children.
*
* @see \Drupal\Core\Render\Element::children()
*
* @deprecated as of Drupal 8.0. Use Element::children() instead.
*/
function element_children(&$elements, $sort = FALSE) {
// Do not attempt to sort elements which have already been sorted.
$sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;
// Filter out properties from the element, leaving only children.
$children = array();
$sortable = FALSE;
foreach ($elements as $key => $value) {
if ($key === '' || $key[0] !== '#') {
if (is_array($value)) {
$children[$key] = $value;
if (isset($value['#weight'])) {
$sortable = TRUE;
}
}
// Only trigger an error if the value is not null.
// @see http://drupal.org/node/1283892
elseif (isset($value)) {
trigger_error(t('"@key" is an invalid render array key', array('@key' => $key)), E_USER_ERROR);
}
}
}
// Sort the children if necessary.
if ($sort && $sortable) {
uasort($children, 'element_sort');
// Put the sorted children back into $elements in the correct order, to
// preserve sorting if the same element is passed through
// element_children() twice.
foreach ($children as $key => $child) {
unset($elements[$key]);
$elements[$key] = $child;
}
$elements['#sorted'] = TRUE;
}
return array_keys($children);
return Element::children($elements, $sort);
}
/**
......@@ -4747,27 +4729,13 @@ function element_children(&$elements, $sort = FALSE) {
*
* @return
* The array keys of the element's visible children.
*
* @see \Drupal\Core\Render\Element::getVisibleChildren()
*
* @deprecated as of Drupal 8.0. Use Element::getVisibleChildren() instead.
*/
function element_get_visible_children(array $elements) {
$visible_children = array();
foreach (element_children($elements) as $key) {
$child = $elements[$key];
// Skip un-accessible children.
if (isset($child['#access']) && !$child['#access']) {
continue;
}
// Skip value and hidden elements, since they are not rendered.
if (isset($child['#type']) && in_array($child['#type'], array('value', 'hidden'))) {
continue;
}
$visible_children[$key] = $child;
}
return array_keys($visible_children);
return Element::getVisibleChildren($elements);
}
/**
......@@ -4781,18 +4749,13 @@ function element_get_visible_children(array $elements) {
* array('#propertyname' => 'attributename'). If both names are identical
* except for the leading '#', then an attribute name value is sufficient and
* no property name needs to be specified.
*
* @see \Drupal\Core\Render\Element::setAttributes()
*
* @deprecated as of Drupal 8.0. Use Element::setAttributes() instead.
*/
function element_set_attributes(array &$element, array $map) {
foreach ($map as $property => $attribute) {
// If the key is numeric, the attribute name needs to be taken over.
if (is_int($property)) {
$property = '#' . $attribute;
}
// Do not overwrite already existing attributes.
if (isset($element[$property]) && !isset($element['#attributes'][$attribute])) {
$element['#attributes'][$attribute] = $element[$property];
}
}
Element::setAttributes($element, $map);
}
/**
......
......@@ -15,6 +15,7 @@
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\HttpKernel;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactory;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
......@@ -1003,7 +1004,7 @@ public function redirectForm($form_state) {
*/
protected function doValidateForm(&$elements, &$form_state, $form_id = NULL) {
// Recurse through all children.
foreach ($this->elementChildren($elements) as $key) {
foreach (Element::children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {
$this->doValidateForm($elements[$key], $form_state);
}
......@@ -1162,7 +1163,7 @@ protected function doValidateForm(&$elements, &$form_state, $form_id = NULL) {
*/
protected function setElementErrorsFromFormState(array &$elements, array &$form_state) {
// Recurse through all children.
foreach ($this->elementChildren($elements) as $key) {
foreach (Element::children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {
$this->setElementErrorsFromFormState($elements[$key], $form_state);
}
......@@ -1367,7 +1368,7 @@ public function doBuildForm($form_id, &$element, &$form_state) {
// Recurse through all child elements.
$count = 0;
foreach ($this->elementChildren($element) as $key) {
foreach (Element::children($element) as $key) {
// Prior to checking properties of child elements, their default
// properties need to be loaded.
if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = $this->getElementInfo($element[$key]['#type']))) {
......@@ -1774,15 +1775,6 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat =
return drupal_set_message($message, $type, $repeat);
}
/**
* Wraps element_children().
*
* @return array
*/
protected function elementChildren(&$elements, $sort = FALSE) {
return element_children($elements, $sort);
}
/**
* Wraps drupal_html_class().
*
......
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element.
*/
namespace Drupal\Core\Render;
use Drupal\Component\Utility\String;
/**
* Deals with drupal render elements.
*/
class Element {
/**
* Checks if the key is a property.
*
* @param string $key
* The key to check.
*
* @return bool
* TRUE of the key is a property, FALSE otherwise.
*/
public static function property($key) {
return $key[0] == '#';
}
/**
* Gets properties of a structured array element (keys beginning with '#').
*
* @param array $element
* An element array to return properties for.
*
* @return array
* An array of property keys for the element.
*/
public static function properties(array $element) {
return array_filter(array_keys($element), 'static::property');
}
/**
* Checks if the key is a child.
*
* @param string $key
* The key to check.
*
* @return bool
* TRUE if the element is a child, FALSE otherwise.
*/
public static function child($key) {
return !isset($key[0]) || $key[0] != '#';
}
/**
* Identifies the children of an element array, optionally sorted by weight.
*
* The children of a element array are those key/value pairs whose key does
* not start with a '#'. See drupal_render() for details.
*
* @param array $elements
* The element array whose children are to be identified. Passed by
* reference.
* @param bool $sort
* Boolean to indicate whether the children should be sorted by weight.
*
* @return array
* The array keys of the element's children.
*/
public static function children(array &$elements, $sort = FALSE) {
// Do not attempt to sort elements which have already been sorted.
$sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;
// Filter out properties from the element, leaving only children.
$children = array();
$sortable = FALSE;
foreach ($elements as $key => $value) {
if ($key === '' || $key[0] !== '#') {
if (is_array($value)) {
$children[$key] = $value;
if (isset($value['#weight'])) {
$sortable = TRUE;
}
}
// Only trigger an error if the value is not null.
// @see http://drupal.org/node/1283892
elseif (isset($value)) {
trigger_error(String::format('"@key" is an invalid render array key', array('@key' => $key)), E_USER_ERROR);
}
}
}
// Sort the children if necessary.
if ($sort && $sortable) {
uasort($children, 'Drupal\Component\Utility\SortArray::sortByWeightProperty');
// Put the sorted children back into $elements in the correct order, to
// preserve sorting if the same element is passed through
// element_children() twice.
foreach ($children as $key => $child) {
unset($elements[$key]);
$elements[$key] = $child;
}
$elements['#sorted'] = TRUE;
}
return array_keys($children);
}
/**
* Returns the visible children of an element.
*
* @param array $elements
* The parent element.
*
* @return array
* The array keys of the element's visible children.
*/
public static function getVisibleChildren(array $elements) {
$visible_children = array();
foreach (static::children($elements) as $key) {
$child = $elements[$key];
// Skip un-accessible children.
if (isset($child['#access']) && !$child['#access']) {
continue;
}
// Skip value and hidden elements, since they are not rendered.
if (isset($child['#type']) && in_array($child['#type'], array('value', 'hidden'))) {
continue;
}
$visible_children[$key] = $child;
}
return array_keys($visible_children);
}
/**
* Sets HTML attributes based on element properties.
*
* @param array $element
* The renderable element to process. Passed by reference.
* @param array $map
* An associative array whose keys are element property names and whose values
* are the HTML attribute names to set for corresponding the property; e.g.,
* array('#propertyname' => 'attributename'). If both names are identical
* except for the leading '#', then an attribute name value is sufficient and
* no property name needs to be specified.
*/
public static function setAttributes(array &$element, array $map) {
foreach ($map as $property => $attribute) {
// If the key is numeric, the attribute name needs to be taken over.
if (is_int($property)) {
$property = '#' . $attribute;
}
// Do not overwrite already existing attributes.
if (isset($element[$property]) && !isset($element['#attributes'][$attribute])) {
$element['#attributes'][$attribute] = $element[$property];
}
}
}
}
......@@ -145,23 +145,6 @@ function testDrupalRenderFormElements() {
));
}
/**
* Tests rendering elements with invalid keys.
*/
function testDrupalRenderInvalidKeys() {
$error = array(
'%type' => 'User error',
'!message' => '"child" is an invalid render array key',
'%function' => 'element_children()',
);
$message = t('%type: !message in %function (line ', $error);
\Drupal::config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save();
$this->drupalGet('common-test/drupal-render-invalid-keys');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertRaw($message, format_string('Found error message: !message.', array('!message' => $message)));
}
/**
* Tests that elements are rendered properly.
*/
......
......@@ -12,14 +12,6 @@ common_test.destination:
requirements:
_permission: 'access content'
common_test.drupal_render_invalid_keys:
path: '/common-test/drupal-render-invalid-keys'
defaults:
_title: 'Drupal Render'
_content: '\Drupal\common_test\Controller\CommonTestController::drupalRenderInvalidKeys'
requirements:
_permission: 'access content'
common_test.js_and_css_querystring:
path: '/common-test/query-string'
defaults:
......
......@@ -61,20 +61,6 @@ public function typeLinkActiveClass() {
);
}
/**
* Renders an element with an invalid render array key.
*
* @return array
* A render array.
*/
public function drupalRenderInvalidKeys() {
define('SIMPLETEST_COLLECT_ERRORS', FALSE);
// Keys that begin with # may contain a value of any type, otherwise they must
// contain arrays.
$element = array('child' => 'This should be an array.');
return drupal_render($element);
}
/**
* Adds a JavaScript file and a CSS file with a query string appended.
*
......
......@@ -813,21 +813,6 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat =
protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) {
}
/**
* {@inheritdoc}
*/
protected function elementChildren(&$elements, $sort = FALSE) {
$children = array();
foreach ($elements as $key => $value) {
if ($key === '' || $key[0] !== '#') {
if (is_array($value)) {
$children[] = $key;
}
}
}
return $children;
}
/**
* {@inheritdoc}
*/
......
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Render\ElementTest.
*/
namespace Drupal\Tests\Core\Render;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Render\Element;
/**
* Tests the Element class
*
* @see \Drupal\Core\Render\Element
*
* @group Drupal
* @group Render
*/
class ElementTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Element test',
'description' => 'Tests \Drupal\Core\Render\Element helper class.',
'group' => 'Render',
);
}
/**
* Tests the property() method.
*/
public function testProperty() {
$this->assertTrue(Element::property('#property'));
$this->assertFalse(Element::property('property'));
$this->assertFalse(Element::property('property#'));
}
/**
* Tests the properties() method.
*/
public function testProperties() {
$element = array(
'#property1' => 'property1',
'#property2' => 'property2',
'property3' => 'property3'
);
$properties = Element::properties($element);
$this->assertContains('#property1', $properties);
$this->assertContains('#property2', $properties);
$this->assertNotContains('property3', $properties);
}
/**
* Tests the child() method.
*/
public function testChild() {
$this->assertFalse(Element::child('#property'));
$this->assertTrue(Element::child('property'));
$this->assertTrue(Element::child('property#'));
}
/**
* Tests the children() method.
*/
public function testChildren() {
$element = array(
'child2' => array('#weight' => 10),
'child1' => array('#weight' => 0),
'child3' => array('#weight' => 20),
'#property' => 'property',
);
$expected = array('child2', 'child1', 'child3');
$element_copy = $element;
$this->assertSame($expected, Element::children($element_copy));
// If #sorted is already set, no sorting should happen.
$element_copy = $element;
$element_copy['#sorted'] = TRUE;
$expected = array('child2', 'child1', 'child3');
$this->assertSame($expected, Element::children($element_copy, TRUE));
// Test with weight sorting, #sorted property should be added.
$expected = array('child1', 'child2', 'child3');
$element_copy = $element;
$this->assertSame($expected, Element::children($element_copy, TRUE));
$this->assertArrayHasKey('#sorted', $element_copy);
$this->assertTrue($element_copy['#sorted']);
// The order should stay the same if no weights present.
$element_no_weight = array(
'child2' => array(),
'child1' => array(),
'child3' => array(),
'#property' => 'property',
);
$expected = array('child2', 'child1', 'child3');
$this->assertSame($expected, Element::children($element_no_weight, TRUE));
}
/**
* Tests the children() method with an invalid key.
*
* @expectedException \PHPUnit_Framework_Error
* @expectedExceptionMessage "foo" is an invalid render array key
*/
public function testInvalidChildren() {
$element = array(
'foo' => 'bar',
);
Element::children($element);
}
/**
* Tests the children() method with an ignored key/value pair.
*/
public function testIgnoredChildren() {
$element = array(
'foo' => NULL,
);
$this->assertSame(array(), Element::children($element));
}
/**
* Tests the visibleChildren() method.
*
* @param array $element
* The test element array.
* @param array $expected_keys
* The expected keys to be returned from Element::getVisibleChildren().
*
* @dataProvider providerVisibleChildren
*/
public function testVisibleChildren(array $element, array $expected_keys) {
$this->assertSame($expected_keys, Element::getVisibleChildren($element));
}
/**
* Data provider for testVisibleChildren.
*
* @return array
*/
public function providerVisibleChildren() {
return array(
array(array('#property1' => '', '#property2' => array()), array()),
array(array('#property1' => '', 'child1' => array()), array('child1')),
array(array('#property1' => '', 'child1' => array(), 'child2' => array('#access' => TRUE)), array('child1', 'child2')),
array(array('#property1' => '', 'child1' => array(), 'child2' => array('#access' => FALSE)), array('child1')),
array(array('#property1' => '', 'child1' => array(), 'child2' => array('#type' => 'textfield')), array('child1', 'child2')),
array(array('#property1' => '', 'child1' => array(), 'child2' => array('#type' => 'value')), array('child1')),
array(array('#property1' => '', 'child1' => array(), 'child2' => array('#type' => 'hidden')), array('child1')),
);
}
/**
* Tests the setAttributes() method.
*
* @dataProvider providerTestSetAttributes
*/
public function testSetAttributes($element, $map, $expected_element) {