MediaWiki  master
TemplateParser.php
Go to the documentation of this file.
1 <?php
2 
4 
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 
187  protected function compile( $code ) {
188  return LightnCandy::compile(
189  $code,
190  [
191  'flags' => $this->compileFlags,
192  'basedir' => $this->templateDir,
193  'fileext' => '.mustache',
194  ]
195  );
196  }
197 
218  public function processTemplate( $templateName, $args, array $scopes = [] ) {
219  $template = $this->getTemplate( $templateName );
220  return $template( $args, $scopes );
221  }
222 }
string $templateDir
The path to the Mustache templates.
processTemplate( $templateName, $args, array $scopes=[])
Returns HTML for a given template by calling the template function with the given args...
if( $line===false) $args
Definition: cdb.php:64
enableRecursivePartials( $enable)
Enable/disable the use of recursive partials.
compileForEval( $fileContents, $filename)
Wrapper for compile() function that verifies successful compilation and strips out the &#39;<...
int $compileFlags
Compilation flags passed to LightnCandy.
$cache
Definition: mcc.php:33
callable [] $renderers
Array of cached rendering functions.
bool $forceRecompile
Always compile template files.
compile( $code)
Compile the Mustache code into PHP code using LightnCandy.
const CACHE_ANYTHING
Definition: Defines.php:81
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
getTemplate( $templateName)
Returns a given template function if found, otherwise throws an exception.
getTemplateFilename( $templateName)
Constructs the location of the source Mustache template.
__construct( $templateDir=null, $forceRecompile=false)