PageRenderTime 58ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Template/Attribute.php

https://gitlab.com/geeta7/drupal
PHP | 351 lines | 136 code | 29 blank | 186 comment | 18 complexity | 0a85711c8ca8ac4890122cb8f2e4186d MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\Core\Template\Attribute.
  5. */
  6. namespace Drupal\Core\Template;
  7. use Drupal\Component\Render\PlainTextOutput;
  8. use Drupal\Component\Utility\SafeMarkup;
  9. use Drupal\Component\Render\MarkupInterface;
  10. /**
  11. * Collects, sanitizes, and renders HTML attributes.
  12. *
  13. * To use, optionally pass in an associative array of defined attributes, or
  14. * add attributes using array syntax. For example:
  15. * @code
  16. * $attributes = new Attribute(array('id' => 'socks'));
  17. * $attributes['class'] = array('black-cat', 'white-cat');
  18. * $attributes['class'][] = 'black-white-cat';
  19. * echo '<cat' . $attributes . '>';
  20. * // Produces <cat id="socks" class="black-cat white-cat black-white-cat">
  21. * @endcode
  22. *
  23. * $attributes always prints out all the attributes. For example:
  24. * @code
  25. * $attributes = new Attribute(array('id' => 'socks'));
  26. * $attributes['class'] = array('black-cat', 'white-cat');
  27. * $attributes['class'][] = 'black-white-cat';
  28. * echo '<cat class="cat ' . $attributes['class'] . '"' . $attributes . '>';
  29. * // Produces <cat class="cat black-cat white-cat black-white-cat" id="socks" class="cat black-cat white-cat black-white-cat">
  30. * @endcode
  31. *
  32. * When printing out individual attributes to customize them within a Twig
  33. * template, use the "without" filter to prevent attributes that have already
  34. * been printed from being printed again. For example:
  35. * @code
  36. * <cat class="{{ attributes.class }} my-custom-class"{{ attributes|without('class') }}>
  37. * {# Produces <cat class="cat black-cat white-cat black-white-cat my-custom-class" id="socks"> #}
  38. * @endcode
  39. *
  40. * The attribute keys and values are automatically escaped for output with
  41. * Html::escape(). No protocol filtering is applied, so when using user-entered
  42. * input as a value for an attribute that expects an URI (href, src, ...),
  43. * UrlHelper::stripDangerousProtocols() should be used to ensure dangerous
  44. * protocols (such as 'javascript:') are removed. For example:
  45. * @code
  46. * $path = 'javascript:alert("xss");';
  47. * $path = UrlHelper::stripDangerousProtocols($path);
  48. * $attributes = new Attribute(array('href' => $path));
  49. * echo '<a' . $attributes . '>';
  50. * // Produces <a href="alert(&quot;xss&quot;);">
  51. * @endcode
  52. *
  53. * The attribute values are considered plain text and are treated as such. If a
  54. * safe HTML string is detected, it is converted to plain text with
  55. * PlainTextOutput::renderFromHtml() before being escaped. For example:
  56. * @code
  57. * $value = t('Highlight the @tag tag', ['@tag' => '<em>']);
  58. * $attributes = new Attribute(['value' => $value]);
  59. * echo '<input' . $attributes . '>';
  60. * // Produces <input value="Highlight the &lt;em&gt; tag">
  61. * @endcode
  62. *
  63. * @see \Drupal\Component\Utility\Html::escape()
  64. * @see \Drupal\Component\Render\PlainTextOutput::renderFromHtml()
  65. * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
  66. */
  67. class Attribute implements \ArrayAccess, \IteratorAggregate, MarkupInterface {
  68. /**
  69. * Stores the attribute data.
  70. *
  71. * @var \Drupal\Core\Template\AttributeValueBase[]
  72. */
  73. protected $storage = array();
  74. /**
  75. * Constructs a \Drupal\Core\Template\Attribute object.
  76. *
  77. * @param array $attributes
  78. * An associative array of key-value pairs to be converted to attributes.
  79. */
  80. public function __construct($attributes = array()) {
  81. foreach ($attributes as $name => $value) {
  82. $this->offsetSet($name, $value);
  83. }
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function offsetGet($name) {
  89. if (isset($this->storage[$name])) {
  90. return $this->storage[$name];
  91. }
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function offsetSet($name, $value) {
  97. $this->storage[$name] = $this->createAttributeValue($name, $value);
  98. }
  99. /**
  100. * Creates the different types of attribute values.
  101. *
  102. * @param string $name
  103. * The attribute name.
  104. * @param mixed $value
  105. * The attribute value.
  106. *
  107. * @return \Drupal\Core\Template\AttributeValueBase
  108. * An AttributeValueBase representation of the attribute's value.
  109. */
  110. protected function createAttributeValue($name, $value) {
  111. // If the value is already an AttributeValueBase object,
  112. // return a new instance of the same class, but with the new name.
  113. if ($value instanceof AttributeValueBase) {
  114. $class = get_class($value);
  115. return new $class($name, $value->value());
  116. }
  117. // An array value or 'class' attribute name are forced to always be an
  118. // AttributeArray value for consistency.
  119. if ($name == 'class' && !is_array($value)) {
  120. // Cast the value to string in case it implements MarkupInterface.
  121. $value = [(string) $value];
  122. }
  123. if (is_array($value)) {
  124. // Cast the value to an array if the value was passed in as a string.
  125. // @todo Decide to fix all the broken instances of class as a string
  126. // in core or cast them.
  127. $value = new AttributeArray($name, $value);
  128. }
  129. elseif (is_bool($value)) {
  130. $value = new AttributeBoolean($name, $value);
  131. }
  132. // As a development aid, we allow the value to be a safe string object.
  133. elseif (SafeMarkup::isSafe($value)) {
  134. // Attributes are not supposed to display HTML markup, so we just convert
  135. // the value to plain text.
  136. $value = PlainTextOutput::renderFromHtml($value);
  137. $value = new AttributeString($name, $value);
  138. }
  139. elseif (!is_object($value)) {
  140. $value = new AttributeString($name, $value);
  141. }
  142. return $value;
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function offsetUnset($name) {
  148. unset($this->storage[$name]);
  149. }
  150. /**
  151. * {@inheritdoc}
  152. */
  153. public function offsetExists($name) {
  154. return isset($this->storage[$name]);
  155. }
  156. /**
  157. * Adds classes or merges them on to array of existing CSS classes.
  158. *
  159. * @param string|array ...
  160. * CSS classes to add to the class attribute array.
  161. *
  162. * @return $this
  163. */
  164. public function addClass() {
  165. $args = func_get_args();
  166. if ($args) {
  167. $classes = array();
  168. foreach ($args as $arg) {
  169. // Merge the values passed in from the classes array.
  170. // The argument is cast to an array to support comma separated single
  171. // values or one or more array arguments.
  172. $classes = array_merge($classes, (array) $arg);
  173. }
  174. // Merge if there are values, just add them otherwise.
  175. if (isset($this->storage['class']) && $this->storage['class'] instanceof AttributeArray) {
  176. // Merge the values passed in from the class value array.
  177. $classes = array_merge($this->storage['class']->value(), $classes);
  178. $this->storage['class']->exchangeArray($classes);
  179. }
  180. else {
  181. $this->offsetSet('class', $classes);
  182. }
  183. }
  184. return $this;
  185. }
  186. /**
  187. * Sets values for an attribute key.
  188. *
  189. * @param string $attribute
  190. * Name of the attribute.
  191. * @param string|array $value
  192. * Value(s) to set for the given attribute key.
  193. *
  194. * @return $this
  195. */
  196. public function setAttribute($attribute, $value) {
  197. $this->offsetSet($attribute, $value);
  198. return $this;
  199. }
  200. /**
  201. * Removes an attribute from an Attribute object.
  202. *
  203. * @param string|array ...
  204. * Attributes to remove from the attribute array.
  205. *
  206. * @return $this
  207. */
  208. public function removeAttribute() {
  209. $args = func_get_args();
  210. foreach ($args as $arg) {
  211. // Support arrays or multiple arguments.
  212. if (is_array($arg)) {
  213. foreach ($arg as $value) {
  214. unset($this->storage[$value]);
  215. }
  216. }
  217. else {
  218. unset($this->storage[$arg]);
  219. }
  220. }
  221. return $this;
  222. }
  223. /**
  224. * Removes argument values from array of existing CSS classes.
  225. *
  226. * @param string|array ...
  227. * CSS classes to remove from the class attribute array.
  228. *
  229. * @return $this
  230. */
  231. public function removeClass() {
  232. // With no class attribute, there is no need to remove.
  233. if (isset($this->storage['class']) && $this->storage['class'] instanceof AttributeArray) {
  234. $args = func_get_args();
  235. $classes = array();
  236. foreach ($args as $arg) {
  237. // Merge the values passed in from the classes array.
  238. // The argument is cast to an array to support comma separated single
  239. // values or one or more array arguments.
  240. $classes = array_merge($classes, (array) $arg);
  241. }
  242. // Remove the values passed in from the value array. Use array_values() to
  243. // ensure that the array index remains sequential.
  244. $classes = array_values(array_diff($this->storage['class']->value(), $classes));
  245. $this->storage['class']->exchangeArray($classes);
  246. }
  247. return $this;
  248. }
  249. /**
  250. * Checks if the class array has the given CSS class.
  251. *
  252. * @param string $class
  253. * The CSS class to check for.
  254. *
  255. * @return bool
  256. * Returns TRUE if the class exists, or FALSE otherwise.
  257. */
  258. public function hasClass($class) {
  259. if (isset($this->storage['class']) && $this->storage['class'] instanceof AttributeArray) {
  260. return in_array($class, $this->storage['class']->value());
  261. }
  262. else {
  263. return FALSE;
  264. }
  265. }
  266. /**
  267. * Implements the magic __toString() method.
  268. */
  269. public function __toString() {
  270. $return = '';
  271. /** @var \Drupal\Core\Template\AttributeValueBase $value */
  272. foreach ($this->storage as $name => $value) {
  273. $rendered = $value->render();
  274. if ($rendered) {
  275. $return .= ' ' . $rendered;
  276. }
  277. }
  278. return $return;
  279. }
  280. /**
  281. * Returns all storage elements as an array.
  282. *
  283. * @return array
  284. * An associative array of attributes.
  285. */
  286. public function toArray() {
  287. $return = [];
  288. foreach ($this->storage as $name => $value) {
  289. $return[$name] = $value->value();
  290. }
  291. return $return;
  292. }
  293. /**
  294. * Implements the magic __clone() method.
  295. */
  296. public function __clone() {
  297. foreach ($this->storage as $name => $value) {
  298. $this->storage[$name] = clone $value;
  299. }
  300. }
  301. /**
  302. * {@inheritdoc}
  303. */
  304. public function getIterator() {
  305. return new \ArrayIterator($this->storage);
  306. }
  307. /**
  308. * Returns the whole array.
  309. */
  310. public function storage() {
  311. return $this->storage;
  312. }
  313. /**
  314. * Returns a representation of the object for use in JSON serialization.
  315. *
  316. * @return string
  317. * The safe string content.
  318. */
  319. public function jsonSerialize() {
  320. return (string) $this;
  321. }
  322. }