PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/fuel/application/libraries/dwoo/Dwoo/Compiler.php

http://github.com/daylightstudio/FUEL-CMS
PHP | 2023 lines | 1270 code | 199 blank | 554 comment | 378 complexity | a6e9132f10f311121438a94ee11dd6a6 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. include dirname(__FILE__) . '/Compilation/Exception.php';
  3. /**
  4. * default dwoo compiler class, compiles dwoo templates into php
  5. *
  6. * This software is provided 'as-is', without any express or implied warranty.
  7. * In no event will the authors be held liable for any damages arising from the use of this software.
  8. *
  9. * @author Jordi Boggiano <j.boggiano@seld.be>
  10. * @copyright Copyright (c) 2008, Jordi Boggiano
  11. * @license http://dwoo.org/LICENSE Modified BSD License
  12. * @link http://dwoo.org/
  13. * @version 1.1.0
  14. * @date 2009-07-18
  15. * @package Dwoo
  16. */
  17. class Dwoo_Compiler implements Dwoo_ICompiler
  18. {
  19. /**
  20. * constant that represents a php opening tag
  21. *
  22. * use it in case it needs to be adjusted
  23. *
  24. * @var string
  25. */
  26. const PHP_OPEN = "<?php ";
  27. /**
  28. * constant that represents a php closing tag
  29. *
  30. * use it in case it needs to be adjusted
  31. *
  32. * @var string
  33. */
  34. const PHP_CLOSE = "?>";
  35. /**
  36. * boolean flag to enable or disable debugging output
  37. *
  38. * @var bool
  39. */
  40. public $debug = false;
  41. /**
  42. * left script delimiter
  43. *
  44. * @var string
  45. */
  46. protected $ld = '{';
  47. /**
  48. * left script delimiter with escaped regex meta characters
  49. *
  50. * @var string
  51. */
  52. protected $ldr = '\\{';
  53. /**
  54. * right script delimiter
  55. *
  56. * @var string
  57. */
  58. protected $rd = '}';
  59. /**
  60. * right script delimiter with escaped regex meta characters
  61. *
  62. * @var string
  63. */
  64. protected $rdr = '\\}';
  65. /**
  66. * defines whether the nested comments should be parsed as nested or not
  67. *
  68. * defaults to false (classic block comment parsing as in all languages)
  69. *
  70. * @var bool
  71. */
  72. protected $allowNestedComments = false;
  73. /**
  74. * defines whether opening and closing tags can contain spaces before valid data or not
  75. *
  76. * turn to true if you want to be sloppy with the syntax, but when set to false it allows
  77. * to skip javascript and css tags as long as they are in the form "{ something", which is
  78. * nice. default is false.
  79. *
  80. * @var bool
  81. */
  82. protected $allowLooseOpenings = false;
  83. /**
  84. * defines whether the compiler will automatically html-escape variables or not
  85. *
  86. * default is false
  87. *
  88. * @var bool
  89. */
  90. protected $autoEscape = false;
  91. /**
  92. * security policy object
  93. *
  94. * @var Dwoo_Security_Policy
  95. */
  96. protected $securityPolicy;
  97. /**
  98. * stores the custom plugins registered with this compiler
  99. *
  100. * @var array
  101. */
  102. protected $customPlugins = array();
  103. /**
  104. * stores the template plugins registered with this compiler
  105. *
  106. * @var array
  107. */
  108. protected $templatePlugins = array();
  109. /**
  110. * stores the pre- and post-processors callbacks
  111. *
  112. * @var array
  113. */
  114. protected $processors = array('pre'=>array(), 'post'=>array());
  115. /**
  116. * stores a list of plugins that are used in the currently compiled
  117. * template, and that are not compilable. these plugins will be loaded
  118. * during the template's runtime if required.
  119. *
  120. * it is a 1D array formatted as key:pluginName value:pluginType
  121. *
  122. * @var array
  123. */
  124. protected $usedPlugins;
  125. /**
  126. * stores the template undergoing compilation
  127. *
  128. * @var string
  129. */
  130. protected $template;
  131. /**
  132. * stores the current pointer position inside the template
  133. *
  134. * @var int
  135. */
  136. protected $pointer;
  137. /**
  138. * stores the current line count inside the template for debugging purposes
  139. *
  140. * @var int
  141. */
  142. protected $line;
  143. /**
  144. * stores the current template source while compiling it
  145. *
  146. * @var string
  147. */
  148. protected $templateSource;
  149. /**
  150. * stores the data within which the scope moves
  151. *
  152. * @var array
  153. */
  154. protected $data;
  155. /**
  156. * variable scope of the compiler, set to null if
  157. * it can not be resolved to a static string (i.e. if some
  158. * plugin defines a new scope based on a variable array key)
  159. *
  160. * @var mixed
  161. */
  162. protected $scope;
  163. /**
  164. * variable scope tree, that allows to rebuild the current
  165. * scope if required, i.e. when going to a parent level
  166. *
  167. * @var array
  168. */
  169. protected $scopeTree;
  170. /**
  171. * block plugins stack, accessible through some methods
  172. *
  173. * @see findBlock
  174. * @see getCurrentBlock
  175. * @see addBlock
  176. * @see addCustomBlock
  177. * @see injectBlock
  178. * @see removeBlock
  179. * @see removeTopBlock
  180. *
  181. * @var array
  182. */
  183. protected $stack = array();
  184. /**
  185. * current block at the top of the block plugins stack,
  186. * accessible through getCurrentBlock
  187. *
  188. * @see getCurrentBlock
  189. *
  190. * @var Dwoo_Block_Plugin
  191. */
  192. protected $curBlock;
  193. /**
  194. * current dwoo object that uses this compiler, or null
  195. *
  196. * @var Dwoo
  197. */
  198. protected $dwoo;
  199. /**
  200. * holds an instance of this class, used by getInstance when you don't
  201. * provide a custom compiler in order to save resources
  202. *
  203. * @var Dwoo_Compiler
  204. */
  205. protected static $instance;
  206. /**
  207. * token types
  208. * @var int
  209. */
  210. const T_UNQUOTED_STRING = 1;
  211. const T_NUMERIC = 2;
  212. const T_NULL = 4;
  213. const T_BOOL = 8;
  214. const T_MATH = 16;
  215. const T_BREAKCHAR = 32;
  216. /**
  217. * constructor
  218. *
  219. * saves the created instance so that child templates get the same one
  220. */
  221. public function __construct()
  222. {
  223. self::$instance = $this;
  224. }
  225. /**
  226. * sets the delimiters to use in the templates
  227. *
  228. * delimiters can be multi-character strings but should not be one of those as they will
  229. * make it very hard to work with templates or might even break the compiler entirely : "\", "$", "|", ":" and finally "#" only if you intend to use config-vars with the #var# syntax.
  230. *
  231. * @param string $left left delimiter
  232. * @param string $right right delimiter
  233. */
  234. public function setDelimiters($left, $right)
  235. {
  236. $this->ld = $left;
  237. $this->rd = $right;
  238. $this->ldr = preg_quote($left, '/');
  239. $this->rdr = preg_quote($right, '/');
  240. }
  241. /**
  242. * returns the left and right template delimiters
  243. *
  244. * @return array containing the left and the right delimiters
  245. */
  246. public function getDelimiters()
  247. {
  248. return array($this->ld, $this->rd);
  249. }
  250. /**
  251. * sets the way to handle nested comments, if set to true
  252. * {* foo {* some other *} comment *} will be stripped correctly.
  253. *
  254. * if false it will remove {* foo {* some other *} and leave "comment *}" alone,
  255. * this is the default behavior
  256. *
  257. * @param bool $allow allow nested comments or not, defaults to true (but the default internal value is false)
  258. */
  259. public function setNestedCommentsHandling($allow = true) {
  260. $this->allowNestedComments = (bool) $allow;
  261. }
  262. /**
  263. * returns the nested comments handling setting
  264. *
  265. * @see setNestedCommentsHandling
  266. * @return bool true if nested comments are allowed
  267. */
  268. public function getNestedCommentsHandling() {
  269. return $this->allowNestedComments;
  270. }
  271. /**
  272. * sets the tag openings handling strictness, if set to true, template tags can
  273. * contain spaces before the first function/string/variable such as { $foo} is valid.
  274. *
  275. * if set to false (default setting), { $foo} is invalid but that is however a good thing
  276. * as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering
  277. * an error, same goes for javascript.
  278. *
  279. * @param bool $allow true to allow loose handling, false to restore default setting
  280. */
  281. public function setLooseOpeningHandling($allow = false)
  282. {
  283. $this->allowLooseOpenings = (bool) $allow;
  284. }
  285. /**
  286. * returns the tag openings handling strictness setting
  287. *
  288. * @see setLooseOpeningHandling
  289. * @return bool true if loose tags are allowed
  290. */
  291. public function getLooseOpeningHandling()
  292. {
  293. return $this->allowLooseOpenings;
  294. }
  295. /**
  296. * changes the auto escape setting
  297. *
  298. * if enabled, the compiler will automatically html-escape variables,
  299. * unless they are passed through the safe function such as {$var|safe}
  300. * or {safe $var}
  301. *
  302. * default setting is disabled/false
  303. *
  304. * @param bool $enabled set to true to enable, false to disable
  305. */
  306. public function setAutoEscape($enabled)
  307. {
  308. $this->autoEscape = (bool) $enabled;
  309. }
  310. /**
  311. * returns the auto escape setting
  312. *
  313. * default setting is disabled/false
  314. *
  315. * @return bool
  316. */
  317. public function getAutoEscape()
  318. {
  319. return $this->autoEscape;
  320. }
  321. /**
  322. * adds a preprocessor to the compiler, it will be called
  323. * before the template is compiled
  324. *
  325. * @param mixed $callback either a valid callback to the preprocessor or a simple name if the autoload is set to true
  326. * @param bool $autoload if set to true, the preprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
  327. */
  328. public function addPreProcessor($callback, $autoload = false)
  329. {
  330. if ($autoload) {
  331. $name = str_replace('Dwoo_Processor_', '', $callback);
  332. $class = 'Dwoo_Processor_'.$name;
  333. if (class_exists($class, false)) {
  334. $callback = array(new $class($this), 'process');
  335. } elseif (function_exists($class)) {
  336. $callback = $class;
  337. } else {
  338. $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
  339. }
  340. $this->processors['pre'][] = $callback;
  341. } else {
  342. $this->processors['pre'][] = $callback;
  343. }
  344. }
  345. /**
  346. * removes a preprocessor from the compiler
  347. *
  348. * @param mixed $callback either a valid callback to the preprocessor or a simple name if it was autoloaded
  349. */
  350. public function removePreProcessor($callback)
  351. {
  352. if (($index = array_search($callback, $this->processors['pre'], true)) !== false) {
  353. unset($this->processors['pre'][$index]);
  354. } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['pre'], true)) !== false) {
  355. unset($this->processors['pre'][$index]);
  356. } else {
  357. $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
  358. foreach ($this->processors['pre'] as $index=>$proc) {
  359. if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
  360. unset($this->processors['pre'][$index]);
  361. break;
  362. }
  363. }
  364. }
  365. }
  366. /**
  367. * adds a postprocessor to the compiler, it will be called
  368. * before the template is compiled
  369. *
  370. * @param mixed $callback either a valid callback to the postprocessor or a simple name if the autoload is set to true
  371. * @param bool $autoload if set to true, the postprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
  372. */
  373. public function addPostProcessor($callback, $autoload = false)
  374. {
  375. if ($autoload) {
  376. $name = str_replace('Dwoo_Processor_', '', $callback);
  377. $class = 'Dwoo_Processor_'.$name;
  378. if (class_exists($class, false)) {
  379. $callback = array(new $class($this), 'process');
  380. } elseif (function_exists($class)) {
  381. $callback = $class;
  382. } else {
  383. $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
  384. }
  385. $this->processors['post'][] = $callback;
  386. } else {
  387. $this->processors['post'][] = $callback;
  388. }
  389. }
  390. /**
  391. * removes a postprocessor from the compiler
  392. *
  393. * @param mixed $callback either a valid callback to the postprocessor or a simple name if it was autoloaded
  394. */
  395. public function removePostProcessor($callback)
  396. {
  397. if (($index = array_search($callback, $this->processors['post'], true)) !== false) {
  398. unset($this->processors['post'][$index]);
  399. } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['post'], true)) !== false) {
  400. unset($this->processors['post'][$index]);
  401. } else {
  402. $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
  403. foreach ($this->processors['post'] as $index=>$proc) {
  404. if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
  405. unset($this->processors['post'][$index]);
  406. break;
  407. }
  408. }
  409. }
  410. }
  411. /**
  412. * internal function to autoload processors at runtime if required
  413. *
  414. * @param string $class the class/function name
  415. * @param string $name the plugin name (without Dwoo_Plugin_ prefix)
  416. */
  417. protected function loadProcessor($class, $name)
  418. {
  419. if (!class_exists($class, false) && !function_exists($class)) {
  420. try {
  421. $this->dwoo->getLoader()->loadPlugin($name);
  422. } catch (Dwoo_Exception $e) {
  423. throw new Dwoo_Exception('Processor '.$name.' could not be found in your plugin directories, please ensure it is in a file named '.$name.'.php in the plugin directory');
  424. }
  425. }
  426. if (class_exists($class, false)) {
  427. return array(new $class($this), 'process');
  428. }
  429. if (function_exists($class)) {
  430. return $class;
  431. }
  432. throw new Dwoo_Exception('Wrong processor name, when using autoload the processor must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Processor_name"');
  433. }
  434. /**
  435. * adds an used plugin, this is reserved for use by the {template} plugin
  436. *
  437. * this is required so that plugin loading bubbles up from loaded
  438. * template files to the current one
  439. *
  440. * @private
  441. * @param string $name function name
  442. * @param int $type plugin type (Dwoo_Core::*_PLUGIN)
  443. */
  444. public function addUsedPlugin($name, $type)
  445. {
  446. $this->usedPlugins[$name] = $type;
  447. }
  448. /**
  449. * returns all the plugins this template uses
  450. *
  451. * @private
  452. * @return array the list of used plugins in the parsed template
  453. */
  454. public function getUsedPlugins()
  455. {
  456. return $this->usedPlugins;
  457. }
  458. /**
  459. * adds a template plugin, this is reserved for use by the {template} plugin
  460. *
  461. * this is required because the template functions are not declared yet
  462. * during compilation, so we must have a way of validating their argument
  463. * signature without using the reflection api
  464. *
  465. * @private
  466. * @param string $name function name
  467. * @param array $params parameter array to help validate the function call
  468. * @param string $uuid unique id of the function
  469. * @param string $body function php code
  470. */
  471. public function addTemplatePlugin($name, array $params, $uuid, $body = null)
  472. {
  473. $this->templatePlugins[$name] = array('params'=> $params, 'body' => $body, 'uuid' => $uuid);
  474. }
  475. /**
  476. * returns all the parsed sub-templates
  477. *
  478. * @private
  479. * @return array the parsed sub-templates
  480. */
  481. public function getTemplatePlugins()
  482. {
  483. return $this->templatePlugins;
  484. }
  485. /**
  486. * marks a template plugin as being called, which means its source must be included in the compiled template
  487. *
  488. * @param string $name function name
  489. */
  490. public function useTemplatePlugin($name)
  491. {
  492. $this->templatePlugins[$name]['called'] = true;
  493. }
  494. /**
  495. * adds the custom plugins loaded into Dwoo to the compiler so it can load them
  496. *
  497. * @see Dwoo_Core::addPlugin
  498. * @param array $customPlugins an array of custom plugins
  499. */
  500. public function setCustomPlugins(array $customPlugins)
  501. {
  502. $this->customPlugins = $customPlugins;
  503. }
  504. /**
  505. * sets the security policy object to enforce some php security settings
  506. *
  507. * use this if untrusted persons can modify templates,
  508. * set it on the Dwoo object as it will be passed onto the compiler automatically
  509. *
  510. * @param Dwoo_Security_Policy $policy the security policy object
  511. */
  512. public function setSecurityPolicy(Dwoo_Security_Policy $policy = null)
  513. {
  514. $this->securityPolicy = $policy;
  515. }
  516. /**
  517. * returns the current security policy object or null by default
  518. *
  519. * @return Dwoo_Security_Policy|null the security policy object if any
  520. */
  521. public function getSecurityPolicy()
  522. {
  523. return $this->securityPolicy;
  524. }
  525. /**
  526. * sets the pointer position
  527. *
  528. * @param int $position the new pointer position
  529. * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
  530. */
  531. public function setPointer($position, $isOffset = false)
  532. {
  533. if ($isOffset) {
  534. $this->pointer += $position;
  535. } else {
  536. $this->pointer = $position;
  537. }
  538. }
  539. /**
  540. * returns the current pointer position, only available during compilation of a template
  541. *
  542. * @return int
  543. */
  544. public function getPointer()
  545. {
  546. return $this->pointer;
  547. }
  548. /**
  549. * sets the line number
  550. *
  551. * @param int $number the new line number
  552. * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
  553. */
  554. public function setLine($number, $isOffset = false)
  555. {
  556. if ($isOffset) {
  557. $this->line += $number;
  558. } else {
  559. $this->line = $number;
  560. }
  561. }
  562. /**
  563. * returns the current line number, only available during compilation of a template
  564. *
  565. * @return int
  566. */
  567. public function getLine()
  568. {
  569. return $this->line;
  570. }
  571. /**
  572. * returns the dwoo object that initiated this template compilation, only available during compilation of a template
  573. *
  574. * @return Dwoo
  575. */
  576. public function getDwoo()
  577. {
  578. return $this->dwoo;
  579. }
  580. /**
  581. * overwrites the template that is being compiled
  582. *
  583. * @param string $newSource the template source that must replace the current one
  584. * @param bool $fromPointer if set to true, only the source from the current pointer position is replaced
  585. * @return string the template or partial template
  586. */
  587. public function setTemplateSource($newSource, $fromPointer = false)
  588. {
  589. if ($fromPointer === true) {
  590. $this->templateSource = substr($this->templateSource, 0, $this->pointer) . $newSource;
  591. } else {
  592. $this->templateSource = $newSource;
  593. }
  594. }
  595. /**
  596. * returns the template that is being compiled
  597. *
  598. * @param mixed $fromPointer if set to true, only the source from the current pointer
  599. * position is returned, if a number is given it overrides the current pointer
  600. * @return string the template or partial template
  601. */
  602. public function getTemplateSource($fromPointer = false)
  603. {
  604. if ($fromPointer === true) {
  605. return substr($this->templateSource, $this->pointer);
  606. } elseif (is_numeric($fromPointer)) {
  607. return substr($this->templateSource, $fromPointer);
  608. } else {
  609. return $this->templateSource;
  610. }
  611. }
  612. /**
  613. * resets the compilation pointer, effectively restarting the compilation process
  614. *
  615. * this is useful if a plugin modifies the template source since it might need to be recompiled
  616. */
  617. public function recompile()
  618. {
  619. $this->setPointer(0);
  620. }
  621. /**
  622. * compiles the provided string down to php code
  623. *
  624. * @param string $tpl the template to compile
  625. * @return string a compiled php string
  626. */
  627. public function compile(Dwoo_Core $dwoo, Dwoo_ITemplate $template)
  628. {
  629. // init vars
  630. $tpl = $template->getSource();
  631. $ptr = 0;
  632. $this->dwoo = $dwoo;
  633. $this->template = $template;
  634. $this->templateSource =& $tpl;
  635. $this->pointer =& $ptr;
  636. while (true) {
  637. // if pointer is at the beginning, reset everything, that allows a plugin to externally reset the compiler if everything must be reparsed
  638. if ($ptr===0) {
  639. // resets variables
  640. $this->usedPlugins = array();
  641. $this->data = array();
  642. $this->scope =& $this->data;
  643. $this->scopeTree = array();
  644. $this->stack = array();
  645. $this->line = 1;
  646. $this->templatePlugins = array();
  647. // add top level block
  648. $compiled = $this->addBlock('topLevelBlock', array(), 0);
  649. $this->stack[0]['buffer'] = '';
  650. if ($this->debug) echo 'COMPILER INIT<br />';
  651. if ($this->debug) echo 'PROCESSING PREPROCESSORS ('.count($this->processors['pre']).')<br>';
  652. // runs preprocessors
  653. foreach ($this->processors['pre'] as $preProc) {
  654. if (is_array($preProc) && isset($preProc['autoload'])) {
  655. $preProc = $this->loadProcessor($preProc['class'], $preProc['name']);
  656. }
  657. if (is_array($preProc) && $preProc[0] instanceof Dwoo_Processor) {
  658. $tpl = call_user_func($preProc, $tpl);
  659. } else {
  660. $tpl = call_user_func($preProc, $this, $tpl);
  661. }
  662. }
  663. unset($preProc);
  664. // show template source if debug
  665. if ($this->debug) echo '<pre>'.print_r(htmlentities($tpl), true).'</pre><hr />';
  666. // strips php tags if required by the security policy
  667. if ($this->securityPolicy !== null) {
  668. $search = array('{<\?php.*?\?>}');
  669. if (ini_get('short_open_tags')) {
  670. $search = array('{<\?.*?\?>}', '{<%.*?%>}');
  671. }
  672. switch($this->securityPolicy->getPhpHandling()) {
  673. case Dwoo_Security_Policy::PHP_ALLOW:
  674. break;
  675. case Dwoo_Security_Policy::PHP_ENCODE:
  676. $tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl);
  677. break;
  678. case Dwoo_Security_Policy::PHP_REMOVE:
  679. $tpl = preg_replace($search, '', $tpl);
  680. }
  681. }
  682. }
  683. $pos = strpos($tpl, $this->ld, $ptr);
  684. if ($pos === false) {
  685. $this->push(substr($tpl, $ptr), 0);
  686. break;
  687. } elseif (substr($tpl, $pos-1, 1) === '\\' && substr($tpl, $pos-2, 1) !== '\\') {
  688. $this->push(substr($tpl, $ptr, $pos-$ptr-1) . $this->ld);
  689. $ptr = $pos+strlen($this->ld);
  690. } elseif (preg_match('/^'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . 'literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', substr($tpl, $pos), $litOpen)) {
  691. if (!preg_match('/'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . '\/literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', $tpl, $litClose, PREG_OFFSET_CAPTURE, $pos)) {
  692. throw new Dwoo_Compilation_Exception($this, 'The {literal} blocks must be closed explicitly with {/literal}');
  693. }
  694. $endpos = $litClose[0][1];
  695. $this->push(substr($tpl, $ptr, $pos-$ptr) . substr($tpl, $pos + strlen($litOpen[0]), $endpos-$pos-strlen($litOpen[0])));
  696. $ptr = $endpos+strlen($litClose[0][0]);
  697. } else {
  698. if (substr($tpl, $pos-2, 1) === '\\' && substr($tpl, $pos-1, 1) === '\\') {
  699. $this->push(substr($tpl, $ptr, $pos-$ptr-1));
  700. $ptr = $pos;
  701. }
  702. $this->push(substr($tpl, $ptr, $pos-$ptr));
  703. $ptr = $pos;
  704. $pos += strlen($this->ld);
  705. if ($this->allowLooseOpenings) {
  706. while (substr($tpl, $pos, 1) === ' ') {
  707. $pos+=1;
  708. }
  709. } else {
  710. if (substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n" || substr($tpl, $pos, 1) === "\t") {
  711. $ptr = $pos;
  712. $this->push($this->ld);
  713. continue;
  714. }
  715. }
  716. // check that there is an end tag present
  717. if (strpos($tpl, $this->rd, $pos) === false) {
  718. throw new Dwoo_Compilation_Exception($this, 'A template tag was not closed, started with "'.substr($tpl, $ptr, 30).'"');
  719. }
  720. $ptr += strlen($this->ld);
  721. $subptr = $ptr;
  722. while (true) {
  723. $parsed = $this->parse($tpl, $subptr, null, false, 'root', $subptr);
  724. // reload loop if the compiler was reset
  725. if ($ptr === 0) {
  726. continue 2;
  727. }
  728. $len = $subptr - $ptr;
  729. $this->push($parsed, substr_count(substr($tpl, $ptr, $len), "\n"));
  730. $ptr += $len;
  731. if ($parsed === false) {
  732. break;
  733. }
  734. }
  735. // adds additional line breaks between php closing and opening tags because the php parser removes those if there is just a single line break
  736. if (substr($this->curBlock['buffer'], -2) === '?>' && preg_match('{^(([\r\n])([\r\n]?))}', substr($tpl, $ptr, 3), $m)) {
  737. if ($m[3] === '') {
  738. $ptr+=1;
  739. $this->push($m[1].$m[1], 1);
  740. } else {
  741. $ptr+=2;
  742. $this->push($m[1]."\n", 2);
  743. }
  744. }
  745. }
  746. }
  747. $compiled .= $this->removeBlock('topLevelBlock');
  748. if ($this->debug) echo 'PROCESSING POSTPROCESSORS<br>';
  749. foreach ($this->processors['post'] as $postProc) {
  750. if (is_array($postProc) && isset($postProc['autoload'])) {
  751. $postProc = $this->loadProcessor($postProc['class'], $postProc['name']);
  752. }
  753. if (is_array($postProc) && $postProc[0] instanceof Dwoo_Processor) {
  754. $compiled = call_user_func($postProc, $compiled);
  755. } else {
  756. $compiled = call_user_func($postProc, $this, $compiled);
  757. }
  758. }
  759. unset($postProc);
  760. if ($this->debug) echo 'COMPILATION COMPLETE : MEM USAGE : '.memory_get_usage().'<br>';
  761. $output = "<?php\n/* template head */\n";
  762. // build plugin preloader
  763. foreach ($this->usedPlugins as $plugin=>$type) {
  764. if ($type & Dwoo_Core::CUSTOM_PLUGIN) {
  765. continue;
  766. }
  767. switch($type) {
  768. case Dwoo_Core::BLOCK_PLUGIN:
  769. case Dwoo_Core::CLASS_PLUGIN:
  770. $output .= "if (class_exists('Dwoo_Plugin_$plugin', false)===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
  771. break;
  772. case Dwoo_Core::FUNC_PLUGIN:
  773. $output .= "if (function_exists('Dwoo_Plugin_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
  774. break;
  775. case Dwoo_Core::SMARTY_MODIFIER:
  776. $output .= "if (function_exists('smarty_modifier_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
  777. break;
  778. case Dwoo_Core::SMARTY_FUNCTION:
  779. $output .= "if (function_exists('smarty_function_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
  780. break;
  781. case Dwoo_Core::SMARTY_BLOCK:
  782. $output .= "if (function_exists('smarty_block_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
  783. break;
  784. case Dwoo_Core::PROXY_PLUGIN:
  785. $output .= $this->getDwoo()->getPluginProxy()->getPreloader($plugin);
  786. break;
  787. default:
  788. throw new Dwoo_Compilation_Exception($this, 'Type error for '.$plugin.' with type'.$type);
  789. }
  790. }
  791. foreach ($this->templatePlugins as $function => $attr) {
  792. if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) {
  793. $this->resolveSubTemplateDependencies($function);
  794. }
  795. }
  796. foreach ($this->templatePlugins as $function) {
  797. if (isset($function['called']) && $function['called'] === true) {
  798. $output .= $function['body'].PHP_EOL;
  799. }
  800. }
  801. $output .= $compiled."\n?>";
  802. $output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*'.preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/').')/', ";\n", $output);
  803. $output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output);
  804. // handle <?xml tag at the beginning
  805. $output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output);
  806. if ($this->debug) {
  807. echo '<hr><pre>';
  808. $lines = preg_split('{\r\n|\n|<br />}', highlight_string(($output), true));
  809. array_shift($lines);
  810. foreach ($lines as $i=>$line) {
  811. echo ($i+1).'. '.$line."\r\n";
  812. }
  813. }
  814. if ($this->debug) echo '<hr></pre></pre>';
  815. $this->template = $this->dwoo = null;
  816. $tpl = null;
  817. return $output;
  818. }
  819. /**
  820. * checks what sub-templates are used in every sub-template so that we're sure they are all compiled
  821. *
  822. * @param string $function the sub-template name
  823. */
  824. protected function resolveSubTemplateDependencies($function)
  825. {
  826. $body = $this->templatePlugins[$function]['body'];
  827. foreach ($this->templatePlugins as $func => $attr) {
  828. if ($func !== $function && !isset($attr['called']) && strpos($body, 'Dwoo_Plugin_'.$func) !== false) {
  829. $this->templatePlugins[$func]['called'] = true;
  830. $this->resolveSubTemplateDependencies($func);
  831. }
  832. }
  833. $this->templatePlugins[$function]['checked'] = true;
  834. }
  835. /**
  836. * adds compiled content to the current block
  837. *
  838. * @param string $content the content to push
  839. * @param int $lineCount newlines count in content, optional
  840. */
  841. public function push($content, $lineCount = null)
  842. {
  843. if ($lineCount === null) {
  844. $lineCount = substr_count($content, "\n");
  845. }
  846. if ($this->curBlock['buffer'] === null && count($this->stack) > 1) {
  847. // buffer is not initialized yet (the block has just been created)
  848. $this->stack[count($this->stack)-2]['buffer'] .= (string) $content;
  849. $this->curBlock['buffer'] = '';
  850. } else {
  851. if (!isset($this->curBlock['buffer'])) {
  852. throw new Dwoo_Compilation_Exception($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere');
  853. }
  854. // append current content to current block's buffer
  855. $this->curBlock['buffer'] .= (string) $content;
  856. }
  857. $this->line += $lineCount;
  858. }
  859. /**
  860. * sets the scope
  861. *
  862. * set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that
  863. * variables are compiled in a more evaluative way than just $this->scope['key']
  864. *
  865. * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
  866. * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope
  867. * @return array the current scope tree
  868. */
  869. public function setScope($scope, $absolute = false)
  870. {
  871. $old = $this->scopeTree;
  872. if ($scope===null) {
  873. unset($this->scope);
  874. $this->scope = null;
  875. }
  876. if (is_array($scope)===false) {
  877. $scope = explode('.', $scope);
  878. }
  879. if ($absolute===true) {
  880. $this->scope =& $this->data;
  881. $this->scopeTree = array();
  882. }
  883. while (($bit = array_shift($scope)) !== null) {
  884. if ($bit === '_parent' || $bit === '_') {
  885. array_pop($this->scopeTree);
  886. reset($this->scopeTree);
  887. $this->scope =& $this->data;
  888. $cnt = count($this->scopeTree);
  889. for ($i=0;$i<$cnt;$i++)
  890. $this->scope =& $this->scope[$this->scopeTree[$i]];
  891. } elseif ($bit === '_root' || $bit === '__') {
  892. $this->scope =& $this->data;
  893. $this->scopeTree = array();
  894. } elseif (isset($this->scope[$bit])) {
  895. $this->scope =& $this->scope[$bit];
  896. $this->scopeTree[] = $bit;
  897. } else {
  898. $this->scope[$bit] = array();
  899. $this->scope =& $this->scope[$bit];
  900. $this->scopeTree[] = $bit;
  901. }
  902. }
  903. return $old;
  904. }
  905. /**
  906. * adds a block to the top of the block stack
  907. *
  908. * @param string $type block type (name)
  909. * @param array $params the parameters array
  910. * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
  911. * @return string the preProcessing() method's output
  912. */
  913. public function addBlock($type, array $params, $paramtype)
  914. {
  915. $class = 'Dwoo_Plugin_'.$type;
  916. if (class_exists($class, false) === false) {
  917. $this->dwoo->getLoader()->loadPlugin($type);
  918. }
  919. $params = $this->mapParams($params, array($class, 'init'), $paramtype);
  920. $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
  921. $this->curBlock =& $this->stack[count($this->stack)-1];
  922. return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
  923. }
  924. /**
  925. * adds a custom block to the top of the block stack
  926. *
  927. * @param string $type block type (name)
  928. * @param array $params the parameters array
  929. * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
  930. * @return string the preProcessing() method's output
  931. */
  932. public function addCustomBlock($type, array $params, $paramtype)
  933. {
  934. $callback = $this->customPlugins[$type]['callback'];
  935. if (is_array($callback)) {
  936. $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
  937. } else {
  938. $class = $callback;
  939. }
  940. $params = $this->mapParams($params, array($class, 'init'), $paramtype);
  941. $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => true, 'class' => $class, 'buffer' => null);
  942. $this->curBlock =& $this->stack[count($this->stack)-1];
  943. return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
  944. }
  945. /**
  946. * injects a block at the top of the plugin stack without calling its preProcessing method
  947. *
  948. * used by {else} blocks to re-add themselves after having closed everything up to their parent
  949. *
  950. * @param string $type block type (name)
  951. * @param array $params parameters array
  952. */
  953. public function injectBlock($type, array $params)
  954. {
  955. $class = 'Dwoo_Plugin_'.$type;
  956. if (class_exists($class, false) === false) {
  957. $this->dwoo->getLoader()->loadPlugin($type);
  958. }
  959. $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
  960. $this->curBlock =& $this->stack[count($this->stack)-1];
  961. }
  962. /**
  963. * removes the closest-to-top block of the given type and all other
  964. * blocks encountered while going down the block stack
  965. *
  966. * @param string $type block type (name)
  967. * @return string the output of all postProcessing() method's return values of the closed blocks
  968. */
  969. public function removeBlock($type)
  970. {
  971. $output = '';
  972. $pluginType = $this->getPluginType($type);
  973. if ($pluginType & Dwoo_Core::SMARTY_BLOCK) {
  974. $type = 'smartyinterface';
  975. }
  976. while (true) {
  977. while ($top = array_pop($this->stack)) {
  978. if ($top['custom']) {
  979. $class = $top['class'];
  980. } else {
  981. $class = 'Dwoo_Plugin_'.$top['type'];
  982. }
  983. if (count($this->stack)) {
  984. $this->curBlock =& $this->stack[count($this->stack)-1];
  985. $this->push(call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']), 0);
  986. } else {
  987. $null = null;
  988. $this->curBlock =& $null;
  989. $output = call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']);
  990. }
  991. if ($top['type'] === $type) {
  992. break 2;
  993. }
  994. }
  995. throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of type "'.$type.'" was closed but was not opened');
  996. break;
  997. }
  998. return $output;
  999. }
  1000. /**
  1001. * returns a reference to the first block of the given type encountered and
  1002. * optionally closes all blocks until it finds it
  1003. *
  1004. * this is mainly used by {else} plugins to close everything that was opened
  1005. * between their parent and themselves
  1006. *
  1007. * @param string $type the block type (name)
  1008. * @param bool $closeAlong whether to close all blocks encountered while going down the block stack or not
  1009. * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
  1010. * 'custom'=>bool defining whether it's a custom plugin or not, for internal use)
  1011. */
  1012. public function &findBlock($type, $closeAlong = false)
  1013. {
  1014. if ($closeAlong===true) {
  1015. while ($b = end($this->stack)) {
  1016. if ($b['type']===$type) {
  1017. return $this->stack[key($this->stack)];
  1018. }
  1019. $this->push($this->removeTopBlock(), 0);
  1020. }
  1021. } else {
  1022. end($this->stack);
  1023. while ($b = current($this->stack)) {
  1024. if ($b['type']===$type) {
  1025. return $this->stack[key($this->stack)];
  1026. }
  1027. prev($this->stack);
  1028. }
  1029. }
  1030. throw new Dwoo_Compilation_Exception($this, 'A parent block of type "'.$type.'" is required and can not be found');
  1031. }
  1032. /**
  1033. * returns a reference to the current block array
  1034. *
  1035. * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
  1036. * 'custom'=>bool defining whether it's a custom plugin or not, for internal use)
  1037. */
  1038. public function &getCurrentBlock()
  1039. {
  1040. return $this->curBlock;
  1041. }
  1042. /**
  1043. * removes the block at the top of the stack and calls its postProcessing() method
  1044. *
  1045. * @return string the postProcessing() method's output
  1046. */
  1047. public function removeTopBlock()
  1048. {
  1049. $o = array_pop($this->stack);
  1050. if ($o === null) {
  1051. throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
  1052. }
  1053. if ($o['custom']) {
  1054. $class = $o['class'];
  1055. } else {
  1056. $class = 'Dwoo_Plugin_'.$o['type'];
  1057. }
  1058. $this->curBlock =& $this->stack[count($this->stack)-1];
  1059. return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
  1060. }
  1061. /**
  1062. * returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out of the given parameter array
  1063. *
  1064. * @param array $params parameter array
  1065. * @return array filtered parameters
  1066. */
  1067. public function getCompiledParams(array $params)
  1068. {
  1069. foreach ($params as $k=>$p) {
  1070. if (is_array($p)) {
  1071. $params[$k] = $p[0];
  1072. }
  1073. }
  1074. return $params;
  1075. }
  1076. /**
  1077. * returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given parameter array
  1078. *
  1079. * @param array $params parameter array
  1080. * @return array filtered parameters
  1081. */
  1082. public function getRealParams(array $params)
  1083. {
  1084. foreach ($params as $k=>$p) {
  1085. if (is_array($p)) {
  1086. $params[$k] = $p[1];
  1087. }
  1088. }
  1089. return $params;
  1090. }
  1091. /**
  1092. * returns the token of each parameter out of the given parameter array
  1093. *
  1094. * @param array $params parameter array
  1095. * @return array tokens
  1096. */
  1097. public function getParamTokens(array $params)
  1098. {
  1099. foreach ($params as $k=>$p) {
  1100. if (is_array($p)) {
  1101. $params[$k] = isset($p[2]) ? $p[2] : 0;
  1102. }
  1103. }
  1104. return $params;
  1105. }
  1106. /**
  1107. * entry point of the parser, it redirects calls to other parse* functions
  1108. *
  1109. * @param string $in the string within which we must parse something
  1110. * @param int $from the starting offset of the parsed area
  1111. * @param int $to the ending offset of the parsed area
  1112. * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
  1113. * @param string $curBlock the current parser-block being processed
  1114. * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
  1115. * @return string parsed values
  1116. */
  1117. protected function parse($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
  1118. {
  1119. if ($to === null) {
  1120. $to = strlen($in);
  1121. }
  1122. $first = substr($in, $from, 1);
  1123. if ($first === false) {
  1124. throw new Dwoo_Compilation_Exception($this, 'Unexpected EOF, a template tag was not closed');
  1125. }
  1126. while ($first===" " || $first==="\n" || $first==="\t" || $first==="\r") {
  1127. if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
  1128. // end template tag
  1129. $pointer += strlen($this->rd);
  1130. if ($this->debug) echo 'TEMPLATE PARSING ENDED<br />';
  1131. return false;
  1132. }
  1133. $from++;
  1134. if ($pointer !== null) {
  1135. $pointer++;
  1136. }
  1137. if ($from >= $to) {
  1138. if (is_array($parsingParams)) {
  1139. return $parsingParams;
  1140. } else {
  1141. return '';
  1142. }
  1143. }
  1144. $first = $in[$from];
  1145. }
  1146. $substr = substr($in, $from, $to-$from);
  1147. if ($this->debug) echo '<br />PARSE CALL : PARSING "<b>'.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').'</b>" @ '.$from.':'.$to.' in '.$curBlock.' : pointer='.$pointer.'<br/>';
  1148. $parsed = "";
  1149. if ($curBlock === 'root' && $first === '*') {
  1150. $src = $this->getTemplateSource();
  1151. $startpos = $this->getPointer() - strlen($this->ld);
  1152. if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
  1153. if ($startpos > 0) {
  1154. do {
  1155. $char = substr($src, --$startpos, 1);
  1156. if ($char == "\n") {
  1157. $startpos++;
  1158. $whitespaceStart = true;
  1159. break;
  1160. }
  1161. } while ($startpos > 0 && ($char == ' ' || $char == "\t"));
  1162. }
  1163. if (!isset($whitespaceStart)) {
  1164. $startpos = $this->getPointer();
  1165. } else {
  1166. $pointer -= $this->getPointer() - $startpos;
  1167. }
  1168. if ($this->allowNestedComments && strpos($src, $this->ld.'*', $this->getPointer()) !== false) {
  1169. $comOpen = $this->ld.'*';
  1170. $comClose = '*'.$this->rd;
  1171. $level = 1;
  1172. $start = $startpos;
  1173. $ptr = $this->getPointer() + '*';
  1174. while ($level > 0 && $ptr < strlen($src)) {
  1175. $open = strpos($src, $comOpen, $ptr);
  1176. $close = strpos($src, $comClose, $ptr);
  1177. if ($open !== false && $close !== false) {
  1178. if ($open < $close) {
  1179. $ptr = $open + strlen($comOpen);
  1180. $level++;
  1181. } else {
  1182. $ptr = $close + strlen($comClose);
  1183. $level--;
  1184. }
  1185. } elseif ($open !== false) {
  1186. $ptr = $open + strlen($comOpen);
  1187. $level++;
  1188. } elseif ($close !== false) {
  1189. $ptr = $close + strlen($comClose);
  1190. $level--;
  1191. } else {
  1192. $ptr = strlen($src);
  1193. }
  1194. }
  1195. $endpos = $ptr - strlen('*'.$this->rd);
  1196. } else {
  1197. $endpos = strpos($src, '*'.$this->rd, $startpos);
  1198. if ($endpos == false) {
  1199. throw new Dwoo_Compilation_Exception($this, 'Un-ended comment');
  1200. }
  1201. }
  1202. $pointer += $endpos - $startpos + strlen('*'.$this->rd);
  1203. if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos+strlen('*'.$this->rd)), $m)) {
  1204. $pointer += strlen($m[0]);
  1205. $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
  1206. }
  1207. return false;
  1208. }
  1209. }
  1210. if ($first==='$') {
  1211. // var
  1212. $out = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
  1213. $parsed = 'var';
  1214. } elseif ($first==='%' && preg_match('#^%[a-z_]#i', $substr)) {
  1215. // const
  1216. $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
  1217. } elseif (($first==='"' || $first==="'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) {
  1218. // string
  1219. $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
  1220. } elseif (preg_match('/^\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?('.(is_array($parsingParams)||$curBlock!='root'?'':'\s+[^(]|').'\s*\(|\s*'.$this->rdr.'|\s*;)/i', $substr)) {
  1221. // func
  1222. $out = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
  1223. $parsed = 'func';
  1224. } elseif ($first === ';') {
  1225. // instruction end
  1226. if ($this->debug) echo 'END OF INSTRUCTION<br />';
  1227. if ($pointer !== null) {
  1228. $pointer++;
  1229. }
  1230. return $this->parse($in, $from+1, $to, false, 'root', $pointer);
  1231. } elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) {
  1232. // close block
  1233. if (!empty($match[1]) && $match[1] == 'else') {
  1234. throw new Dwoo_Compilation_Exception($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
  1235. }
  1236. if (!empty($match[1]) && $match[1] == 'elseif') {
  1237. throw new Dwoo_Compilation_Exception($this, 'Elseif blocks must not be closed explicitly, they are automatically closed when their parent block is closed or a new else/elseif block is declared after them');
  1238. }
  1239. if ($pointer !== null) {
  1240. $pointer += strlen($match[0]);
  1241. }
  1242. if (empty($match[1])) {
  1243. if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
  1244. $pointer -= strlen($match[0]);
  1245. }
  1246. if ($this->debug) echo 'TOP BLOCK CLOSED<br />';
  1247. return $this->removeTopBlock();
  1248. } else {
  1249. if ($this->debug) echo 'BLOCK OF TYPE '.$match[1].' CLOSED<br />';
  1250. return $this->removeBlock($match[1]);
  1251. }
  1252. } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
  1253. // end template tag
  1254. if ($this->debug) echo 'TAG PARSING ENDED<br />';
  1255. $pointer += strlen($this->rd);
  1256. return false;
  1257. } elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*='.($curBlock === 'array' ? '>?':'').')(?:\s+|[^=]).*#i', $substr, $match)) {
  1258. // named parameter
  1259. if ($this->debug) echo 'NAMED PARAM FOUND<br />';
  1260. $len = strlen($match[1]);
  1261. while (substr($in, $from+$len, 1)===' ') {
  1262. $len++;
  1263. }
  1264. if ($pointer !== null) {
  1265. $pointer += $len;
  1266. }
  1267. $output = array(trim($match[1], " \t\r\n=>'\""), $this->parse($in, $from+$len, $to, false, 'namedparam', $pointer));
  1268. $parsingParams[] = $output;
  1269. return $parsingParams;
  1270. } elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) {
  1271. // static member access
  1272. $parsed = 'var';
  1273. if (is_array($parsingParams)) {
  1274. $parsingParams[] = array($match[1], $match[1]);
  1275. $out = $parsingParams;
  1276. } else {
  1277. $out = $match[1];
  1278. }
  1279. $pointer += strlen($match[1]);
  1280. } elseif ($substr!=='' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
  1281. // unquoted string, bool or number
  1282. $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
  1283. } else {
  1284. // parse error
  1285. throw new Dwoo_Compilation_Exception($this, 'Parse error in "'.substr($in, $from, $to-$from).'"');
  1286. }
  1287. if (empty($out)) {
  1288. return '';
  1289. }
  1290. $substr = substr($in, $pointer, $to-$pointer);
  1291. // var parsed, check if any var-extension applies
  1292. if ($parsed==='var') {
  1293. if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
  1294. if($this->debug) echo 'PARSING POST-VAR EXPRESSION '.$substr.'<br />';
  1295. // parse expressions
  1296. $pointer += strlen($match[0]) - 1;
  1297. if (is_array($parsingParams)) {
  1298. if ($match[2] == '$') {
  1299. $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
  1300. } else {
  1301. $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
  1302. }
  1303. $out[count($out)-1][0] .= $match[1] . $expr[0][0];
  1304. $out[count($out)-1][1] .= $match[1] . $expr[0][1];
  1305. } else {
  1306. if ($match[2] == '$') {
  1307. $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
  1308. } else {
  1309. $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
  1310. }
  1311. if (is_array($out) && is_array($expr)) {
  1312. $out[0] .= $match[1] . $expr[0];
  1313. $out[1] .= $match[1] . $expr[1];
  1314. } elseif (is_array($out)) {
  1315. $out[0] .= $match[1] . $expr;
  1316. $out[1] .= $match[1] . $expr;
  1317. } elseif (is_array($expr)) {
  1318. $out .= $match[1] . $expr[0];
  1319. } else {
  1320. $out .= $match[1] . $expr;
  1321. }
  1322. }
  1323. } else if ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
  1324. if($this->debug) echo 'PARSING POST-VAR ASSIGNMENT '.$substr.'<br />';
  1325. // parse assignment
  1326. $value = $match[2];
  1327. $operator = trim($match[1]);
  1328. if (substr($value, 0, 1) == '=') {
  1329. throw new Dwoo_Compilation_Exception($this, 'Unexpected "=" in <em>'.$substr.'</em>');
  1330. }
  1331. if ($pointer !== null) {
  1332. $pointer += strlen($match[1]);
  1333. }
  1334. if ($operator !== '++' && $operator !== '--') {
  1335. $parts = array();
  1336. $ptr = 0;
  1337. $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
  1338. $pointer += $ptr;
  1339. // load if plugin
  1340. try {
  1341. $this->getPluginType('if');
  1342. } catch (Dwoo_Exception $e) {
  1343. throw new Dwoo_Compilation_Exception($this, 'Assignments require the "if" plugin to be accessible');
  1344. }
  1345. $parts = $this->mapParams($parts, array('Dwoo_Plugin_if', 'init'), 1);
  1346. $tokens = $this->getParamTokens($parts);
  1347. $parts = $this->getCompiledParams($parts);
  1348. $value = Dwoo_Plugin_if::replaceKeywords($parts['*'], $tokens['*'], $this);
  1349. $echo = '';
  1350. } else {
  1351. $value = array();
  1352. $echo = 'echo ';
  1353. }
  1354. if ($this->autoEscape) {
  1355. $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
  1356. }
  1357. $out = Dwoo_Compiler::PHP_OPEN. $echo . $out . $operator . implode(' ', $value) . Dwoo_Compiler::PHP_CLOSE;
  1358. } else if ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) {
  1359. // parse namedparam with var as name (only for array)
  1360. if ($this->debug) echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND<br />';
  1361. $len = strlen($match[1]);
  1362. $var = $out[count($out)-1];
  1363. $pointer += $len;
  1364. $output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer));
  1365. $parsingParams[] = $output;
  1366. return $parsingParams;
  1367. }
  1368. }
  1369. if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^\|@?[a-z0-9_]+(:.*)?#i', $substr, $match)) {
  1370. // parse modifier on funcs or vars
  1371. $srcPointer = $pointer;
  1372. if (is_array($parsingParams)) {
  1373. $tmp = $this->replaceModifiers(array(null, null, $out[count($out)-1][0], $match[0]), 'var', $pointer);
  1374. $out[count($out)-1][0] = $tmp;
  1375. $out[count($out)-1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
  1376. } else {
  1377. $out = $this->replaceModifiers(array(null, null, $out, $match[0]), 'var', $pointer);
  1378. }
  1379. }
  1380. // func parsed, check if any func-extension applies
  1381. if ($parsed==='func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) {
  1382. // parse method call or property read
  1383. $ptr = 0;
  1384. if (is_array($parsingParams)) {
  1385. $output = $this->parseMethodCall($out[count($out)-1][1], $match[0], $curBlock, $ptr);
  1386. $out[count($out)-1][0] = $output;
  1387. $out[count($out)-1][1] .= substr($match[0], 0, $ptr);
  1388. } else {
  1389. $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
  1390. }
  1391. $pointer += $ptr;
  1392. }
  1393. if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
  1394. return self::PHP_OPEN .'echo '.$out.';'. self::PHP_CLOSE;
  1395. } else {
  1396. return $out;
  1397. }
  1398. }
  1399. /**
  1400. * parses a function call
  1401. *
  1402. * @param string $in the string within which we must parse something
  1403. * @param int $from the starting offset of the parsed area
  1404. * @param int $to the ending offset of the parsed area
  1405. * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
  1406. * @param string $curBlock the current parser-block being processed
  1407. * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
  1408. * @return string parsed values
  1409. */
  1410. protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
  1411. {
  1412. $cmdstr = substr($in, $from, $to-$from);
  1413. preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*'.$this->rdr.'|\s*;)?/i', $cmdstr, $match);
  1414. if (empty($match[1])) {
  1415. throw new Dwoo_Compilation_Exception($this, 'Parse error, invalid function name : '.substr($cmdstr, 0, 15));
  1416. }
  1417. $func = $match[1];
  1418. if (!empty($match[2])) {
  1419. $cmdstr = $match[1];
  1420. }
  1421. if ($this->debug) echo 'FUNC FOUND ('.$func.')<br />';
  1422. $paramsep = '';
  1423. if (is_array($parsingParams) || $curBlock != 'root') {
  1424. $paramspos = strpos($cmdstr, '(');
  1425. $paramsep = ')';
  1426. } elseif(preg_match_all('#[a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
  1427. $paramspos = $match[1][0][1];
  1428. $paramsep = substr($match[1][0][0], -1) === '(' ? ')':'';
  1429. if($paramsep === ')') {
  1430. $paramspos += strlen($match[1][0][0]) - 1;
  1431. if(substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
  1432. $paramsep = '';
  1433. if(strlen($match[1][0][0]) > 1) {
  1434. $paramspos--;
  1435. }
  1436. }
  1437. }
  1438. } else {
  1439. $paramspos = false;
  1440. }
  1441. $state = 0;
  1442. if ($paramspos === false) {
  1443. $params = array();
  1444. if ($curBlock !== 'root') {
  1445. return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
  1446. }
  1447. } else {
  1448. if ($curBlock === 'condition') {
  1449. // load if plugin
  1450. $this->getPluginType('if');
  1451. if (Dwoo_Plugin_if::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) {
  1452. return $this->parseOthers($in, $from, $t

Large files files are truncated, but you can click here to view the full file