Environment.php 30KB

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