MediaWiki  1.34.0
TemplateParser.php
Go to the documentation of this file.
1 <?php
2 
4 
26 class TemplateParser {
30  protected $templateDir;
31 
35  protected $renderers;
36 
40  protected $forceRecompile = false;
41 
45  protected $compileFlags;
46 
51  public function __construct( $templateDir = null, $forceRecompile = false ) {
52  $this->templateDir = $templateDir ?: __DIR__ . '/templates';
53  $this->forceRecompile = $forceRecompile;
54 
55  // Do not add more flags here without discussion.
56  // If you do add more flags, be sure to update unit tests as well.
57  $this->compileFlags = LightnCandy::FLAG_ERROR_EXCEPTION | LightnCandy::FLAG_MUSTACHELOOKUP;
58  }
59 
64  public function enableRecursivePartials( $enable ) {
65  if ( $enable ) {
66  $this->compileFlags |= LightnCandy::FLAG_RUNTIMEPARTIAL;
67  } else {
68  $this->compileFlags &= ~LightnCandy::FLAG_RUNTIMEPARTIAL;
69  }
70  }
71 
78  protected function getTemplateFilename( $templateName ) {
79  // Prevent path traversal. Based on Language::isValidCode().
80  // This is for paranoia. The $templateName should never come from
81  // untrusted input.
82  if (
83  strcspn( $templateName, ":/\\\000&<>'\"%" ) !== strlen( $templateName )
84  ) {
85  throw new UnexpectedValueException( "Malformed \$templateName: $templateName" );
86  }
87 
88  return "{$this->templateDir}/{$templateName}.mustache";
89  }
90 
97  protected function getTemplate( $templateName ) {
98  $templateKey = $templateName . '|' . $this->compileFlags;
99 
100  // If a renderer has already been defined for this template, reuse it
101  if ( isset( $this->renderers[$templateKey] ) &&
102  is_callable( $this->renderers[$templateKey] )
103  ) {
104  return $this->renderers[$templateKey];
105  }
106 
107  $filename = $this->getTemplateFilename( $templateName );
108 
109  if ( !file_exists( $filename ) ) {
110  throw new RuntimeException( "Could not locate template: {$filename}" );
111  }
112 
113  // Read the template file
114  $fileContents = file_get_contents( $filename );
115 
116  // Generate a quick hash for cache invalidation
117  $fastHash = md5( $this->compileFlags . '|' . $fileContents );
118 
119  // Fetch a secret key for building a keyed hash of the PHP code
120  $config = MediaWikiServices::getInstance()->getMainConfig();
121  $secretKey = $config->get( 'SecretKey' );
122 
123  if ( $secretKey ) {
124  // See if the compiled PHP code is stored in cache.
126  $key = $cache->makeKey( 'template', $templateName, $fastHash );
127  $code = $this->forceRecompile ? null : $cache->get( $key );
128 
129  if ( $code ) {
130  // Verify the integrity of the cached PHP code
131  $keyedHash = substr( $code, 0, 64 );
132  $code = substr( $code, 64 );
133  if ( $keyedHash !== hash_hmac( 'sha256', $code, $secretKey ) ) {
134  // If the integrity check fails, don't use the cached code
135  // We'll update the invalid cache below
136  $code = null;
137  }
138  }
139  if ( !$code ) {
140  $code = $this->compileForEval( $fileContents, $filename );
141 
142  // Prefix the cached code with a keyed hash (64 hex chars) as an integrity check
143  $cache->set( $key, hash_hmac( 'sha256', $code, $secretKey ) . $code );
144  }
145  // If there is no secret key available, don't use cache
146  } else {
147  $code = $this->compileForEval( $fileContents, $filename );
148  }
149 
150  $renderer = eval( $code );
151  if ( !is_callable( $renderer ) ) {
152  throw new RuntimeException( "Requested template, {$templateName}, is not callable" );
153  }
154  $this->renderers[$templateKey] = $renderer;
155  return $renderer;
156  }
157 
166  protected function compileForEval( $fileContents, $filename ) {
167  // Compile the template into PHP code
168  $code = $this->compile( $fileContents );
169 
170  if ( !$code ) {
171  throw new RuntimeException( "Could not compile template: {$filename}" );
172  }
173 
174  // Strip the "<?php" added by lightncandy so that it can be eval()ed
175  if ( substr( $code, 0, 5 ) === '<?php' ) {
176  $code = substr( $code, 5 );
177  }
178 
179  return $code;
180  }
181 
188  protected function compile( $code ) {
189  if ( !class_exists( 'LightnCandy' ) ) {
190  throw new RuntimeException( 'LightnCandy class not defined' );
191  }
192  return LightnCandy::compile(
193  $code,
194  [
195  'flags' => $this->compileFlags,
196  'basedir' => $this->templateDir,
197  'fileext' => '.mustache',
198  ]
199  );
200  }
201 
222  public function processTemplate( $templateName, $args, array $scopes = [] ) {
223  $template = $this->getTemplate( $templateName );
224  return $template( $args, $scopes );
225  }
226 }
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:81
$args
if( $line===false) $args
Definition: cdb.php:64
$cache
$cache
Definition: mcc.php:33
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:268