file Element.php

Namespace

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

Classes

Name Description
Element Provides helper methods for Drupal render elements.