Environment.php 29KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  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.6.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`, 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. * Gets the registered unary Operators.
  870. *
  871. * @return array An array of unary operators
  872. */
  873. public function getUnaryOperators()
  874. {
  875. if (null === $this->unaryOperators) {
  876. $this->initOperators();
  877. }
  878. return $this->unaryOperators;
  879. }
  880. /**
  881. * Gets the registered binary Operators.
  882. *
  883. * @return array An array of binary operators
  884. */
  885. public function getBinaryOperators()
  886. {
  887. if (null === $this->binaryOperators) {
  888. $this->initOperators();
  889. }
  890. return $this->binaryOperators;
  891. }
  892. public function computeAlternatives($name, $items)
  893. {
  894. $alternatives = array();
  895. foreach ($items as $item) {
  896. $lev = levenshtein($name, $item);
  897. if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
  898. $alternatives[$item] = $lev;
  899. }
  900. }
  901. asort($alternatives);
  902. return array_keys($alternatives);
  903. }
  904. protected function initOperators()
  905. {
  906. $this->unaryOperators = array();
  907. $this->binaryOperators = array();
  908. foreach ($this->getExtensions() as $extension) {
  909. $operators = $extension->getOperators();
  910. if (!$operators) {
  911. continue;
  912. }
  913. if (2 !== count($operators)) {
  914. throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
  915. }
  916. $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
  917. $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
  918. }
  919. }
  920. protected function writeCacheFile($file, $content)
  921. {
  922. $dir = dirname($file);
  923. if (!is_dir($dir)) {
  924. if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
  925. throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
  926. }
  927. } elseif (!is_writable($dir)) {
  928. throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
  929. }
  930. $tmpFile = tempnam(dirname($file), basename($file));
  931. if (false !== @file_put_contents($tmpFile, $content)) {
  932. // rename does not work on Win32 before 5.2.6
  933. if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
  934. chmod($file, 0644);
  935. return;
  936. }
  937. }
  938. throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
  939. }
  940. }