Attribute.php 6.64 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Template\Attribute.
6
7
8
9
 */

namespace Drupal\Core\Template;

10
use Drupal\Component\Utility\SafeMarkup;
11
12

/**
13
 * Collects, sanitizes, and renders HTML attributes.
14
 *
15
16
 * To use, optionally pass in an associative array of defined attributes, or
 * add attributes using array syntax. For example:
17
18
19
20
 * @code
 *  $attributes = new Attribute(array('id' => 'socks'));
 *  $attributes['class'] = array('black-cat', 'white-cat');
 *  $attributes['class'][] = 'black-white-cat';
21
 *  echo '<cat' . $attributes . '>';
22
23
24
 *  // Produces <cat id="socks" class="black-cat white-cat black-white-cat">
 * @endcode
 *
25
 * $attributes always prints out all the attributes. For example:
26
27
28
29
 * @code
 *  $attributes = new Attribute(array('id' => 'socks'));
 *  $attributes['class'] = array('black-cat', 'white-cat');
 *  $attributes['class'][] = 'black-white-cat';
30
 *  echo '<cat class="cat ' . $attributes['class'] . '"' . $attributes . '>';
31
 *  // Produces <cat class="cat black-cat white-cat black-white-cat" id="socks" class="cat black-cat white-cat black-white-cat">
32
 * @endcode
33
34
35
36
37
38
39
40
41
42
43
 *
 * When printing out individual attributes to customize them within a Twig
 * template, use the "without" filter to prevent attributes that have already
 * been printed from being printed again. For example:
 * @code
 *  <cat class="{{ attributes.class }} my-custom-class"{{ attributes|without('class') }}>
 *  {# Produces <cat class="cat black-cat white-cat black-white-cat my-custom-class" id="socks"> #}
 * @endcode
 *
 * The attribute keys and values are automatically sanitized for output with
 * \Drupal\Component\Utility\String::checkPlain().
44
 */
45
class Attribute implements \ArrayAccess, \IteratorAggregate {
46
47
48
49
50
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
76
77
78

  /**
   * Stores the attribute data.
   *
   * @var array
   */
  protected $storage = array();

  /**
   * Constructs a \Drupal\Core\Template\Attribute object.
   *
   * @param array $attributes
   *   An associative array of key-value pairs to be converted to attributes.
   */
  public function __construct($attributes = array()) {
    foreach ($attributes as $name => $value) {
      $this->offsetSet($name, $value);
    }
  }

  /**
   * Implements ArrayAccess::offsetGet().
   */
  public function offsetGet($name) {
    if (isset($this->storage[$name])) {
      return $this->storage[$name];
    }
  }

  /**
   * Implements ArrayAccess::offsetSet().
   */
  public function offsetSet($name, $value) {
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
    $this->storage[$name] = $this->createAttributeValue($name, $value);
  }

  /**
   * Creates the different types of attribute values.
   *
   * @param string $name
   *   The attribute name.
   * @param mixed $value
   *   The attribute value.
   *
   * @return \Drupal\Core\Template\AttributeValueBase
   *   An AttributeValueBase representation of the attribute's value.
   */
  protected function createAttributeValue($name, $value) {
94
95
96
97
98
99
100
101
102
103
104
105
    // If the value is already an AttributeValueBase object, return it
    // straight away.
    if ($value instanceOf AttributeValueBase) {
      return $value;
    }
    // An array value or 'class' attribute name are forced to always be an
    // AttributeArray value for consistency.
    if (is_array($value) || $name == 'class') {
      // Cast the value to an array if the value was passed in as a string.
      // @todo Decide to fix all the broken instances of class as a string
      // in core or cast them.
      $value = new AttributeArray($name, (array) $value);
106
107
108
109
110
111
112
    }
    elseif (is_bool($value)) {
      $value = new AttributeBoolean($name, $value);
    }
    elseif (!is_object($value)) {
      $value = new AttributeString($name, $value);
    }
113
    return $value;
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
  }

  /**
   * Implements ArrayAccess::offsetUnset().
   */
  public function offsetUnset($name) {
    unset($this->storage[$name]);
  }

  /**
   * Implements ArrayAccess::offsetExists().
   */
  public function offsetExists($name) {
    return isset($this->storage[$name]);
  }

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  /**
   * Adds argument values by merging them on to array of existing CSS classes.
   *
   * @param string|array ...
   *   CSS classes to add to the class attribute array.
   *
   * @return $this
   */
  public function addClass() {
    $args = func_get_args();
    $classes = array();
    foreach ($args as $arg) {
      // Merge the values passed in from the classes array.
      // The argument is cast to an array to support comma separated single
      // values or one or more array arguments.
      $classes = array_merge($classes, (array) $arg);
    }

    // Merge if there are values, just add them otherwise.
    if (isset($this->storage['class']) && $this->storage['class'] instanceOf AttributeArray) {
      // Merge the values passed in from the class value array.
      $classes = array_merge($this->storage['class']->value(), $classes);
      // Filter out any duplicate values.
      $classes = array_unique($classes);
      $this->storage['class']->exchangeArray($classes);
    }
    else {
      // Filter out any duplicate values.
      $classes = array_unique($classes);
      $this->offsetSet('class', $classes);
    }

    return $this;
  }

  /**
   * Removes argument values from array of existing CSS classes.
   *
   * @param string|array ...
   *   CSS classes to remove from the class attribute array.
   *
   * @return $this
   */
  public function removeClass() {
    // With no class attribute, there is no need to remove.
    if (isset($this->storage['class']) && $this->storage['class'] instanceOf AttributeArray) {
      $args = func_get_args();
      $classes = array();
      foreach ($args as $arg) {
        // Merge the values passed in from the classes array.
        // The argument is cast to an array to support comma separated single
        // values or one or more array arguments.
        $classes = array_merge($classes, (array) $arg);
      }

      // Remove the values passed in from the value array.
      $classes = array_diff($this->storage['class']->value(), $classes);
      $this->storage['class']->exchangeArray($classes);
    }
    return $this;
  }

192
193
194
195
196
197
  /**
   * Implements the magic __toString() method.
   */
  public function __toString() {
    $return = '';
    foreach ($this->storage as $name => $value) {
198
199
200
      $rendered = $value->render();
      if ($rendered) {
        $return .= ' ' . $rendered;
201
202
      }
    }
203
    return SafeMarkup::set($return);
204
205
206
207
208
209
210
  }

  /**
   * Implements the magic __clone() method.
   */
  public function  __clone() {
    foreach ($this->storage as $name => $value) {
211
      $this->storage[$name] = clone $value;
212
213
214
215
216
217
218
    }
  }

  /**
   * Implements IteratorAggregate::getIterator().
   */
  public function getIterator() {
219
    return new \ArrayIterator($this->storage);
220
221
222
223
224
  }

  /**
   * Returns the whole array.
   */
225
226
  public function storage() {
    return $this->storage;
227
228
229
  }

}