summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/help/src/HelpTwigExtension.php
blob: b8a77a914f6b61d6316c58cc8aef03ef011de914 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php

namespace Drupal\help;

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Url;
use Symfony\Component\Routing\Exception\InvalidParameterException;
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

/**
 * Defines and registers Drupal Twig extensions for rendering help topics.
 *
 * @internal
 *   Tagged services are internal.
 */
class HelpTwigExtension extends AbstractExtension {

  use StringTranslationTrait;

  /**
   * Constructs a \Drupal\help\HelpTwigExtension.
   *
   * @param \Drupal\Core\Access\AccessManagerInterface $accessManager
   *   The access manager.
   * @param \Drupal\help\HelpTopicPluginManagerInterface $pluginManager
   *   The help topic plugin manager service.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   The string translation service.
   */
  public function __construct(protected AccessManagerInterface $accessManager, protected HelpTopicPluginManagerInterface $pluginManager, TranslationInterface $string_translation) {
    $this->stringTranslation = $string_translation;
  }

  /**
   * {@inheritdoc}
   */
  public function getFunctions(): array {
    return [
      new TwigFunction('help_route_link', [$this, 'getRouteLink']),
      new TwigFunction('help_topic_link', [$this, 'getTopicLink']),
    ];
  }

  /**
   * Returns a link or plain text, given text, route name, and parameters.
   *
   * @param string $text
   *   The link text.
   * @param string $route
   *   The name of the route.
   * @param array $parameters
   *   (optional) An associative array of route parameter names and values.
   * @param array $options
   *   (optional) An associative array of additional options. The 'absolute'
   *   option is forced to be TRUE.
   *
   * @return array
   *   A render array with a generated absolute link to the given route. If
   *   the user does not have permission for the route, or an exception occurs,
   *   such as a missing route or missing parameters, the render array is for
   *   the link text as a plain string instead.
   *
   * @see \Drupal\Core\Template\TwigExtension::getUrl()
   */
  public function getRouteLink(string $text, string $route, array $parameters = [], array $options = []): array {
    assert($this->accessManager instanceof AccessManagerInterface, "The access manager hasn't been set up. Any configuration YAML file with a service directive dealing with the Twig configuration can cause this, most likely found in a recently installed or changed module.");

    $bubbles = new BubbleableMetadata();
    $bubbles->addCacheTags(['route_match']);

    try {
      $access_object = $this->accessManager->checkNamedRoute($route, $parameters, NULL, TRUE);
      $bubbles->addCacheableDependency($access_object);

      if ($access_object->isAllowed()) {
        $options['absolute'] = TRUE;
        $url = Url::fromRoute($route, $parameters, $options);
        // Generate the URL to check for parameter problems and collect
        // cache metadata.
        $generated = $url->toString(TRUE);
        $bubbles->addCacheableDependency($generated);
        $build = [
          '#title' => $text,
          '#type' => 'link',
          '#url' => $url,
        ];
      }
      else {
        // If the user doesn't have access, return the link text.
        $build = ['#markup' => $text];
      }
    }
    catch (RouteNotFoundException | MissingMandatoryParametersException | InvalidParameterException) {
      // If the route had one of these exceptions, return the link text.
      $build = ['#markup' => $text];
    }
    $bubbles->applyTo($build);
    return $build;
  }

  /**
   * Returns a link to a help topic, or the title of the topic.
   *
   * @param string $topic_id
   *   The help topic ID.
   *
   * @return array
   *   A render array with a generated absolute link to the given topic. If
   *   the user does not have permission to view the topic, or an exception
   *   occurs, such as the topic not being defined due to a module not being
   *   installed, a default string is returned.
   *
   * @see \Drupal\Core\Template\TwigExtension::getUrl()
   */
  public function getTopicLink(string $topic_id): array {
    assert($this->pluginManager instanceof HelpTopicPluginManagerInterface, "The plugin manager hasn't been set up. Any configuration YAML file with a service directive dealing with the Twig configuration can cause this, most likely found in a recently installed or changed module.");

    $bubbles = new BubbleableMetadata();
    $bubbles->addCacheableDependency($this->pluginManager);
    try {
      $plugin = $this->pluginManager->createInstance($topic_id);
    }
    catch (PluginNotFoundException) {
      // Not a topic.
      $plugin = FALSE;
    }

    if ($plugin) {
      $parameters = ['id' => $topic_id];
      $route = 'help.help_topic';
      $build = $this->getRouteLink($plugin->getLabel(), $route, $parameters);
      $bubbles->addCacheableDependency($plugin);
    }
    else {
      $build = [
        '#markup' => $this->t('Missing help topic %topic', [
          '%topic' => $topic_id,
        ]),
      ];
    }

    $bubbles->applyTo($build);
    return $build;
  }

}