f0422265b661885816adb81a45bd28dba554696d
[yaffs-website] / web / themes / contrib / bootstrap / src / Utility / Element.php
1 <?php
2
3 namespace Drupal\bootstrap\Utility;
4
5 use Drupal\bootstrap\Bootstrap;
6 use Drupal\Component\Render\FormattableMarkup;
7 use Drupal\Component\Render\MarkupInterface;
8 use Drupal\Component\Utility\Xss;
9 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\Core\Render\Element as CoreElement;
11
12 /**
13  * Provides helper methods for Drupal render elements.
14  *
15  * @ingroup utility
16  *
17  * @see \Drupal\Core\Render\Element
18  */
19 class Element extends DrupalAttributes {
20
21   /**
22    * The current state of the form.
23    *
24    * @var \Drupal\Core\Form\FormStateInterface
25    */
26   protected $formState;
27
28   /**
29    * The element type.
30    *
31    * @var string
32    */
33   protected $type = FALSE;
34
35   /**
36    * {@inheritdoc}
37    */
38   protected $attributePrefix = '#';
39
40   /**
41    * Element constructor.
42    *
43    * @param array|string $element
44    *   A render array element.
45    * @param \Drupal\Core\Form\FormStateInterface $form_state
46    *   The current state of the form.
47    */
48   public function __construct(&$element = [], FormStateInterface $form_state = NULL) {
49     if (!is_array($element)) {
50       $element = ['#markup' => $element instanceof MarkupInterface ? $element : new FormattableMarkup($element, [])];
51     }
52     $this->array = &$element;
53     $this->formState = $form_state;
54   }
55
56   /**
57    * Magic get method.
58    *
59    * This is only for child elements, not properties.
60    *
61    * @param string $key
62    *   The name of the child element to retrieve.
63    *
64    * @return \Drupal\bootstrap\Utility\Element
65    *   The child element object.
66    *
67    * @throws \InvalidArgumentException
68    *   Throws this error when the name is a property (key starting with #).
69    */
70   public function &__get($key) {
71     if (CoreElement::property($key)) {
72       throw new \InvalidArgumentException('Cannot dynamically retrieve element property. Please use \Drupal\bootstrap\Utility\Element::getProperty instead.');
73     }
74     $instance = new self($this->offsetGet($key, []));
75     return $instance;
76   }
77
78   /**
79    * Magic set method.
80    *
81    * This is only for child elements, not properties.
82    *
83    * @param string $key
84    *   The name of the child element to set.
85    * @param mixed $value
86    *   The value of $name to set.
87    *
88    * @throws \InvalidArgumentException
89    *   Throws this error when the name is a property (key starting with #).
90    */
91   public function __set($key, $value) {
92     if (CoreElement::property($key)) {
93       throw new \InvalidArgumentException('Cannot dynamically retrieve element property. Use \Drupal\bootstrap\Utility\Element::setProperty instead.');
94     }
95     $this->offsetSet($key, ($value instanceof Element ? $value->getArray() : $value));
96   }
97
98   /**
99    * Magic isset method.
100    *
101    * This is only for child elements, not properties.
102    *
103    * @param string $name
104    *   The name of the child element to check.
105    *
106    * @return bool
107    *   TRUE or FALSE
108    *
109    * @throws \InvalidArgumentException
110    *   Throws this error when the name is a property (key starting with #).
111    */
112   public function __isset($name) {
113     if (CoreElement::property($name)) {
114       throw new \InvalidArgumentException('Cannot dynamically check if an element has a property. Use \Drupal\bootstrap\Utility\Element::unsetProperty instead.');
115     }
116     return parent::__isset($name);
117   }
118
119   /**
120    * Magic unset method.
121    *
122    * This is only for child elements, not properties.
123    *
124    * @param mixed $name
125    *   The name of the child element to unset.
126    *
127    * @throws \InvalidArgumentException
128    *   Throws this error when the name is a property (key starting with #).
129    */
130   public function __unset($name) {
131     if (CoreElement::property($name)) {
132       throw new \InvalidArgumentException('Cannot dynamically unset an element property. Use \Drupal\bootstrap\Utility\Element::hasProperty instead.');
133     }
134     parent::__unset($name);
135   }
136
137   /**
138    * Appends a property with a value.
139    *
140    * @param string $name
141    *   The name of the property to set.
142    * @param mixed $value
143    *   The value of the property to set.
144    *
145    * @return $this
146    */
147   public function appendProperty($name, $value) {
148     $property = &$this->getProperty($name);
149     $value = $value instanceof Element ? $value->getArray() : $value;
150
151     // If property isn't set, just set it.
152     if (!isset($property)) {
153       $property = $value;
154       return $this;
155     }
156
157     if (is_array($property)) {
158       $property[] = Element::create($value)->getArray();
159     }
160     else {
161       $property .= (string) $value;
162     }
163
164     return $this;
165   }
166
167   /**
168    * Identifies the children of an element array, optionally sorted by weight.
169    *
170    * The children of a element array are those key/value pairs whose key does
171    * not start with a '#'. See drupal_render() for details.
172    *
173    * @param bool $sort
174    *   Boolean to indicate whether the children should be sorted by weight.
175    *
176    * @return array
177    *   The array keys of the element's children.
178    */
179   public function childKeys($sort = FALSE) {
180     return CoreElement::children($this->array, $sort);
181   }
182
183   /**
184    * Retrieves the children of an element array, optionally sorted by weight.
185    *
186    * The children of a element array are those key/value pairs whose key does
187    * not start with a '#'. See drupal_render() for details.
188    *
189    * @param bool $sort
190    *   Boolean to indicate whether the children should be sorted by weight.
191    *
192    * @return \Drupal\bootstrap\Utility\Element[]
193    *   An array child elements.
194    */
195   public function children($sort = FALSE) {
196     $children = [];
197     foreach ($this->childKeys($sort) as $child) {
198       $children[$child] = new self($this->array[$child]);
199     }
200     return $children;
201   }
202
203   /**
204    * Adds a specific Bootstrap class to color a button based on its text value.
205    *
206    * @param bool $override
207    *   Flag determining whether or not to override any existing set class.
208    *
209    * @return $this
210    */
211   public function colorize($override = TRUE) {
212     $button = $this->isButton();
213
214     // @todo refactor this more so it's not just "button" specific.
215     $prefix = $button ? 'btn' : 'has';
216
217     // List of classes, based on the prefix.
218     $classes = [
219       "$prefix-primary", "$prefix-success", "$prefix-info",
220       "$prefix-warning", "$prefix-danger", "$prefix-link",
221       // Default should be last.
222       "$prefix-default",
223     ];
224
225     // Set the class to "btn-default" if it shouldn't be colorized.
226     $class = $button && !Bootstrap::getTheme()->getSetting('button_colorize') ? 'btn-default' : FALSE;
227
228     // Search for an existing class.
229     if (!$class || !$override) {
230       foreach ($classes as $value) {
231         if ($this->hasClass($value)) {
232           $class = $value;
233           break;
234         }
235       }
236     }
237
238     // Find a class based on the value of "value", "title" or "button_type".
239     if (!$class) {
240       $value = $this->getProperty('value', $this->getProperty('title', ''));
241       $class = "$prefix-" . Bootstrap::cssClassFromString($value, $button ? $this->getProperty('button_type', 'default') : 'default');
242     }
243
244     // Remove any existing classes and add the specified class.
245     if ($class) {
246       $this->removeClass($classes)->addClass($class);
247       if ($button && $this->getProperty('split')) {
248         $this->removeClass($classes, $this::SPLIT_BUTTON)->addClass($class, $this::SPLIT_BUTTON);
249       }
250     }
251
252     return $this;
253   }
254
255   /**
256    * Creates a new \Drupal\bootstrap\Utility\Element instance.
257    *
258    * @param array|string $element
259    *   A render array element or a string.
260    * @param \Drupal\Core\Form\FormStateInterface $form_state
261    *   A current FormState instance, if any.
262    *
263    * @return \Drupal\bootstrap\Utility\Element
264    *   The newly created element instance.
265    */
266   public static function create(&$element = [], FormStateInterface $form_state = NULL) {
267     return $element instanceof self ? $element : new self($element, $form_state);
268   }
269
270   /**
271    * Creates a new standalone \Drupal\bootstrap\Utility\Element instance.
272    *
273    * It does not reference the original element passed. If an Element instance
274    * is passed, it will clone it so it doesn't affect the original element.
275    *
276    * @param array|string|\Drupal\bootstrap\Utility\Element $element
277    *   A render array element, string or Element instance.
278    * @param \Drupal\Core\Form\FormStateInterface $form_state
279    *   A current FormState instance, if any.
280    *
281    * @return \Drupal\bootstrap\Utility\Element
282    *   The newly created element instance.
283    */
284   public static function createStandalone($element = [], FormStateInterface $form_state = NULL) {
285     // Immediately return a cloned version if element is already an Element.
286     if ($element instanceof self) {
287       return clone $element;
288     }
289     $standalone = is_object($element) ? clone $element : $element;
290     return static::create($standalone, $form_state);
291   }
292
293   /**
294    * {@inheritdoc}
295    */
296   public function exchangeArray($data) {
297     $old = parent::exchangeArray($data);
298     return $old;
299   }
300
301   /**
302    * Traverses the element to find the closest button.
303    *
304    * @return \Drupal\bootstrap\Utility\Element|false
305    *   The first button element or FALSE if no button could be found.
306    */
307   public function &findButton() {
308     $button = FALSE;
309     foreach ($this->children() as $child) {
310       if ($child->isButton()) {
311         $button = $child;
312         break;
313       }
314       if ($result = &$child->findButton()) {
315         $button = $result;
316         break;
317       }
318     }
319     return $button;
320   }
321
322   /**
323    * Retrieves the render array for the element.
324    *
325    * @return array
326    *   The element render array, passed by reference.
327    */
328   public function &getArray() {
329     return $this->array;
330   }
331
332   /**
333    * Retrieves a context value from the #context element property, if any.
334    *
335    * @param string $name
336    *   The name of the context key to retrieve.
337    * @param mixed $default
338    *   Optional. The default value to use if the context $name isn't set.
339    *
340    * @return mixed|null
341    *   The context value or the $default value if not set.
342    */
343   public function &getContext($name, $default = NULL) {
344     $context = &$this->getProperty('context', []);
345     if (!isset($context[$name])) {
346       $context[$name] = $default;
347     }
348     return $context[$name];
349   }
350
351   /**
352    * Returns the error message filed against the given form element.
353    *
354    * Form errors higher up in the form structure override deeper errors as well
355    * as errors on the element itself.
356    *
357    * @return string|null
358    *   Either the error message for this element or NULL if there are no errors.
359    *
360    * @throws \BadMethodCallException
361    *   When the element instance was not constructed with a valid form state
362    *   object.
363    */
364   public function getError() {
365     if (!$this->formState) {
366       throw new \BadMethodCallException('The element instance must be constructed with a valid form state object to use this method.');
367     }
368     return $this->formState->getError($this->array);
369   }
370
371   /**
372    * Retrieves the render array for the element.
373    *
374    * @param string $name
375    *   The name of the element property to retrieve, not including the # prefix.
376    * @param mixed $default
377    *   The default to set if property does not exist.
378    *
379    * @return mixed
380    *   The property value, NULL if not set.
381    */
382   public function &getProperty($name, $default = NULL) {
383     return $this->offsetGet("#$name", $default);
384   }
385
386   /**
387    * Returns the visible children of an element.
388    *
389    * @return array
390    *   The array keys of the element's visible children.
391    */
392   public function getVisibleChildren() {
393     return CoreElement::getVisibleChildren($this->array);
394   }
395
396   /**
397    * Indicates whether the element has an error set.
398    *
399    * @throws \BadMethodCallException
400    *   When the element instance was not constructed with a valid form state
401    *   object.
402    */
403   public function hasError() {
404     $error = $this->getError();
405     return isset($error);
406   }
407
408   /**
409    * Indicates whether the element has a specific property.
410    *
411    * @param string $name
412    *   The property to check.
413    */
414   public function hasProperty($name) {
415     return $this->offsetExists("#$name");
416   }
417
418   /**
419    * Indicates whether the element is a button.
420    *
421    * @return bool
422    *   TRUE or FALSE.
423    */
424   public function isButton() {
425     $button_types = ['button', 'submit', 'reset', 'image_button'];
426     return !empty($this->array['#is_button']) || $this->isType($button_types) || $this->hasClass('btn');
427   }
428
429   /**
430    * Indicates whether the given element is empty.
431    *
432    * An element that only has #cache set is considered empty, because it will
433    * render to the empty string.
434    *
435    * @return bool
436    *   Whether the given element is empty.
437    */
438   public function isEmpty() {
439     return CoreElement::isEmpty($this->array);
440   }
441
442   /**
443    * Indicates whether a property on the element is empty.
444    *
445    * @param string $name
446    *   The property to check.
447    *
448    * @return bool
449    *   Whether the given property on the element is empty.
450    */
451   public function isPropertyEmpty($name) {
452     return $this->hasProperty($name) && empty($this->getProperty($name));
453   }
454
455   /**
456    * Checks if a value is a render array.
457    *
458    * @param mixed $value
459    *   The value to check.
460    *
461    * @return bool
462    *   TRUE if the given value is a render array, otherwise FALSE.
463    */
464   public static function isRenderArray($value) {
465     return is_array($value) && (isset($value['#type']) ||
466       isset($value['#theme']) || isset($value['#theme_wrappers']) ||
467       isset($value['#markup']) || isset($value['#attached']) ||
468       isset($value['#cache']) || isset($value['#lazy_builder']) ||
469       isset($value['#create_placeholder']) || isset($value['#pre_render']) ||
470       isset($value['#post_render']) || isset($value['#process']));
471   }
472
473   /**
474    * Checks if the element is a specific type of element.
475    *
476    * @param string|array $type
477    *   The element type(s) to check.
478    *
479    * @return bool
480    *   TRUE if element is or one of $type.
481    */
482   public function isType($type) {
483     $property = $this->getProperty('type');
484     return $property && in_array($property, (is_array($type) ? $type : [$type]));
485   }
486
487   /**
488    * Determines if an element is visible.
489    *
490    * @return bool
491    *   TRUE if the element is visible, otherwise FALSE.
492    */
493   public function isVisible() {
494     return CoreElement::isVisibleElement($this->array);
495   }
496
497   /**
498    * Maps an element's properties to its attributes array.
499    *
500    * @param array $map
501    *   An associative array whose keys are element property names and whose
502    *   values are the HTML attribute names to set on the corresponding
503    *   property; e.g., array('#propertyname' => 'attributename'). If both names
504    *   are identical except for the leading '#', then an attribute name value is
505    *   sufficient and no property name needs to be specified.
506    *
507    * @return $this
508    */
509   public function map(array $map) {
510     CoreElement::setAttributes($this->array, $map);
511     return $this;
512   }
513
514   /**
515    * Prepends a property with a value.
516    *
517    * @param string $name
518    *   The name of the property to set.
519    * @param mixed $value
520    *   The value of the property to set.
521    *
522    * @return $this
523    */
524   public function prependProperty($name, $value) {
525     $property = &$this->getProperty($name);
526     $value = $value instanceof Element ? $value->getArray() : $value;
527
528     // If property isn't set, just set it.
529     if (!isset($property)) {
530       $property = $value;
531       return $this;
532     }
533
534     if (is_array($property)) {
535       array_unshift($property, Element::create($value)->getArray());
536     }
537     else {
538       $property = (string) $value . (string) $property;
539     }
540
541     return $this;
542   }
543
544   /**
545    * Gets properties of a structured array element (keys beginning with '#').
546    *
547    * @return array
548    *   An array of property keys for the element.
549    */
550   public function properties() {
551     return CoreElement::properties($this->array);
552   }
553
554   /**
555    * Renders the final element HTML.
556    *
557    * @return \Drupal\Component\Render\MarkupInterface
558    *   The rendered HTML.
559    */
560   public function render() {
561     /** @var \Drupal\Core\Render\Renderer $renderer */
562     $renderer = \Drupal::service('renderer');
563     return $renderer->render($this->array);
564   }
565
566   /**
567    * Renders the final element HTML.
568    *
569    * @return \Drupal\Component\Render\MarkupInterface
570    *   The rendered HTML.
571    */
572   public function renderPlain() {
573     /** @var \Drupal\Core\Render\Renderer $renderer */
574     $renderer = \Drupal::service('renderer');
575     return $renderer->renderPlain($this->array);
576   }
577
578   /**
579    * Renders the final element HTML.
580    *
581    * (Cannot be executed within another render context.)
582    *
583    * @return \Drupal\Component\Render\MarkupInterface
584    *   The rendered HTML.
585    */
586   public function renderRoot() {
587     /** @var \Drupal\Core\Render\Renderer $renderer */
588     $renderer = \Drupal::service('renderer');
589     return $renderer->renderRoot($this->array);
590   }
591
592   /**
593    * Adds Bootstrap button size class to the element.
594    *
595    * @param string $class
596    *   The full button size class to add. If none is provided, it will default
597    *   to any set theme setting.
598    * @param bool $override
599    *   Flag indicating if the passed $class should be forcibly set. Setting
600    *   this to FALSE allows any existing set class to persist.
601    *
602    * @return $this
603    */
604   public function setButtonSize($class = NULL, $override = TRUE) {
605     // Immediately return if element is not a button.
606     if (!$this->isButton()) {
607       return $this;
608     }
609
610     // Retrieve the button size classes from the specific setting's options.
611     static $classes;
612     if (!isset($classes)) {
613       $classes = [];
614       if ($button_size = Bootstrap::getTheme()->getSettingPlugin('button_size')) {
615         $classes = array_keys($button_size->getOptions());
616       }
617     }
618
619     // Search for an existing class.
620     if (!$class || !$override) {
621       foreach ($classes as $value) {
622         if ($this->hasClass($value)) {
623           $class = $value;
624           break;
625         }
626       }
627     }
628
629     // Attempt to get the default button size, if set.
630     if (!$class) {
631       $class = Bootstrap::getTheme()->getSetting('button_size');
632     }
633
634     // Remove any existing classes and add the specified class.
635     if ($class) {
636       $this->removeClass($classes)->addClass($class);
637       if ($this->getProperty('split')) {
638         $this->removeClass($classes, $this::SPLIT_BUTTON)->addClass($class, $this::SPLIT_BUTTON);
639       }
640     }
641
642     return $this;
643   }
644
645   /**
646    * Flags an element as having an error.
647    *
648    * @param string $message
649    *   (optional) The error message to present to the user.
650    *
651    * @return $this
652    *
653    * @throws \BadMethodCallException
654    *   When the element instance was not constructed with a valid form state
655    *   object.
656    */
657   public function setError($message = '') {
658     if (!$this->formState) {
659       throw new \BadMethodCallException('The element instance must be constructed with a valid form state object to use this method.');
660     }
661     $this->formState->setError($this->array, $message);
662     return $this;
663   }
664
665   /**
666    * Adds an icon to button element based on its text value.
667    *
668    * @param array $icon
669    *   An icon render array.
670    *
671    * @return $this
672    *
673    * @see \Drupal\bootstrap\Bootstrap::glyphicon()
674    */
675   public function setIcon(array $icon = NULL) {
676     if ($this->isButton() && !Bootstrap::getTheme()->getSetting('button_iconize')) {
677       return $this;
678     }
679     if ($value = $this->getProperty('value', $this->getProperty('title'))) {
680       $icon = isset($icon) ? $icon : Bootstrap::glyphiconFromString($value);
681       $this->setProperty('icon', $icon);
682     }
683     return $this;
684   }
685
686   /**
687    * Sets the value for a property.
688    *
689    * @param string $name
690    *   The name of the property to set.
691    * @param mixed $value
692    *   The value of the property to set.
693    * @param bool $recurse
694    *   Flag indicating wither to set the same property on child elements.
695    *
696    * @return $this
697    */
698   public function setProperty($name, $value, $recurse = FALSE) {
699     $this->array["#$name"] = $value instanceof Element ? $value->getArray() : $value;
700     if ($recurse) {
701       foreach ($this->children() as $child) {
702         $child->setProperty($name, $value, $recurse);
703       }
704     }
705     return $this;
706   }
707
708   /**
709    * Converts an element description into a tooltip based on certain criteria.
710    *
711    * @param array|\Drupal\bootstrap\Utility\Element|null $target_element
712    *   The target element render array the tooltip is to be attached to, passed
713    *   by reference or an existing Element object. If not set, it will default
714    *   this Element instance.
715    * @param bool $input_only
716    *   Toggle determining whether or not to only convert input elements.
717    * @param int $length
718    *   The length of characters to determine if description is "simple".
719    *
720    * @return $this
721    */
722   public function smartDescription(&$target_element = NULL, $input_only = TRUE, $length = NULL) {
723     static $theme;
724     if (!isset($theme)) {
725       $theme = Bootstrap::getTheme();
726     }
727
728     // Determine if tooltips are enabled.
729     static $enabled;
730     if (!isset($enabled)) {
731       $enabled = $theme->getSetting('tooltip_enabled') && $theme->getSetting('forms_smart_descriptions');
732     }
733
734     // Immediately return if tooltip descriptions are not enabled.
735     if (!$enabled) {
736       return $this;
737     }
738
739     // Allow a different element to attach the tooltip.
740     /** @var \Drupal\bootstrap\Utility\Element $target */
741     if (is_object($target_element) && $target_element instanceof self) {
742       $target = $target_element;
743     }
744     elseif (isset($target_element) && is_array($target_element)) {
745       $target = new self($target_element, $this->formState);
746     }
747     else {
748       $target = $this;
749     }
750
751     // For "password_confirm" element types, move the target to the first
752     // textfield.
753     if ($target->isType('password_confirm')) {
754       $target = $target->pass1;
755     }
756
757     // Retrieve the length limit for smart descriptions.
758     if (!isset($length)) {
759       // Disable length checking by setting it to FALSE if empty.
760       $length = (int) $theme->getSetting('forms_smart_descriptions_limit') ?: FALSE;
761     }
762
763     // Retrieve the allowed tags for smart descriptions. This is primarily used
764     // for display purposes only (i.e. non-UI/UX related elements that wouldn't
765     // require a user to "click", like a link). Disable length checking by
766     // setting it to FALSE if empty.
767     static $allowed_tags;
768     if (!isset($allowed_tags)) {
769       $allowed_tags = array_filter(array_unique(array_map('trim', explode(',', $theme->getSetting('forms_smart_descriptions_allowed_tags') . '')))) ?: FALSE;
770     }
771
772     // Return if element or target shouldn't have "simple" tooltip descriptions.
773     $html = FALSE;
774
775     // If the description is a render array, it must first be pre-rendered so
776     // it can be later passed to Unicode::isSimple() if needed.
777     $description = $this->hasProperty('description') ? $this->getProperty('description') : FALSE;
778     if (static::isRenderArray($description)) {
779       $description = static::createStandalone($description)->renderPlain();
780     }
781
782     if (
783       // Ignore if element has no #description.
784       !$description
785
786       // Ignore if description is not a simple string or MarkupInterface.
787       || (!is_string($description) && !($description instanceof MarkupInterface))
788
789       // Ignore if element is not an input.
790       || ($input_only && !$target->hasProperty('input'))
791
792       // Ignore if the target element already has a "data-toggle" attribute set.
793       || $target->hasAttribute('data-toggle')
794
795       // Ignore if the target element is #disabled.
796       || $target->hasProperty('disabled')
797
798       // Ignore if either the actual element or target element has an explicit
799       // #smart_description property set to FALSE.
800       || !$this->getProperty('smart_description', TRUE)
801       || !$target->getProperty('smart_description', TRUE)
802
803       // Ignore if the description is not "simple".
804       || !Unicode::isSimple($description, $length, $allowed_tags, $html)
805     ) {
806       // Set the both the actual element and the target element
807       // #smart_description property to FALSE.
808       $this->setProperty('smart_description', FALSE);
809       $target->setProperty('smart_description', FALSE);
810       return $this;
811     }
812
813     // Default attributes type.
814     $type = DrupalAttributes::ATTRIBUTES;
815
816     // Use #label_attributes for 'checkbox' and 'radio' elements.
817     if ($this->isType(['checkbox', 'radio'])) {
818       $type = DrupalAttributes::LABEL;
819     }
820     // Use #wrapper_attributes for 'checkboxes' and 'radios' elements.
821     elseif ($this->isType(['checkboxes', 'radios'])) {
822       $type = DrupalAttributes::WRAPPER;
823     }
824
825     // Retrieve the proper attributes array.
826     $attributes = $target->getAttributes($type);
827
828     // Set the tooltip attributes.
829     $attributes['title'] = $allowed_tags !== FALSE ? Xss::filter((string) $description, $allowed_tags) : $description;
830     $attributes['data-toggle'] = 'tooltip';
831     if ($html || $allowed_tags === FALSE) {
832       $attributes['data-html'] = 'true';
833     }
834
835     // Remove the element description so it isn't (re-)rendered later.
836     $this->unsetProperty('description');
837
838     return $this;
839   }
840
841   /**
842    * Removes a property from the element.
843    *
844    * @param string $name
845    *   The name of the property to unset.
846    *
847    * @return $this
848    */
849   public function unsetProperty($name) {
850     unset($this->array["#$name"]);
851     return $this;
852   }
853
854 }