Element.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <?php
  2. namespace Drupal\Core\Render;
  3. use Drupal\Component\Render\FormattableMarkup;
  4. use Drupal\Core\Access\AccessResultInterface;
  5. /**
  6. * Provides helper methods for Drupal render elements.
  7. *
  8. * @see \Drupal\Core\Render\Element\ElementInterface
  9. *
  10. * @ingroup theme_render
  11. */
  12. class Element {
  13. /**
  14. * Checks if the key is a property.
  15. *
  16. * @param string $key
  17. * The key to check.
  18. *
  19. * @return bool
  20. * TRUE of the key is a property, FALSE otherwise.
  21. */
  22. public static function property($key) {
  23. return $key[0] == '#';
  24. }
  25. /**
  26. * Gets properties of a structured array element (keys beginning with '#').
  27. *
  28. * @param array $element
  29. * An element array to return properties for.
  30. *
  31. * @return array
  32. * An array of property keys for the element.
  33. */
  34. public static function properties(array $element) {
  35. return array_filter(array_keys($element), 'static::property');
  36. }
  37. /**
  38. * Checks if the key is a child.
  39. *
  40. * @param string $key
  41. * The key to check.
  42. *
  43. * @return bool
  44. * TRUE if the element is a child, FALSE otherwise.
  45. */
  46. public static function child($key) {
  47. return !isset($key[0]) || $key[0] != '#';
  48. }
  49. /**
  50. * Identifies the children of an element array, optionally sorted by weight.
  51. *
  52. * The children of a element array are those key/value pairs whose key does
  53. * not start with a '#'. See drupal_render() for details.
  54. *
  55. * @param array $elements
  56. * The element array whose children are to be identified. Passed by
  57. * reference.
  58. * @param bool $sort
  59. * Boolean to indicate whether the children should be sorted by weight.
  60. *
  61. * @return array
  62. * The array keys of the element's children.
  63. */
  64. public static function children(array &$elements, $sort = FALSE) {
  65. // Do not attempt to sort elements which have already been sorted.
  66. $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;
  67. // Filter out properties from the element, leaving only children.
  68. $count = count($elements);
  69. $child_weights = [];
  70. $i = 0;
  71. $sortable = FALSE;
  72. foreach ($elements as $key => $value) {
  73. if ($key === '' || $key[0] !== '#') {
  74. if (is_array($value)) {
  75. if (isset($value['#weight'])) {
  76. $weight = $value['#weight'];
  77. $sortable = TRUE;
  78. }
  79. else {
  80. $weight = 0;
  81. }
  82. // Supports weight with up to three digit precision and conserve
  83. // the insertion order.
  84. $child_weights[$key] = floor($weight * 1000) + $i / $count;
  85. }
  86. // Only trigger an error if the value is not null.
  87. // @see https://www.drupal.org/node/1283892
  88. elseif (isset($value)) {
  89. trigger_error(new FormattableMarkup('"@key" is an invalid render array key', ['@key' => $key]), E_USER_ERROR);
  90. }
  91. }
  92. $i++;
  93. }
  94. // Sort the children if necessary.
  95. if ($sort && $sortable) {
  96. asort($child_weights);
  97. // Put the sorted children back into $elements in the correct order, to
  98. // preserve sorting if the same element is passed through
  99. // \Drupal\Core\Render\Element::children() twice.
  100. foreach ($child_weights as $key => $weight) {
  101. $value = $elements[$key];
  102. unset($elements[$key]);
  103. $elements[$key] = $value;
  104. }
  105. $elements['#sorted'] = TRUE;
  106. }
  107. return array_keys($child_weights);
  108. }
  109. /**
  110. * Returns the visible children of an element.
  111. *
  112. * @param array $elements
  113. * The parent element.
  114. *
  115. * @return array
  116. * The array keys of the element's visible children.
  117. */
  118. public static function getVisibleChildren(array $elements) {
  119. $visible_children = [];
  120. foreach (static::children($elements) as $key) {
  121. $child = $elements[$key];
  122. // Skip value and hidden elements, since they are not rendered.
  123. if (!static::isVisibleElement($child)) {
  124. continue;
  125. }
  126. $visible_children[$key] = $child;
  127. }
  128. return array_keys($visible_children);
  129. }
  130. /**
  131. * Determines if an element is visible.
  132. *
  133. * @param array $element
  134. * The element to check for visibility.
  135. *
  136. * @return bool
  137. * TRUE if the element is visible, otherwise FALSE.
  138. */
  139. public static function isVisibleElement($element) {
  140. return (!isset($element['#type']) || !in_array($element['#type'], ['value', 'hidden', 'token']))
  141. && (!isset($element['#access'])
  142. || (($element['#access'] instanceof AccessResultInterface && $element['#access']->isAllowed()) || ($element['#access'] === TRUE)));
  143. }
  144. /**
  145. * Sets HTML attributes based on element properties.
  146. *
  147. * @param array $element
  148. * The renderable element to process. Passed by reference.
  149. * @param array $map
  150. * An associative array whose keys are element property names and whose
  151. * values are the HTML attribute names to set on the corresponding
  152. * property; e.g., array('#propertyname' => 'attributename'). If both names
  153. * are identical except for the leading '#', then an attribute name value is
  154. * sufficient and no property name needs to be specified.
  155. */
  156. public static function setAttributes(array &$element, array $map) {
  157. foreach ($map as $property => $attribute) {
  158. // If the key is numeric, the attribute name needs to be taken over.
  159. if (is_int($property)) {
  160. $property = '#' . $attribute;
  161. }
  162. // Do not overwrite already existing attributes.
  163. if (isset($element[$property]) && !isset($element['#attributes'][$attribute])) {
  164. $element['#attributes'][$attribute] = $element[$property];
  165. }
  166. }
  167. }
  168. /**
  169. * Indicates whether the given element is empty.
  170. *
  171. * An element that only has #cache set is considered empty, because it will
  172. * render to the empty string.
  173. *
  174. * @param array $elements
  175. * The element.
  176. *
  177. * @return bool
  178. * Whether the given element is empty.
  179. */
  180. public static function isEmpty(array $elements) {
  181. return empty($elements) || (count($elements) === 1 && array_keys($elements) === ['#cache']);
  182. }
  183. }