Environment.php 30KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 2009 Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Stores the Twig configuration.
  12. *
  13. * @package twig
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. */
  16. class Twig_Environment
  17. {
  18. const VERSION = '1.7.0';
  19. protected $charset;
  20. protected $loader;
  21. protected $debug;
  22. protected $autoReload;
  23. protected $cache;
  24. protected $lexer;
  25. protected $parser;
  26. protected $compiler;
  27. protected $baseTemplateClass;
  28. protected $extensions;
  29. protected $parsers;
  30. protected $visitors;
  31. protected $filters;
  32. protected $tests;
  33. protected $functions;
  34. protected $globals;
  35. protected $runtimeInitialized;
  36. protected $loadedTemplates;
  37. protected $strictVariables;
  38. protected $unaryOperators;
  39. protected $binaryOperators;
  40. protected $templateClassPrefix = '__TwigTemplate_';
  41. protected $functionCallbacks;
  42. protected $filterCallbacks;
  43. protected $staging;
  44. /**
  45. * Constructor.
  46. *
  47. * Available options:
  48. *
  49. * * debug: When set to `true`, the generated templates have a __toString()
  50. * method that you can use to display the generated nodes (default to
  51. * false).
  52. *
  53. * * charset: The charset used by the templates (default to utf-8).
  54. *
  55. * * base_template_class: The base template class to use for generated
  56. * templates (default to Twig_Template).
  57. *
  58. * * cache: An absolute path where to store the compiled templates, or
  59. * false to disable compilation cache (default)
  60. *
  61. * * auto_reload: Whether to reload the template is the original source changed.
  62. * If you don't provide the auto_reload option, it will be
  63. * determined automatically base on the debug value.
  64. *
  65. * * strict_variables: Whether to ignore invalid variables in templates
  66. * (default to false).
  67. *
  68. * * autoescape: Whether to enable auto-escaping (default to true);
  69. *
  70. * * optimizations: A flag that indicates which optimizations to apply
  71. * (default to -1 which means that all optimizations are enabled;
  72. * set it to 0 to disable)
  73. *
  74. * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
  75. * @param array $options An array of options
  76. */
  77. public function __construct(Twig_LoaderInterface $loader = null, $options = array())
  78. {
  79. if (null !== $loader) {
  80. $this->setLoader($loader);
  81. }
  82. $options = array_merge(array(
  83. 'debug' => false,
  84. 'charset' => 'UTF-8',
  85. 'base_template_class' => 'Twig_Template',
  86. 'strict_variables' => false,
  87. 'autoescape' => true,
  88. 'cache' => false,
  89. 'auto_reload' => null,
  90. 'optimizations' => -1,
  91. ), $options);
  92. $this->debug = (bool) $options['debug'];
  93. $this->charset = $options['charset'];
  94. $this->baseTemplateClass = $options['base_template_class'];
  95. $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
  96. $this->extensions = array(
  97. 'core' => new Twig_Extension_Core(),
  98. 'escaper' => new Twig_Extension_Escaper((bool) $options['autoescape']),
  99. 'optimizer' => new Twig_Extension_Optimizer($options['optimizations']),
  100. );
  101. $this->strictVariables = (bool) $options['strict_variables'];
  102. $this->runtimeInitialized = false;
  103. $this->setCache($options['cache']);
  104. $this->functionCallbacks = array();
  105. $this->filterCallbacks = array();
  106. }
  107. /**
  108. * Gets the base template class for compiled templates.
  109. *
  110. * @return string The base template class name
  111. */
  112. public function getBaseTemplateClass()
  113. {
  114. return $this->baseTemplateClass;
  115. }
  116. /**
  117. * Sets the base template class for compiled templates.
  118. *
  119. * @param string $class The base template class name
  120. */
  121. public function setBaseTemplateClass($class)
  122. {
  123. $this->baseTemplateClass = $class;
  124. }
  125. /**
  126. * Enables debugging mode.
  127. */
  128. public function enableDebug()
  129. {
  130. $this->debug = true;
  131. }
  132. /**
  133. * Disables debugging mode.
  134. */
  135. public function disableDebug()
  136. {
  137. $this->debug = false;
  138. }
  139. /**
  140. * Checks if debug mode is enabled.
  141. *
  142. * @return Boolean true if debug mode is enabled, false otherwise
  143. */
  144. public function isDebug()
  145. {
  146. return $this->debug;
  147. }
  148. /**
  149. * Enables the auto_reload option.
  150. */
  151. public function enableAutoReload()
  152. {
  153. $this->autoReload = true;
  154. }
  155. /**
  156. * Disables the auto_reload option.
  157. */
  158. public function disableAutoReload()
  159. {
  160. $this->autoReload = false;
  161. }
  162. /**
  163. * Checks if the auto_reload option is enabled.
  164. *
  165. * @return Boolean true if auto_reload is enabled, false otherwise
  166. */
  167. public function isAutoReload()
  168. {
  169. return $this->autoReload;
  170. }
  171. /**
  172. * Enables the strict_variables option.
  173. */
  174. public function enableStrictVariables()
  175. {
  176. $this->strictVariables = true;
  177. }
  178. /**
  179. * Disables the strict_variables option.
  180. */
  181. public function disableStrictVariables()
  182. {
  183. $this->strictVariables = false;
  184. }
  185. /**
  186. * Checks if the strict_variables option is enabled.
  187. *
  188. * @return Boolean true if strict_variables is enabled, false otherwise
  189. */
  190. public function isStrictVariables()
  191. {
  192. return $this->strictVariables;
  193. }
  194. /**
  195. * Gets the cache directory or false if cache is disabled.
  196. *
  197. * @return string|false
  198. */
  199. public function getCache()
  200. {
  201. return $this->cache;
  202. }
  203. /**
  204. * Sets the cache directory or false if cache is disabled.
  205. *
  206. * @param string|false $cache The absolute path to the compiled templates,
  207. * or false to disable cache
  208. */
  209. public function setCache($cache)
  210. {
  211. $this->cache = $cache ? $cache : false;
  212. }
  213. /**
  214. * Gets the cache filename for a given template.
  215. *
  216. * @param string $name The template name
  217. *
  218. * @return string The cache file name
  219. */
  220. public function getCacheFilename($name)
  221. {
  222. if (false === $this->cache) {
  223. return false;
  224. }
  225. $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix));
  226. return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php';
  227. }
  228. /**
  229. * Gets the template class associated with the given string.
  230. *
  231. * @param string $name The name for which to calculate the template class name
  232. *
  233. * @return string The template class name
  234. */
  235. public function getTemplateClass($name)
  236. {
  237. return $this->templateClassPrefix.md5($this->loader->getCacheKey($name));
  238. }
  239. /**
  240. * Gets the template class prefix.
  241. *
  242. * @return string The template class prefix
  243. */
  244. public function getTemplateClassPrefix()
  245. {
  246. return $this->templateClassPrefix;
  247. }
  248. /**
  249. * Renders a template.
  250. *
  251. * @param string $name The template name
  252. * @param array $context An array of parameters to pass to the template
  253. *
  254. * @return string The rendered template
  255. */
  256. public function render($name, array $context = array())
  257. {
  258. return $this->loadTemplate($name)->render($context);
  259. }
  260. /**
  261. * Displays a template.
  262. *
  263. * @param string $name The template name
  264. * @param array $context An array of parameters to pass to the template
  265. */
  266. public function display($name, array $context = array())
  267. {
  268. $this->loadTemplate($name)->display($context);
  269. }
  270. /**
  271. * Loads a template by name.
  272. *
  273. * @param string $name The template name
  274. *
  275. * @return Twig_TemplateInterface A template instance representing the given template name
  276. */
  277. public function loadTemplate($name)
  278. {
  279. $cls = $this->getTemplateClass($name);
  280. if (isset($this->loadedTemplates[$cls])) {
  281. return $this->loadedTemplates[$cls];
  282. }
  283. if (!class_exists($cls, false)) {
  284. if (false === $cache = $this->getCacheFilename($name)) {
  285. eval('?>'.$this->compileSource($this->loader->getSource($name), $name));
  286. } else {
  287. if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
  288. $this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name));
  289. }
  290. require_once $cache;
  291. }
  292. }
  293. if (!$this->runtimeInitialized) {
  294. $this->initRuntime();
  295. }
  296. return $this->loadedTemplates[$cls] = new $cls($this);
  297. }
  298. /**
  299. * Returns true if the template is still fresh.
  300. *
  301. * Besides checking the loader for freshness information,
  302. * this method also checks if the enabled extensions have
  303. * not changed.
  304. *
  305. * @param string $name The template name
  306. * @param timestamp $time The last modification time of the cached template
  307. *
  308. * @return Boolean true if the template is fresh, false otherwise
  309. */
  310. public function isTemplateFresh($name, $time)
  311. {
  312. foreach ($this->extensions as $extension) {
  313. $r = new ReflectionObject($extension);
  314. if (filemtime($r->getFileName()) > $time) {
  315. return false;
  316. }
  317. }
  318. return $this->loader->isFresh($name, $time);
  319. }
  320. public function resolveTemplate($names)
  321. {
  322. if (!is_array($names)) {
  323. $names = array($names);
  324. }
  325. foreach ($names as $name) {
  326. if ($name instanceof Twig_Template) {
  327. return $name;
  328. }
  329. try {
  330. return $this->loadTemplate($name);
  331. } catch (Twig_Error_Loader $e) {
  332. }
  333. }
  334. if (1 === count($names)) {
  335. throw $e;
  336. }
  337. throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
  338. }
  339. /**
  340. * Clears the internal template cache.
  341. */
  342. public function clearTemplateCache()
  343. {
  344. $this->loadedTemplates = array();
  345. }
  346. /**
  347. * Clears the template cache files on the filesystem.
  348. */
  349. public function clearCacheFiles()
  350. {
  351. if (false === $this->cache) {
  352. return;
  353. }
  354. foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
  355. if ($file->isFile()) {
  356. @unlink($file->getPathname());
  357. }
  358. }
  359. }
  360. /**
  361. * Gets the Lexer instance.
  362. *
  363. * @return Twig_LexerInterface A Twig_LexerInterface instance
  364. */
  365. public function getLexer()
  366. {
  367. if (null === $this->lexer) {
  368. $this->lexer = new Twig_Lexer($this);
  369. }
  370. return $this->lexer;
  371. }
  372. /**
  373. * Sets the Lexer instance.
  374. *
  375. * @param Twig_LexerInterface A Twig_LexerInterface instance
  376. */
  377. public function setLexer(Twig_LexerInterface $lexer)
  378. {
  379. $this->lexer = $lexer;
  380. }
  381. /**
  382. * Tokenizes a source code.
  383. *
  384. * @param string $source The template source code
  385. * @param string $name The template name
  386. *
  387. * @return Twig_TokenStream A Twig_TokenStream instance
  388. */
  389. public function tokenize($source, $name = null)
  390. {
  391. return $this->getLexer()->tokenize($source, $name);
  392. }
  393. /**
  394. * Gets the Parser instance.
  395. *
  396. * @return Twig_ParserInterface A Twig_ParserInterface instance
  397. */
  398. public function getParser()
  399. {
  400. if (null === $this->parser) {
  401. $this->parser = new Twig_Parser($this);
  402. }
  403. return $this->parser;
  404. }
  405. /**
  406. * Sets the Parser instance.
  407. *
  408. * @param Twig_ParserInterface A Twig_ParserInterface instance
  409. */
  410. public function setParser(Twig_ParserInterface $parser)
  411. {
  412. $this->parser = $parser;
  413. }
  414. /**
  415. * Parses a token stream.
  416. *
  417. * @param Twig_TokenStream $tokens A Twig_TokenStream instance
  418. *
  419. * @return Twig_Node_Module A Node tree
  420. */
  421. public function parse(Twig_TokenStream $tokens)
  422. {
  423. return $this->getParser()->parse($tokens);
  424. }
  425. /**
  426. * Gets the Compiler instance.
  427. *
  428. * @return Twig_CompilerInterface A Twig_CompilerInterface instance
  429. */
  430. public function getCompiler()
  431. {
  432. if (null === $this->compiler) {
  433. $this->compiler = new Twig_Compiler($this);
  434. }
  435. return $this->compiler;
  436. }
  437. /**
  438. * Sets the Compiler instance.
  439. *
  440. * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance
  441. */
  442. public function setCompiler(Twig_CompilerInterface $compiler)
  443. {
  444. $this->compiler = $compiler;
  445. }
  446. /**
  447. * Compiles a Node.
  448. *
  449. * @param Twig_NodeInterface $node A Twig_NodeInterface instance
  450. *
  451. * @return string The compiled PHP source code
  452. */
  453. public function compile(Twig_NodeInterface $node)
  454. {
  455. return $this->getCompiler()->compile($node)->getSource();
  456. }
  457. /**
  458. * Compiles a template source code.
  459. *
  460. * @param string $source The template source code
  461. * @param string $name The template name
  462. *
  463. * @return string The compiled PHP source code
  464. */
  465. public function compileSource($source, $name = null)
  466. {
  467. try {
  468. return $this->compile($this->parse($this->tokenize($source, $name)));
  469. } catch (Twig_Error $e) {
  470. $e->setTemplateFile($name);
  471. throw $e;
  472. } catch (Exception $e) {
  473. throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
  474. }
  475. }
  476. /**
  477. * Sets the Loader instance.
  478. *
  479. * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
  480. */
  481. public function setLoader(Twig_LoaderInterface $loader)
  482. {
  483. $this->loader = $loader;
  484. }
  485. /**
  486. * Gets the Loader instance.
  487. *
  488. * @return Twig_LoaderInterface A Twig_LoaderInterface instance
  489. */
  490. public function getLoader()
  491. {
  492. return $this->loader;
  493. }
  494. /**
  495. * Sets the default template charset.
  496. *
  497. * @param string $charset The default charset
  498. */
  499. public function setCharset($charset)
  500. {
  501. $this->charset = $charset;
  502. }
  503. /**
  504. * Gets the default template charset.
  505. *
  506. * @return string The default charset
  507. */
  508. public function getCharset()
  509. {
  510. return $this->charset;
  511. }
  512. /**
  513. * Initializes the runtime environment.
  514. */
  515. public function initRuntime()
  516. {
  517. $this->runtimeInitialized = true;
  518. foreach ($this->getExtensions() as $extension) {
  519. $extension->initRuntime($this);
  520. }
  521. }
  522. /**
  523. * Returns true if the given extension is registered.
  524. *
  525. * @param string $name The extension name
  526. *
  527. * @return Boolean Whether the extension is registered or not
  528. */
  529. public function hasExtension($name)
  530. {
  531. return isset($this->extensions[$name]);
  532. }
  533. /**
  534. * Gets an extension by name.
  535. *
  536. * @param string $name The extension name
  537. *
  538. * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance
  539. */
  540. public function getExtension($name)
  541. {
  542. if (!isset($this->extensions[$name])) {
  543. throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name));
  544. }
  545. return $this->extensions[$name];
  546. }
  547. /**
  548. * Registers an extension.
  549. *
  550. * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance
  551. */
  552. public function addExtension(Twig_ExtensionInterface $extension)
  553. {
  554. $this->extensions[$extension->getName()] = $extension;
  555. $this->parsers = null;
  556. $this->visitors = null;
  557. $this->filters = null;
  558. $this->tests = null;
  559. $this->functions = null;
  560. $this->globals = null;
  561. }
  562. /**
  563. * Removes an extension by name.
  564. *
  565. * @param string $name The extension name
  566. */
  567. public function removeExtension($name)
  568. {
  569. unset($this->extensions[$name]);
  570. $this->parsers = null;
  571. $this->visitors = null;
  572. $this->filters = null;
  573. $this->tests = null;
  574. $this->functions = null;
  575. $this->globals = null;
  576. }
  577. /**
  578. * Registers an array of extensions.
  579. *
  580. * @param array $extensions An array of extensions
  581. */
  582. public function setExtensions(array $extensions)
  583. {
  584. foreach ($extensions as $extension) {
  585. $this->addExtension($extension);
  586. }
  587. }
  588. /**
  589. * Returns all registered extensions.
  590. *
  591. * @return array An array of extensions
  592. */
  593. public function getExtensions()
  594. {
  595. return $this->extensions;
  596. }
  597. /**
  598. * Registers a Token Parser.
  599. *
  600. * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
  601. */
  602. public function addTokenParser(Twig_TokenParserInterface $parser)
  603. {
  604. $this->staging['token_parsers'][] = $parser;
  605. $this->parsers = null;
  606. }
  607. /**
  608. * Gets the registered Token Parsers.
  609. *
  610. * @return Twig_TokenParserBrokerInterface A broker containing token parsers
  611. */
  612. public function getTokenParsers()
  613. {
  614. if (null === $this->parsers) {
  615. $this->parsers = new Twig_TokenParserBroker();
  616. if (isset($this->staging['token_parsers'])) {
  617. foreach ($this->staging['token_parsers'] as $parser) {
  618. $this->parsers->addTokenParser($parser);
  619. }
  620. }
  621. foreach ($this->getExtensions() as $extension) {
  622. $parsers = $extension->getTokenParsers();
  623. foreach($parsers as $parser) {
  624. if ($parser instanceof Twig_TokenParserInterface) {
  625. $this->parsers->addTokenParser($parser);
  626. } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
  627. $this->parsers->addTokenParserBroker($parser);
  628. } else {
  629. throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
  630. }
  631. }
  632. }
  633. }
  634. return $this->parsers;
  635. }
  636. /**
  637. * Gets registered tags.
  638. *
  639. * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
  640. *
  641. * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
  642. */
  643. public function getTags()
  644. {
  645. $tags = array();
  646. foreach ($this->getTokenParsers()->getParsers() as $parser) {
  647. if ($parser instanceof Twig_TokenParserInterface) {
  648. $tags[$parser->getTag()] = $parser;
  649. }
  650. }
  651. return $tags;
  652. }
  653. /**
  654. * Registers a Node Visitor.
  655. *
  656. * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
  657. */
  658. public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
  659. {
  660. $this->staging['visitors'][] = $visitor;
  661. $this->visitors = null;
  662. }
  663. /**
  664. * Gets the registered Node Visitors.
  665. *
  666. * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
  667. */
  668. public function getNodeVisitors()
  669. {
  670. if (null === $this->visitors) {
  671. $this->visitors = isset($this->staging['visitors']) ? $this->staging['visitors'] : array();
  672. foreach ($this->getExtensions() as $extension) {
  673. $this->visitors = array_merge($this->visitors, $extension->getNodeVisitors());
  674. }
  675. }
  676. return $this->visitors;
  677. }
  678. /**
  679. * Registers a Filter.
  680. *
  681. * @param string $name The filter name
  682. * @param Twig_FilterInterface $filter A Twig_FilterInterface instance
  683. */
  684. public function addFilter($name, Twig_FilterInterface $filter)
  685. {
  686. $this->staging['filters'][$name] = $filter;
  687. $this->filters = null;
  688. }
  689. /**
  690. * Get a filter by name.
  691. *
  692. * Subclasses may override this method and load filters differently;
  693. * so no list of filters is available.
  694. *
  695. * @param string $name The filter name
  696. *
  697. * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists
  698. */
  699. public function getFilter($name)
  700. {
  701. if (null === $this->filters) {
  702. $this->getFilters();
  703. }
  704. if (isset($this->filters[$name])) {
  705. return $this->filters[$name];
  706. }
  707. foreach ($this->filters as $pattern => $filter) {
  708. $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
  709. if ($count) {
  710. if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
  711. array_shift($matches);
  712. $filter->setArguments($matches);
  713. return $filter;
  714. }
  715. }
  716. }
  717. foreach ($this->filterCallbacks as $callback) {
  718. if (false !== $filter = call_user_func($callback, $name)) {
  719. return $filter;
  720. }
  721. }
  722. return false;
  723. }
  724. public function registerUndefinedFilterCallback($callable)
  725. {
  726. $this->filterCallbacks[] = $callable;
  727. }
  728. /**
  729. * Gets the registered Filters.
  730. *
  731. * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
  732. *
  733. * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
  734. *
  735. * @see registerUndefinedFilterCallback
  736. */
  737. public function getFilters()
  738. {
  739. if (null === $this->filters) {
  740. $this->filters = isset($this->staging['filters']) ? $this->staging['filters'] : array();
  741. foreach ($this->getExtensions() as $extension) {
  742. $this->filters = array_merge($this->filters, $extension->getFilters());
  743. }
  744. }
  745. return $this->filters;
  746. }
  747. /**
  748. * Registers a Test.
  749. *
  750. * @param string $name The test name
  751. * @param Twig_TestInterface $test A Twig_TestInterface instance
  752. */
  753. public function addTest($name, Twig_TestInterface $test)
  754. {
  755. $this->staging['tests'][$name] = $test;
  756. $this->tests = null;
  757. }
  758. /**
  759. * Gets the registered Tests.
  760. *
  761. * @return Twig_TestInterface[] An array of Twig_TestInterface instances
  762. */
  763. public function getTests()
  764. {
  765. if (null === $this->tests) {
  766. $this->tests = isset($this->staging['tests']) ? $this->staging['tests'] : array();
  767. foreach ($this->getExtensions() as $extension) {
  768. $this->tests = array_merge($this->tests, $extension->getTests());
  769. }
  770. }
  771. return $this->tests;
  772. }
  773. /**
  774. * Registers a Function.
  775. *
  776. * @param string $name The function name
  777. * @param Twig_FunctionInterface $function A Twig_FunctionInterface instance
  778. */
  779. public function addFunction($name, Twig_FunctionInterface $function)
  780. {
  781. $this->staging['functions'][$name] = $function;
  782. $this->functions = null;
  783. }
  784. /**
  785. * Get a function by name.
  786. *
  787. * Subclasses may override this method and load functions differently;
  788. * so no list of functions is available.
  789. *
  790. * @param string $name function name
  791. *
  792. * @return Twig_Function|false A Twig_Function instance or false if the function does not exists
  793. */
  794. public function getFunction($name)
  795. {
  796. if (null === $this->functions) {
  797. $this->getFunctions();
  798. }
  799. if (isset($this->functions[$name])) {
  800. return $this->functions[$name];
  801. }
  802. foreach ($this->functions as $pattern => $function) {
  803. $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
  804. if ($count) {
  805. if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
  806. array_shift($matches);
  807. $function->setArguments($matches);
  808. return $function;
  809. }
  810. }
  811. }
  812. foreach ($this->functionCallbacks as $callback) {
  813. if (false !== $function = call_user_func($callback, $name)) {
  814. return $function;
  815. }
  816. }
  817. return false;
  818. }
  819. public function registerUndefinedFunctionCallback($callable)
  820. {
  821. $this->functionCallbacks[] = $callable;
  822. }
  823. /**
  824. * Gets registered functions.
  825. *
  826. * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
  827. *
  828. * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
  829. *
  830. * @see registerUndefinedFunctionCallback
  831. */
  832. public function getFunctions()
  833. {
  834. if (null === $this->functions) {
  835. $this->functions = isset($this->staging['functions']) ? $this->staging['functions'] : array();
  836. foreach ($this->getExtensions() as $extension) {
  837. $this->functions = array_merge($this->functions, $extension->getFunctions());
  838. }
  839. }
  840. return $this->functions;
  841. }
  842. /**
  843. * Registers a Global.
  844. *
  845. * @param string $name The global name
  846. * @param mixed $value The global value
  847. */
  848. public function addGlobal($name, $value)
  849. {
  850. $this->staging['globals'][$name] = $value;
  851. $this->globals = null;
  852. }
  853. /**
  854. * Gets the registered Globals.
  855. *
  856. * @return array An array of globals
  857. */
  858. public function getGlobals()
  859. {
  860. if (null === $this->globals) {
  861. $this->globals = isset($this->staging['globals']) ? $this->staging['globals'] : array();
  862. foreach ($this->getExtensions() as $extension) {
  863. $this->globals = array_merge($this->globals, $extension->getGlobals());
  864. }
  865. }
  866. return $this->globals;
  867. }
  868. /**
  869. * Merges a context with the defined globals.
  870. *
  871. * @param array $context An array representing the context
  872. *
  873. * @return array The context merged with the globals
  874. */
  875. public function mergeGlobals(array $context)
  876. {
  877. // we don't use array_merge as the context being generally
  878. // bigger than globals, this code is faster.
  879. foreach ($this->getGlobals() as $key => $value) {
  880. if (!array_key_exists($key, $context)) {
  881. $context[$key] = $value;
  882. }
  883. }
  884. return $context;
  885. }
  886. /**
  887. * Gets the registered unary Operators.
  888. *
  889. * @return array An array of unary operators
  890. */
  891. public function getUnaryOperators()
  892. {
  893. if (null === $this->unaryOperators) {
  894. $this->initOperators();
  895. }
  896. return $this->unaryOperators;
  897. }
  898. /**
  899. * Gets the registered binary Operators.
  900. *
  901. * @return array An array of binary operators
  902. */
  903. public function getBinaryOperators()
  904. {
  905. if (null === $this->binaryOperators) {
  906. $this->initOperators();
  907. }
  908. return $this->binaryOperators;
  909. }
  910. public function computeAlternatives($name, $items)
  911. {
  912. $alternatives = array();
  913. foreach ($items as $item) {
  914. $lev = levenshtein($name, $item);
  915. if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
  916. $alternatives[$item] = $lev;
  917. }
  918. }
  919. asort($alternatives);
  920. return array_keys($alternatives);
  921. }
  922. protected function initOperators()
  923. {
  924. $this->unaryOperators = array();
  925. $this->binaryOperators = array();
  926. foreach ($this->getExtensions() as $extension) {
  927. $operators = $extension->getOperators();
  928. if (!$operators) {
  929. continue;
  930. }
  931. if (2 !== count($operators)) {
  932. throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
  933. }
  934. $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
  935. $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
  936. }
  937. }
  938. protected function writeCacheFile($file, $content)
  939. {
  940. $dir = dirname($file);
  941. if (!is_dir($dir)) {
  942. if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
  943. throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
  944. }
  945. } elseif (!is_writable($dir)) {
  946. throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
  947. }
  948. $tmpFile = tempnam(dirname($file), basename($file));
  949. if (false !== @file_put_contents($tmpFile, $content)) {
  950. // rename does not work on Win32 before 5.2.6
  951. if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
  952. @chmod($file, 0644);
  953. return;
  954. }
  955. }
  956. throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
  957. }
  958. }