Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Template;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\Cache\CacheBackendInterface;
|
Chris@17
|
6 use Drupal\Core\PhpStorage\PhpStorageFactory;
|
Chris@0
|
7 use Drupal\Core\Render\Markup;
|
Chris@0
|
8 use Drupal\Core\State\StateInterface;
|
Chris@0
|
9
|
Chris@0
|
10 /**
|
Chris@0
|
11 * A class that defines a Twig environment for Drupal.
|
Chris@0
|
12 *
|
Chris@0
|
13 * Instances of this class are used to store the configuration and extensions,
|
Chris@0
|
14 * and are used to load templates from the file system or other locations.
|
Chris@0
|
15 *
|
Chris@0
|
16 * @see core\vendor\twig\twig\lib\Twig\Environment.php
|
Chris@0
|
17 */
|
Chris@0
|
18 class TwigEnvironment extends \Twig_Environment {
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@17
|
21 * Key name of the Twig cache prefix metadata key-value pair in State.
|
Chris@17
|
22 */
|
Chris@17
|
23 const CACHE_PREFIX_METADATA_KEY = 'twig_extension_hash_prefix';
|
Chris@17
|
24
|
Chris@17
|
25 /**
|
Chris@17
|
26 * The state service.
|
Chris@17
|
27 *
|
Chris@17
|
28 * @var \Drupal\Core\State\StateInterface
|
Chris@17
|
29 */
|
Chris@17
|
30 protected $state;
|
Chris@17
|
31
|
Chris@17
|
32 /**
|
Chris@0
|
33 * Static cache of template classes.
|
Chris@0
|
34 *
|
Chris@0
|
35 * @var array
|
Chris@0
|
36 */
|
Chris@0
|
37 protected $templateClasses;
|
Chris@0
|
38
|
Chris@18
|
39 /**
|
Chris@18
|
40 * The template cache filename prefix.
|
Chris@18
|
41 *
|
Chris@18
|
42 * @var string
|
Chris@18
|
43 */
|
Chris@0
|
44 protected $twigCachePrefix = '';
|
Chris@0
|
45
|
Chris@0
|
46 /**
|
Chris@0
|
47 * Constructs a TwigEnvironment object and stores cache and storage
|
Chris@0
|
48 * internally.
|
Chris@0
|
49 *
|
Chris@0
|
50 * @param string $root
|
Chris@0
|
51 * The app root.
|
Chris@0
|
52 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
Chris@0
|
53 * The cache bin.
|
Chris@0
|
54 * @param string $twig_extension_hash
|
Chris@0
|
55 * The Twig extension hash.
|
Chris@0
|
56 * @param \Drupal\Core\State\StateInterface $state
|
Chris@0
|
57 * The state service.
|
Chris@0
|
58 * @param \Twig_LoaderInterface $loader
|
Chris@0
|
59 * The Twig loader or loader chain.
|
Chris@0
|
60 * @param array $options
|
Chris@0
|
61 * The options for the Twig environment.
|
Chris@0
|
62 */
|
Chris@18
|
63 public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, array $options = []) {
|
Chris@17
|
64 $this->state = $state;
|
Chris@17
|
65
|
Chris@0
|
66 // Ensure that twig.engine is loaded, given that it is needed to render a
|
Chris@0
|
67 // template because functions like TwigExtension::escapeFilter() are called.
|
Chris@18
|
68 // @todo remove in Drupal 9.0.0 https://www.drupal.org/node/3011393.
|
Chris@0
|
69 require_once $root . '/core/themes/engines/twig/twig.engine';
|
Chris@0
|
70
|
Chris@0
|
71 $this->templateClasses = [];
|
Chris@0
|
72
|
Chris@0
|
73 $options += [
|
Chris@0
|
74 // @todo Ensure garbage collection of expired files.
|
Chris@0
|
75 'cache' => TRUE,
|
Chris@0
|
76 'debug' => FALSE,
|
Chris@0
|
77 'auto_reload' => NULL,
|
Chris@0
|
78 ];
|
Chris@0
|
79 // Ensure autoescaping is always on.
|
Chris@0
|
80 $options['autoescape'] = 'html';
|
Chris@0
|
81 if ($options['cache'] === TRUE) {
|
Chris@17
|
82 $current = $state->get(static::CACHE_PREFIX_METADATA_KEY, ['twig_extension_hash' => '']);
|
Chris@0
|
83 if ($current['twig_extension_hash'] !== $twig_extension_hash || empty($current['twig_cache_prefix'])) {
|
Chris@0
|
84 $current = [
|
Chris@0
|
85 'twig_extension_hash' => $twig_extension_hash,
|
Chris@0
|
86 // Generate a new prefix which invalidates any existing cached files.
|
Chris@0
|
87 'twig_cache_prefix' => uniqid(),
|
Chris@0
|
88
|
Chris@0
|
89 ];
|
Chris@17
|
90 $state->set(static::CACHE_PREFIX_METADATA_KEY, $current);
|
Chris@0
|
91 }
|
Chris@0
|
92 $this->twigCachePrefix = $current['twig_cache_prefix'];
|
Chris@0
|
93
|
Chris@0
|
94 $options['cache'] = new TwigPhpStorageCache($cache, $this->twigCachePrefix);
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@18
|
97 $this->setLoader($loader);
|
Chris@18
|
98 parent::__construct($this->getLoader(), $options);
|
Chris@18
|
99 $policy = new TwigSandboxPolicy();
|
Chris@18
|
100 $sandbox = new \Twig_Extension_Sandbox($policy, TRUE);
|
Chris@18
|
101 $this->addExtension($sandbox);
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 /**
|
Chris@17
|
105 * Invalidates all compiled Twig templates.
|
Chris@17
|
106 *
|
Chris@17
|
107 * @see \drupal_flush_all_caches
|
Chris@17
|
108 */
|
Chris@17
|
109 public function invalidate() {
|
Chris@17
|
110 PhpStorageFactory::get('twig')->deleteAll();
|
Chris@17
|
111 $this->templateClasses = [];
|
Chris@17
|
112 $this->state->delete(static::CACHE_PREFIX_METADATA_KEY);
|
Chris@17
|
113 }
|
Chris@17
|
114
|
Chris@17
|
115 /**
|
Chris@0
|
116 * Get the cache prefixed used by \Drupal\Core\Template\TwigPhpStorageCache
|
Chris@0
|
117 *
|
Chris@0
|
118 * @return string
|
Chris@0
|
119 * The file cache prefix, or empty string if the cache is disabled.
|
Chris@0
|
120 */
|
Chris@0
|
121 public function getTwigCachePrefix() {
|
Chris@0
|
122 return $this->twigCachePrefix;
|
Chris@0
|
123 }
|
Chris@0
|
124
|
Chris@0
|
125 /**
|
Chris@0
|
126 * Gets the template class associated with the given string.
|
Chris@0
|
127 *
|
Chris@0
|
128 * @param string $name
|
Chris@0
|
129 * The name for which to calculate the template class name.
|
Chris@0
|
130 * @param int $index
|
Chris@0
|
131 * The index if it is an embedded template.
|
Chris@0
|
132 *
|
Chris@0
|
133 * @return string
|
Chris@0
|
134 * The template class name.
|
Chris@0
|
135 */
|
Chris@0
|
136 public function getTemplateClass($name, $index = NULL) {
|
Chris@0
|
137 // We override this method to add caching because it gets called multiple
|
Chris@0
|
138 // times when the same template is used more than once. For example, a page
|
Chris@0
|
139 // rendering 50 nodes without any node template overrides will use the same
|
Chris@0
|
140 // node.html.twig for the output of each node and the same compiled class.
|
Chris@0
|
141 $cache_index = $name . (NULL === $index ? '' : '_' . $index);
|
Chris@0
|
142 if (!isset($this->templateClasses[$cache_index])) {
|
Chris@18
|
143 $this->templateClasses[$cache_index] = parent::getTemplateClass($name, $index);
|
Chris@0
|
144 }
|
Chris@0
|
145 return $this->templateClasses[$cache_index];
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 /**
|
Chris@0
|
149 * Renders a twig string directly.
|
Chris@0
|
150 *
|
Chris@0
|
151 * Warning: You should use the render element 'inline_template' together with
|
Chris@0
|
152 * the #template attribute instead of this method directly.
|
Chris@0
|
153 * On top of that you have to ensure that the template string is not dynamic
|
Chris@0
|
154 * but just an ordinary static php string, because there may be installations
|
Chris@0
|
155 * using read-only PHPStorage that want to generate all possible twig
|
Chris@0
|
156 * templates as part of a build step. So it is important that an automated
|
Chris@0
|
157 * script can find the templates and extract them. This is only possible if
|
Chris@0
|
158 * the template is a regular string.
|
Chris@0
|
159 *
|
Chris@0
|
160 * @param string $template_string
|
Chris@0
|
161 * The template string to render with placeholders.
|
Chris@0
|
162 * @param array $context
|
Chris@0
|
163 * An array of parameters to pass to the template.
|
Chris@0
|
164 *
|
Chris@0
|
165 * @return \Drupal\Component\Render\MarkupInterface|string
|
Chris@0
|
166 * The rendered inline template as a Markup object.
|
Chris@0
|
167 *
|
Chris@0
|
168 * @see \Drupal\Core\Template\Loader\StringLoader::exists()
|
Chris@0
|
169 */
|
Chris@0
|
170 public function renderInline($template_string, array $context = []) {
|
Chris@0
|
171 // Prefix all inline templates with a special comment.
|
Chris@0
|
172 $template_string = '{# inline_template_start #}' . $template_string;
|
Chris@18
|
173 return Markup::create($this->createTemplate($template_string)->render($context));
|
Chris@0
|
174 }
|
Chris@0
|
175
|
Chris@0
|
176 }
|