PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/core/lib/Drupal/Core/Render/Element/Link.php

http://github.com/drupal/drupal
PHP | 204 lines | 59 code | 10 blank | 135 comment | 11 complexity | dee419b8cb70b398736be24df5d4bd2e MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Core\Render\Element;
  3. use Drupal\Component\Utility\NestedArray;
  4. use Drupal\Component\Utility\Html as HtmlUtility;
  5. use Drupal\Core\Render\BubbleableMetadata;
  6. use Drupal\Core\Render\Element;
  7. use Drupal\Core\Url as CoreUrl;
  8. /**
  9. * Provides a link render element.
  10. *
  11. * Properties:
  12. * - #title: The link text.
  13. * - #url: \Drupal\Core\Url object containing URL information pointing to a
  14. * internal or external link. See \Drupal\Core\Utility\LinkGeneratorInterface.
  15. *
  16. * Usage example:
  17. * @code
  18. * $build['examples_link'] = [
  19. * '#title' => $this->t('Examples'),
  20. * '#type' => 'link',
  21. * '#url' => \Drupal\Core\Url::fromRoute('examples.description')
  22. * ];
  23. * @endcode
  24. *
  25. * @RenderElement("link")
  26. */
  27. class Link extends RenderElement {
  28. /**
  29. * {@inheritdoc}
  30. */
  31. public function getInfo() {
  32. $class = get_class($this);
  33. return [
  34. '#pre_render' => [
  35. [$class, 'preRenderLink'],
  36. ],
  37. ];
  38. }
  39. /**
  40. * Pre-render callback: Renders a link into #markup.
  41. *
  42. * Doing so during pre_render gives modules a chance to alter the link parts.
  43. *
  44. * @param array $element
  45. * A structured array whose keys form the arguments to
  46. * \Drupal\Core\Utility\LinkGeneratorInterface::generate():
  47. * - #title: The link text.
  48. * - #url: The URL info either pointing to a route or a non routed path.
  49. * - #options: (optional) An array of options to pass to the link generator.
  50. *
  51. * @return array
  52. * The passed-in element containing a rendered link in '#markup'.
  53. */
  54. public static function preRenderLink($element) {
  55. // By default, link options to pass to the link generator are normally set
  56. // in #options.
  57. $element += ['#options' => []];
  58. // However, within the scope of renderable elements, #attributes is a valid
  59. // way to specify attributes, too. Take them into account, but do not override
  60. // attributes from #options.
  61. if (isset($element['#attributes'])) {
  62. $element['#options'] += ['attributes' => []];
  63. $element['#options']['attributes'] += $element['#attributes'];
  64. }
  65. // This #pre_render callback can be invoked from inside or outside of a Form
  66. // API context, and depending on that, a HTML ID may be already set in
  67. // different locations. #options should have precedence over Form API's #id.
  68. // #attributes have been taken over into #options above already.
  69. if (isset($element['#options']['attributes']['id'])) {
  70. $element['#id'] = $element['#options']['attributes']['id'];
  71. }
  72. elseif (isset($element['#id'])) {
  73. $element['#options']['attributes']['id'] = $element['#id'];
  74. }
  75. // Conditionally invoke self::preRenderAjaxForm(), if #ajax is set.
  76. if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) {
  77. // If no HTML ID was found above, automatically create one.
  78. if (!isset($element['#id'])) {
  79. $element['#id'] = $element['#options']['attributes']['id'] = HtmlUtility::getUniqueId('ajax-link');
  80. }
  81. $element = static::preRenderAjaxForm($element);
  82. }
  83. if (!empty($element['#url']) && $element['#url'] instanceof CoreUrl) {
  84. $options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']);
  85. /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
  86. $link_generator = \Drupal::service('link_generator');
  87. $generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options));
  88. $element['#markup'] = $generated_link;
  89. $generated_link->merge(BubbleableMetadata::createFromRenderArray($element))
  90. ->applyTo($element);
  91. }
  92. return $element;
  93. }
  94. /**
  95. * Pre-render callback: Collects child links into a single array.
  96. *
  97. * This method can be added as a pre_render callback for a renderable array,
  98. * usually one which will be themed by links.html.twig. It iterates through
  99. * all unrendered children of the element, collects any #links properties it
  100. * finds, merges them into the parent element's #links array, and prevents
  101. * those children from being rendered separately.
  102. *
  103. * The purpose of this is to allow links to be logically grouped into related
  104. * categories, so that each child group can be rendered as its own list of
  105. * links if drupal_render() is called on it, but calling drupal_render() on
  106. * the parent element will still produce a single list containing all the
  107. * remaining links, regardless of what group they were in.
  108. *
  109. * A typical example comes from node links, which are stored in a renderable
  110. * array similar to this:
  111. * @code
  112. * $build['links'] = array(
  113. * '#theme' => 'links__node',
  114. * '#pre_render' => array(Link::class, 'preRenderLinks'),
  115. * 'comment' => array(
  116. * '#theme' => 'links__node__comment',
  117. * '#links' => array(
  118. * // An array of links associated with node comments, suitable for
  119. * // passing in to links.html.twig.
  120. * ),
  121. * ),
  122. * 'statistics' => array(
  123. * '#theme' => 'links__node__statistics',
  124. * '#links' => array(
  125. * // An array of links associated with node statistics, suitable for
  126. * // passing in to links.html.twig.
  127. * ),
  128. * ),
  129. * 'translation' => array(
  130. * '#theme' => 'links__node__translation',
  131. * '#links' => array(
  132. * // An array of links associated with node translation, suitable for
  133. * // passing in to links.html.twig.
  134. * ),
  135. * ),
  136. * );
  137. * @endcode
  138. *
  139. * In this example, the links are grouped by functionality, which can be
  140. * helpful to themers who want to display certain kinds of links
  141. * independently. For example, adding this code to node.html.twig will result
  142. * in the comment links being rendered as a single list:
  143. * @code
  144. * {{ content.links.comment }}
  145. * @endcode
  146. *
  147. * (where a node's content has been transformed into $content before handing
  148. * control to the node.html.twig template).
  149. *
  150. * The preRenderLinks method defined here allows the above flexibility, but
  151. * also allows the following code to be used to render all remaining links
  152. * into a single list, regardless of their group:
  153. * @code
  154. * {{ content.links }}
  155. * @endcode
  156. *
  157. * In the above example, this will result in the statistics and translation
  158. * links being rendered together in a single list (but not the comment links,
  159. * which were rendered previously on their own).
  160. *
  161. * Because of the way this method works, the individual properties of each
  162. * group (for example, a group-specific #theme property such as
  163. * 'links__node__comment' in the example above, or any other property such as
  164. * #attributes or #pre_render that is attached to it) are only used when that
  165. * group is rendered on its own. When the group is rendered together with
  166. * other children, these child-specific properties are ignored, and only the
  167. * overall properties of the parent are used.
  168. *
  169. * @param array $element
  170. * Render array containing child links to group.
  171. *
  172. * @return array
  173. * Render array containing child links grouped into a single array.
  174. */
  175. public static function preRenderLinks($element) {
  176. $element += ['#links' => [], '#attached' => []];
  177. foreach (Element::children($element) as $key) {
  178. $child = &$element[$key];
  179. // If the child has links which have not been printed yet and the user has
  180. // access to it, merge its links in to the parent.
  181. if (isset($child['#links']) && empty($child['#printed']) && Element::isVisibleElement($child)) {
  182. $element['#links'] += $child['#links'];
  183. // Mark the child as having been printed already (so that its links
  184. // cannot be mistakenly rendered twice).
  185. $child['#printed'] = TRUE;
  186. }
  187. // Merge attachments.
  188. if (isset($child['#attached'])) {
  189. $element['#attached'] = BubbleableMetadata::mergeAttachments($element['#attached'], $child['#attached']);
  190. }
  191. }
  192. return $element;
  193. }
  194. }