Element.php 5.95 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\Core\Render;

5
use Drupal\Component\Utility\SafeMarkup;
6
use Drupal\Core\Access\AccessResultInterface;
7 8

/**
9 10 11 12 13
 * Provides helper methods for Drupal render elements.
 *
 * @see \Drupal\Core\Render\Element\ElementInterface
 *
 * @ingroup theme_render
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 */
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
50
   *   TRUE if the element is a child, FALSE otherwise.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
   */
  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.
76
    $count = count($elements);
77
    $child_weights = [];
78
    $i = 0;
79 80 81 82 83
    $sortable = FALSE;
    foreach ($elements as $key => $value) {
      if ($key === '' || $key[0] !== '#') {
        if (is_array($value)) {
          if (isset($value['#weight'])) {
84
            $weight = $value['#weight'];
85 86
            $sortable = TRUE;
          }
87 88 89 90 91 92
          else {
            $weight = 0;
          }
          // Supports weight with up to three digit precision and conserve
          // the insertion order.
          $child_weights[$key] = floor($weight * 1000) + $i / $count;
93 94
        }
        // Only trigger an error if the value is not null.
95
        // @see https://www.drupal.org/node/1283892
96
        elseif (isset($value)) {
97
          trigger_error(SafeMarkup::format('"@key" is an invalid render array key', ['@key' => $key]), E_USER_ERROR);
98 99
        }
      }
100
      $i++;
101
    }
102

103 104
    // Sort the children if necessary.
    if ($sort && $sortable) {
105
      asort($child_weights);
106 107
      // Put the sorted children back into $elements in the correct order, to
      // preserve sorting if the same element is passed through
108
      // \Drupal\Core\Render\Element::children() twice.
109 110
      foreach ($child_weights as $key => $weight) {
        $value = $elements[$key];
111
        unset($elements[$key]);
112
        $elements[$key] = $value;
113 114 115 116
      }
      $elements['#sorted'] = TRUE;
    }

117
    return array_keys($child_weights);
118 119 120 121 122 123 124 125 126 127 128 129
  }

  /**
   * 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) {
130
    $visible_children = [];
131 132 133 134 135

    foreach (static::children($elements) as $key) {
      $child = $elements[$key];

      // Skip value and hidden elements, since they are not rendered.
136
      if (!static::isVisibleElement($child)) {
137 138 139 140 141 142 143 144 145
        continue;
      }

      $visible_children[$key] = $child;
    }

    return array_keys($visible_children);
  }

146 147 148 149 150 151 152 153 154 155
  /**
   * Determines if an element is visible.
   *
   * @param array $element
   *   The element to check for visibility.
   *
   * @return bool
   *   TRUE if the element is visible, otherwise FALSE.
   */
  public static function isVisibleElement($element) {
156 157 158
    return (!isset($element['#type']) || !in_array($element['#type'], ['value', 'hidden', 'token']))
      && (!isset($element['#access'])
      || (($element['#access'] instanceof AccessResultInterface && $element['#access']->isAllowed()) || ($element['#access'] === TRUE)));
159 160
  }

161 162 163 164 165 166
  /**
   * Sets HTML attributes based on element properties.
   *
   * @param array $element
   *   The renderable element to process. Passed by reference.
   * @param array $map
167 168 169 170 171
   *   An associative array whose keys are element property names and whose
   *   values are the HTML attribute names to set on the corresponding
   *   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.
172 173 174 175 176 177 178 179 180 181 182 183 184 185
   */
  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];
      }
    }
  }

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
  /**
   * Indicates whether the given element is empty.
   *
   * An element that only has #cache set is considered empty, because it will
   * render to the empty string.
   *
   * @param array $elements
   *   The element.
   *
   * @return bool
   *   Whether the given element is empty.
   */
  public static function isEmpty(array $elements) {
    return empty($elements) || (count($elements) === 1 && array_keys($elements) === ['#cache']);
  }

202
}