Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403]

   1  <?php
   2  
   3  /*
   4   * This file is part of Mustache.php.
   5   *
   6   * (c) 2010-2017 Justin Hileman
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  /**
  13   * A Mustache implementation in PHP.
  14   *
  15   * {@link http://defunkt.github.com/mustache}
  16   *
  17   * Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
  18   * logic from template files. In fact, it is not even possible to embed logic in the template.
  19   *
  20   * This is very, very rad.
  21   *
  22   * @author Justin Hileman {@link http://justinhileman.com}
  23   */
  24  class Mustache_Engine
  25  {
  26      const VERSION        = '2.14.2';
  27      const SPEC_VERSION   = '1.2.2';
  28  
  29      const PRAGMA_FILTERS      = 'FILTERS';
  30      const PRAGMA_BLOCKS       = 'BLOCKS';
  31      const PRAGMA_ANCHORED_DOT = 'ANCHORED-DOT';
  32  
  33      // Known pragmas
  34      private static $knownPragmas = array(
  35          self::PRAGMA_FILTERS      => true,
  36          self::PRAGMA_BLOCKS       => true,
  37          self::PRAGMA_ANCHORED_DOT => true,
  38      );
  39  
  40      // Template cache
  41      private $templates = array();
  42  
  43      // Environment
  44      private $templateClassPrefix = '__Mustache_';
  45      private $cache;
  46      private $lambdaCache;
  47      private $cacheLambdaTemplates = false;
  48      private $loader;
  49      private $partialsLoader;
  50      private $helpers;
  51      private $escape;
  52      private $entityFlags = ENT_COMPAT;
  53      private $charset = 'UTF-8';
  54      private $logger;
  55      private $strictCallables = false;
  56      private $disableLambdaRendering = false;
  57      private $pragmas = array();
  58      private $delimiters;
  59  
  60      // Services
  61      private $tokenizer;
  62      private $parser;
  63      private $compiler;
  64  
  65      /**
  66       * Mustache class constructor.
  67       *
  68       * Passing an $options array allows overriding certain Mustache options during instantiation:
  69       *
  70       *     $options = array(
  71       *         // The class prefix for compiled templates. Defaults to '__Mustache_'.
  72       *         'template_class_prefix' => '__MyTemplates_',
  73       *
  74       *         // A Mustache cache instance or a cache directory string for compiled templates.
  75       *         // Mustache will not cache templates unless this is set.
  76       *         'cache' => dirname(__FILE__).'/tmp/cache/mustache',
  77       *
  78       *         // Override default permissions for cache files. Defaults to using the system-defined umask. It is
  79       *         // *strongly* recommended that you configure your umask properly rather than overriding permissions here.
  80       *         'cache_file_mode' => 0666,
  81       *
  82       *         // Optionally, enable caching for lambda section templates. This is generally not recommended, as lambda
  83       *         // sections are often too dynamic to benefit from caching.
  84       *         'cache_lambda_templates' => true,
  85       *
  86       *         // Customize the tag delimiters used by this engine instance. Note that overriding here changes the
  87       *         // delimiters used to parse all templates and partials loaded by this instance. To override just for a
  88       *         // single template, use an inline "change delimiters" tag at the start of the template file:
  89       *         //
  90       *         //     {{=<% %>=}}
  91       *         //
  92       *         'delimiters' => '<% %>',
  93       *
  94       *         // A Mustache template loader instance. Uses a StringLoader if not specified.
  95       *         'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
  96       *
  97       *         // A Mustache loader instance for partials.
  98       *         'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
  99       *
 100       *         // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as
 101       *         // efficient or lazy as a Filesystem (or database) loader.
 102       *         'partials' => array('foo' => file_get_contents(dirname(__FILE__).'/views/partials/foo.mustache')),
 103       *
 104       *         // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order
 105       *         // sections), or any other valid Mustache context value. They will be prepended to the context stack,
 106       *         // so they will be available in any template loaded by this Mustache instance.
 107       *         'helpers' => array('i18n' => function ($text) {
 108       *             // do something translatey here...
 109       *         }),
 110       *
 111       *         // An 'escape' callback, responsible for escaping double-mustache variables.
 112       *         'escape' => function ($value) {
 113       *             return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8');
 114       *         },
 115       *
 116       *         // Type argument for `htmlspecialchars`.  Defaults to ENT_COMPAT.  You may prefer ENT_QUOTES.
 117       *         'entity_flags' => ENT_QUOTES,
 118       *
 119       *         // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'.
 120       *         'charset' => 'ISO-8859-1',
 121       *
 122       *         // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible
 123       *         // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is
 124       *         // available as well:
 125       *         'logger' => new Mustache_Logger_StreamLogger('php://stderr'),
 126       *
 127       *         // Only treat Closure instances and invokable classes as callable. If true, values like
 128       *         // `array('ClassName', 'methodName')` and `array($classInstance, 'methodName')`, which are traditionally
 129       *         // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This
 130       *         // helps protect against arbitrary code execution when user input is passed directly into the template.
 131       *         // This currently defaults to false, but will default to true in v3.0.
 132       *         'strict_callables' => true,
 133       *
 134       *         // Do not render the output of lambdas. Use this to prevent repeated rendering if the lambda already
 135       *         // takes care of rendering its content. This helps protect against mustache code injection when user
 136       *         // input is passed directly into the template. Defaults to false.
 137       *         'disable_lambda_rendering' => true,
 138       *
 139       *         // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual
 140       *         // templates.
 141       *         'pragmas' => [Mustache_Engine::PRAGMA_FILTERS],
 142       *     );
 143       *
 144       * @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable
 145       *
 146       * @param array $options (default: array())
 147       */
 148      public function __construct(array $options = array())
 149      {
 150          if (isset($options['template_class_prefix'])) {
 151              if ((string) $options['template_class_prefix'] === '') {
 152                  throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "template_class_prefix" must not be empty');
 153              }
 154  
 155              $this->templateClassPrefix = $options['template_class_prefix'];
 156          }
 157  
 158          if (isset($options['cache'])) {
 159              $cache = $options['cache'];
 160  
 161              if (is_string($cache)) {
 162                  $mode  = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null;
 163                  $cache = new Mustache_Cache_FilesystemCache($cache, $mode);
 164              }
 165  
 166              $this->setCache($cache);
 167          }
 168  
 169          if (isset($options['cache_lambda_templates'])) {
 170              $this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates'];
 171          }
 172  
 173          if (isset($options['loader'])) {
 174              $this->setLoader($options['loader']);
 175          }
 176  
 177          if (isset($options['partials_loader'])) {
 178              $this->setPartialsLoader($options['partials_loader']);
 179          }
 180  
 181          if (isset($options['partials'])) {
 182              $this->setPartials($options['partials']);
 183          }
 184  
 185          if (isset($options['helpers'])) {
 186              $this->setHelpers($options['helpers']);
 187          }
 188  
 189          if (isset($options['escape'])) {
 190              if (!is_callable($options['escape'])) {
 191                  throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "escape" option must be callable');
 192              }
 193  
 194              $this->escape = $options['escape'];
 195          }
 196  
 197          if (isset($options['entity_flags'])) {
 198              $this->entityFlags = $options['entity_flags'];
 199          }
 200  
 201          if (isset($options['charset'])) {
 202              $this->charset = $options['charset'];
 203          }
 204  
 205          if (isset($options['logger'])) {
 206              $this->setLogger($options['logger']);
 207          }
 208  
 209          if (isset($options['strict_callables'])) {
 210              $this->strictCallables = $options['strict_callables'];
 211          }
 212  
 213          if (isset($options['disable_lambda_rendering'])) {
 214              $this->disableLambdaRendering = $options['disable_lambda_rendering'];
 215          }
 216  
 217          if (isset($options['delimiters'])) {
 218              $this->delimiters = $options['delimiters'];
 219          }
 220  
 221          if (isset($options['pragmas'])) {
 222              foreach ($options['pragmas'] as $pragma) {
 223                  if (!isset(self::$knownPragmas[$pragma])) {
 224                      throw new Mustache_Exception_InvalidArgumentException(sprintf('Unknown pragma: "%s".', $pragma));
 225                  }
 226                  $this->pragmas[$pragma] = true;
 227              }
 228          }
 229      }
 230  
 231      /**
 232       * Shortcut 'render' invocation.
 233       *
 234       * Equivalent to calling `$mustache->loadTemplate($template)->render($context);`
 235       *
 236       * @see Mustache_Engine::loadTemplate
 237       * @see Mustache_Template::render
 238       *
 239       * @param string $template
 240       * @param mixed  $context  (default: array())
 241       *
 242       * @return string Rendered template
 243       */
 244      public function render($template, $context = array())
 245      {
 246          return $this->loadTemplate($template)->render($context);
 247      }
 248  
 249      /**
 250       * Get the current Mustache escape callback.
 251       *
 252       * @return callable|null
 253       */
 254      public function getEscape()
 255      {
 256          return $this->escape;
 257      }
 258  
 259      /**
 260       * Get the current Mustache entitity type to escape.
 261       *
 262       * @return int
 263       */
 264      public function getEntityFlags()
 265      {
 266          return $this->entityFlags;
 267      }
 268  
 269      /**
 270       * Get the current Mustache character set.
 271       *
 272       * @return string
 273       */
 274      public function getCharset()
 275      {
 276          return $this->charset;
 277      }
 278  
 279      /**
 280       * Get the current globally enabled pragmas.
 281       *
 282       * @return array
 283       */
 284      public function getPragmas()
 285      {
 286          return array_keys($this->pragmas);
 287      }
 288  
 289      /**
 290       * Set the Mustache template Loader instance.
 291       *
 292       * @param Mustache_Loader $loader
 293       */
 294      public function setLoader(Mustache_Loader $loader)
 295      {
 296          $this->loader = $loader;
 297      }
 298  
 299      /**
 300       * Get the current Mustache template Loader instance.
 301       *
 302       * If no Loader instance has been explicitly specified, this method will instantiate and return
 303       * a StringLoader instance.
 304       *
 305       * @return Mustache_Loader
 306       */
 307      public function getLoader()
 308      {
 309          if (!isset($this->loader)) {
 310              $this->loader = new Mustache_Loader_StringLoader();
 311          }
 312  
 313          return $this->loader;
 314      }
 315  
 316      /**
 317       * Set the Mustache partials Loader instance.
 318       *
 319       * @param Mustache_Loader $partialsLoader
 320       */
 321      public function setPartialsLoader(Mustache_Loader $partialsLoader)
 322      {
 323          $this->partialsLoader = $partialsLoader;
 324      }
 325  
 326      /**
 327       * Get the current Mustache partials Loader instance.
 328       *
 329       * If no Loader instance has been explicitly specified, this method will instantiate and return
 330       * an ArrayLoader instance.
 331       *
 332       * @return Mustache_Loader
 333       */
 334      public function getPartialsLoader()
 335      {
 336          if (!isset($this->partialsLoader)) {
 337              $this->partialsLoader = new Mustache_Loader_ArrayLoader();
 338          }
 339  
 340          return $this->partialsLoader;
 341      }
 342  
 343      /**
 344       * Set partials for the current partials Loader instance.
 345       *
 346       * @throws Mustache_Exception_RuntimeException If the current Loader instance is immutable
 347       *
 348       * @param array $partials (default: array())
 349       */
 350      public function setPartials(array $partials = array())
 351      {
 352          if (!isset($this->partialsLoader)) {
 353              $this->partialsLoader = new Mustache_Loader_ArrayLoader();
 354          }
 355  
 356          if (!$this->partialsLoader instanceof Mustache_Loader_MutableLoader) {
 357              throw new Mustache_Exception_RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
 358          }
 359  
 360          $this->partialsLoader->setTemplates($partials);
 361      }
 362  
 363      /**
 364       * Set an array of Mustache helpers.
 365       *
 366       * An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or
 367       * any other valid Mustache context value. They will be prepended to the context stack, so they will be available in
 368       * any template loaded by this Mustache instance.
 369       *
 370       * @throws Mustache_Exception_InvalidArgumentException if $helpers is not an array or Traversable
 371       *
 372       * @param array|Traversable $helpers
 373       */
 374      public function setHelpers($helpers)
 375      {
 376          if (!is_array($helpers) && !$helpers instanceof Traversable) {
 377              throw new Mustache_Exception_InvalidArgumentException('setHelpers expects an array of helpers');
 378          }
 379  
 380          $this->getHelpers()->clear();
 381  
 382          foreach ($helpers as $name => $helper) {
 383              $this->addHelper($name, $helper);
 384          }
 385      }
 386  
 387      /**
 388       * Get the current set of Mustache helpers.
 389       *
 390       * @see Mustache_Engine::setHelpers
 391       *
 392       * @return Mustache_HelperCollection
 393       */
 394      public function getHelpers()
 395      {
 396          if (!isset($this->helpers)) {
 397              $this->helpers = new Mustache_HelperCollection();
 398          }
 399  
 400          return $this->helpers;
 401      }
 402  
 403      /**
 404       * Add a new Mustache helper.
 405       *
 406       * @see Mustache_Engine::setHelpers
 407       *
 408       * @param string $name
 409       * @param mixed  $helper
 410       */
 411      public function addHelper($name, $helper)
 412      {
 413          $this->getHelpers()->add($name, $helper);
 414      }
 415  
 416      /**
 417       * Get a Mustache helper by name.
 418       *
 419       * @see Mustache_Engine::setHelpers
 420       *
 421       * @param string $name
 422       *
 423       * @return mixed Helper
 424       */
 425      public function getHelper($name)
 426      {
 427          return $this->getHelpers()->get($name);
 428      }
 429  
 430      /**
 431       * Check whether this Mustache instance has a helper.
 432       *
 433       * @see Mustache_Engine::setHelpers
 434       *
 435       * @param string $name
 436       *
 437       * @return bool True if the helper is present
 438       */
 439      public function hasHelper($name)
 440      {
 441          return $this->getHelpers()->has($name);
 442      }
 443  
 444      /**
 445       * Remove a helper by name.
 446       *
 447       * @see Mustache_Engine::setHelpers
 448       *
 449       * @param string $name
 450       */
 451      public function removeHelper($name)
 452      {
 453          $this->getHelpers()->remove($name);
 454      }
 455  
 456      /**
 457       * Set the Mustache Logger instance.
 458       *
 459       * @throws Mustache_Exception_InvalidArgumentException If logger is not an instance of Mustache_Logger or Psr\Log\LoggerInterface
 460       *
 461       * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
 462       */
 463      public function setLogger($logger = null)
 464      {
 465          if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) {
 466              throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
 467          }
 468  
 469          if ($this->getCache()->getLogger() === null) {
 470              $this->getCache()->setLogger($logger);
 471          }
 472  
 473          $this->logger = $logger;
 474      }
 475  
 476      /**
 477       * Get the current Mustache Logger instance.
 478       *
 479       * @return Mustache_Logger|Psr\Log\LoggerInterface
 480       */
 481      public function getLogger()
 482      {
 483          return $this->logger;
 484      }
 485  
 486      /**
 487       * Set the Mustache Tokenizer instance.
 488       *
 489       * @param Mustache_Tokenizer $tokenizer
 490       */
 491      public function setTokenizer(Mustache_Tokenizer $tokenizer)
 492      {
 493          $this->tokenizer = $tokenizer;
 494      }
 495  
 496      /**
 497       * Get the current Mustache Tokenizer instance.
 498       *
 499       * If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one.
 500       *
 501       * @return Mustache_Tokenizer
 502       */
 503      public function getTokenizer()
 504      {
 505          if (!isset($this->tokenizer)) {
 506              $this->tokenizer = new Mustache_Tokenizer();
 507          }
 508  
 509          return $this->tokenizer;
 510      }
 511  
 512      /**
 513       * Set the Mustache Parser instance.
 514       *
 515       * @param Mustache_Parser $parser
 516       */
 517      public function setParser(Mustache_Parser $parser)
 518      {
 519          $this->parser = $parser;
 520      }
 521  
 522      /**
 523       * Get the current Mustache Parser instance.
 524       *
 525       * If no Parser instance has been explicitly specified, this method will instantiate and return a new one.
 526       *
 527       * @return Mustache_Parser
 528       */
 529      public function getParser()
 530      {
 531          if (!isset($this->parser)) {
 532              $this->parser = new Mustache_Parser();
 533          }
 534  
 535          return $this->parser;
 536      }
 537  
 538      /**
 539       * Set the Mustache Compiler instance.
 540       *
 541       * @param Mustache_Compiler $compiler
 542       */
 543      public function setCompiler(Mustache_Compiler $compiler)
 544      {
 545          $this->compiler = $compiler;
 546      }
 547  
 548      /**
 549       * Get the current Mustache Compiler instance.
 550       *
 551       * If no Compiler instance has been explicitly specified, this method will instantiate and return a new one.
 552       *
 553       * @return Mustache_Compiler
 554       */
 555      public function getCompiler()
 556      {
 557          if (!isset($this->compiler)) {
 558              $this->compiler = new Mustache_Compiler();
 559          }
 560  
 561          return $this->compiler;
 562      }
 563  
 564      /**
 565       * Set the Mustache Cache instance.
 566       *
 567       * @param Mustache_Cache $cache
 568       */
 569      public function setCache(Mustache_Cache $cache)
 570      {
 571          if (isset($this->logger) && $cache->getLogger() === null) {
 572              $cache->setLogger($this->getLogger());
 573          }
 574  
 575          $this->cache = $cache;
 576      }
 577  
 578      /**
 579       * Get the current Mustache Cache instance.
 580       *
 581       * If no Cache instance has been explicitly specified, this method will instantiate and return a new one.
 582       *
 583       * @return Mustache_Cache
 584       */
 585      public function getCache()
 586      {
 587          if (!isset($this->cache)) {
 588              $this->setCache(new Mustache_Cache_NoopCache());
 589          }
 590  
 591          return $this->cache;
 592      }
 593  
 594      /**
 595       * Get the current Lambda Cache instance.
 596       *
 597       * If 'cache_lambda_templates' is enabled, this is the default cache instance. Otherwise, it is a NoopCache.
 598       *
 599       * @see Mustache_Engine::getCache
 600       *
 601       * @return Mustache_Cache
 602       */
 603      protected function getLambdaCache()
 604      {
 605          if ($this->cacheLambdaTemplates) {
 606              return $this->getCache();
 607          }
 608  
 609          if (!isset($this->lambdaCache)) {
 610              $this->lambdaCache = new Mustache_Cache_NoopCache();
 611          }
 612  
 613          return $this->lambdaCache;
 614      }
 615  
 616      /**
 617       * Helper method to generate a Mustache template class.
 618       *
 619       * This method must be updated any time options are added which make it so
 620       * the same template could be parsed and compiled multiple different ways.
 621       *
 622       * @param string|Mustache_Source $source
 623       *
 624       * @return string Mustache Template class name
 625       */
 626      public function getTemplateClassName($source)
 627      {
 628          // For the most part, adding a new option here should do the trick.
 629          //
 630          // Pick a value here which is unique for each possible way the template
 631          // could be compiled... but not necessarily unique per option value. See
 632          // escape below, which only needs to differentiate between 'custom' and
 633          // 'default' escapes.
 634          //
 635          // Keep this list in alphabetical order :)
 636          $chunks = array(
 637              'charset'                => $this->charset,
 638              'delimiters'             => $this->delimiters ? $this->delimiters : '{{ }}',
 639              'entityFlags'            => $this->entityFlags,
 640              'escape'                 => isset($this->escape) ? 'custom' : 'default',
 641              'key'                    => ($source instanceof Mustache_Source) ? $source->getKey() : 'source',
 642              'pragmas'                => $this->getPragmas(),
 643              'strictCallables'        => $this->strictCallables,
 644              'disableLambdaRendering' => $this->disableLambdaRendering,
 645              'version'                => self::VERSION,
 646          );
 647  
 648          $key = json_encode($chunks);
 649  
 650          // Template Source instances have already provided their own source key. For strings, just include the whole
 651          // source string in the md5 hash.
 652          if (!$source instanceof Mustache_Source) {
 653              $key .= "\n" . $source;
 654          }
 655  
 656          return $this->templateClassPrefix . md5($key);
 657      }
 658  
 659      /**
 660       * Load a Mustache Template by name.
 661       *
 662       * @param string $name
 663       *
 664       * @return Mustache_Template
 665       */
 666      public function loadTemplate($name)
 667      {
 668          return $this->loadSource($this->getLoader()->load($name));
 669      }
 670  
 671      /**
 672       * Load a Mustache partial Template by name.
 673       *
 674       * This is a helper method used internally by Template instances for loading partial templates. You can most likely
 675       * ignore it completely.
 676       *
 677       * @param string $name
 678       *
 679       * @return Mustache_Template
 680       */
 681      public function loadPartial($name)
 682      {
 683          try {
 684              if (isset($this->partialsLoader)) {
 685                  $loader = $this->partialsLoader;
 686              } elseif (isset($this->loader) && !$this->loader instanceof Mustache_Loader_StringLoader) {
 687                  $loader = $this->loader;
 688              } else {
 689                  throw new Mustache_Exception_UnknownTemplateException($name);
 690              }
 691  
 692              return $this->loadSource($loader->load($name));
 693          } catch (Mustache_Exception_UnknownTemplateException $e) {
 694              // If the named partial cannot be found, log then return null.
 695              $this->log(
 696                  Mustache_Logger::WARNING,
 697                  'Partial not found: "{name}"',
 698                  array('name' => $e->getTemplateName())
 699              );
 700          }
 701      }
 702  
 703      /**
 704       * Load a Mustache lambda Template by source.
 705       *
 706       * This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most
 707       * likely ignore it completely.
 708       *
 709       * @param string $source
 710       * @param string $delims (default: null)
 711       *
 712       * @return Mustache_Template
 713       */
 714      public function loadLambda($source, $delims = null)
 715      {
 716          if ($delims !== null) {
 717              $source = $delims . "\n" . $source;
 718          }
 719  
 720          return $this->loadSource($source, $this->getLambdaCache());
 721      }
 722  
 723      /**
 724       * Instantiate and return a Mustache Template instance by source.
 725       *
 726       * Optionally provide a Mustache_Cache instance. This is used internally by Mustache_Engine::loadLambda to respect
 727       * the 'cache_lambda_templates' configuration option.
 728       *
 729       * @see Mustache_Engine::loadTemplate
 730       * @see Mustache_Engine::loadPartial
 731       * @see Mustache_Engine::loadLambda
 732       *
 733       * @param string|Mustache_Source $source
 734       * @param Mustache_Cache         $cache  (default: null)
 735       *
 736       * @return Mustache_Template
 737       */
 738      private function loadSource($source, Mustache_Cache $cache = null)
 739      {
 740          $className = $this->getTemplateClassName($source);
 741  
 742          if (!isset($this->templates[$className])) {
 743              if ($cache === null) {
 744                  $cache = $this->getCache();
 745              }
 746  
 747              if (!class_exists($className, false)) {
 748                  if (!$cache->load($className)) {
 749                      $compiled = $this->compile($source);
 750                      $cache->cache($className, $compiled);
 751                  }
 752              }
 753  
 754              $this->log(
 755                  Mustache_Logger::DEBUG,
 756                  'Instantiating template: "{className}"',
 757                  array('className' => $className)
 758              );
 759  
 760              $this->templates[$className] = new $className($this);
 761          }
 762  
 763          return $this->templates[$className];
 764      }
 765  
 766      /**
 767       * Helper method to tokenize a Mustache template.
 768       *
 769       * @see Mustache_Tokenizer::scan
 770       *
 771       * @param string $source
 772       *
 773       * @return array Tokens
 774       */
 775      private function tokenize($source)
 776      {
 777          return $this->getTokenizer()->scan($source, $this->delimiters);
 778      }
 779  
 780      /**
 781       * Helper method to parse a Mustache template.
 782       *
 783       * @see Mustache_Parser::parse
 784       *
 785       * @param string $source
 786       *
 787       * @return array Token tree
 788       */
 789      private function parse($source)
 790      {
 791          $parser = $this->getParser();
 792          $parser->setPragmas($this->getPragmas());
 793  
 794          return $parser->parse($this->tokenize($source));
 795      }
 796  
 797      /**
 798       * Helper method to compile a Mustache template.
 799       *
 800       * @see Mustache_Compiler::compile
 801       *
 802       * @param string|Mustache_Source $source
 803       *
 804       * @return string generated Mustache template class code
 805       */
 806      private function compile($source)
 807      {
 808          $name = $this->getTemplateClassName($source);
 809  
 810          $this->log(
 811              Mustache_Logger::INFO,
 812              'Compiling template to "{className}" class',
 813              array('className' => $name)
 814          );
 815  
 816          if ($source instanceof Mustache_Source) {
 817              $source = $source->getSource();
 818          }
 819          $tree = $this->parse($source);
 820  
 821          $compiler = $this->getCompiler();
 822          $compiler->setPragmas($this->getPragmas());
 823  
 824          return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags, $this->disableLambdaRendering);
 825      }
 826  
 827      /**
 828       * Add a log record if logging is enabled.
 829       *
 830       * @param int    $level   The logging level
 831       * @param string $message The log message
 832       * @param array  $context The log context
 833       */
 834      private function log($level, $message, array $context = array())
 835      {
 836          if (isset($this->logger)) {
 837              $this->logger->log($level, $message, $context);
 838          }
 839      }
 840  }