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

/includes/Opl/Opt/Compiler/Class.php

https://bitbucket.org/kandsten/hitta.sverok.se
PHP | 3663 lines | 2679 code | 193 blank | 791 comment | 484 complexity | 251f002e7a1918a53f611464877d43de MD5 | raw file
Possible License(s): GPL-3.0, MIT
  1. <?php
  2. /*
  3. * OPEN POWER LIBS <http://www.invenzzia.org>
  4. *
  5. * This file is subject to the new BSD license that is bundled
  6. * with this package in the file LICENSE. It is also available through
  7. * WWW at this URL: <http://www.invenzzia.org/license/new-bsd>
  8. *
  9. * Copyright (c) Invenzzia Group <http://www.invenzzia.org>
  10. * and other contributors. See website for details.
  11. *
  12. * $Id: Class.php 274 2009-12-29 10:17:48Z zyxist $
  13. */
  14. class Opt_Compiler_Class
  15. {
  16. // Opcodes
  17. const OP_VARIABLE = 1;
  18. const OP_LANGUAGE_VAR = 2;
  19. const OP_STRING = 4;
  20. const OP_NUMBER = 8;
  21. const OP_ARRAY = 16;
  22. const OP_OBJECT = 32;
  23. const OP_IDENTIFIER = 64;
  24. const OP_OPERATOR = 128;
  25. const OP_POST_OPERATOR = 256;
  26. const OP_PRE_OPERATOR = 512;
  27. const OP_ASSIGN = 1024;
  28. const OP_NULL = 2048;
  29. const OP_SQ_BRACKET = 4096;
  30. const OP_SQ_BRACKET_E = 8192;
  31. const OP_FUNCTION = 16384;
  32. const OP_METHOD = 32768;
  33. const OP_BRACKET = 65536;
  34. const OP_CLASS = 131072;
  35. const OP_CALL = 262144;
  36. const OP_FIELD = 524288;
  37. const OP_EXPRESSION = 1048576;
  38. const OP_OBJMAN = 2097152;
  39. const OP_BRACKET_E = 4194304;
  40. const OP_TU = 8388608;
  41. const OP_CURLY_BRACKET = 16777216;
  42. const ESCAPE_ON = true;
  43. const ESCAPE_OFF = false;
  44. const ESCAPE_BOTH = 2;
  45. // Current compilation
  46. protected $_template = NULL;
  47. protected $_attr = array();
  48. protected $_stack = NULL;
  49. protected $_node = NULL;
  50. static protected $_recursionDetector = NULL;
  51. // Compiler info
  52. protected $_tags = array();
  53. protected $_attributes = array();
  54. protected $_conversions = array();
  55. protected $_processors = array();
  56. protected $_dependencies = array();
  57. // OPT parser info
  58. protected $_tpl;
  59. protected $_instructions;
  60. protected $_namespaces;
  61. protected $_functions;
  62. protected $_classes;
  63. protected $_blocks;
  64. protected $_components;
  65. protected $_tf;
  66. protected $_entities;
  67. protected $_formnatInfo;
  68. protected $_formats = array();
  69. protected $_formatObj = array();
  70. protected $_inheritance;
  71. // Regular expressions
  72. private $_rCDataExpression = '/(\<\!\[CDATA\[|\]\]\>)/msi';
  73. private $_rCommentExpression = '/(\<\!\-\-|\-\-\>)/si';
  74. private $_rCommentSplitExpression = '/(\<\!\-\-(.*?)\-\-\>)/si';
  75. private $_rOpeningChar = '[a-zA-Z\:\_]';
  76. private $_rNameChar = '[a-zA-Z0-9\:\.\_\-]';
  77. private $_rNameExpression;
  78. private $_rXmlTagExpression;
  79. private $_rTagExpandExpression;
  80. private $_rQuirksTagExpression = '';
  81. private $_rExpressionTag = '/(\{([^\}]*)\})/msi';
  82. private $_rAttributeTokens = '/(?:[^\=\"\'\s]+|\=|\"|\'|\s)/x';
  83. private $_rPrologTokens = '/(?:[^\=\"\'\s]+|\=|\'|\"|\s)/x';
  84. private $_rModifiers = 'si';
  85. private $_rXmlHeader = '/(\<\?xml.+\?\>)/msi';
  86. private $_rProlog = '/\<\?xml(.+)\?\>|/msi';
  87. private $_rEncodingName = '/[A-Za-z]([A-Za-z0-9.\_]|\-)*/si';
  88. private $_rBacktickString = '`[^`\\\\]*(?:\\\\.[^`\\\\]*)*`';
  89. private $_rSingleQuoteString = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
  90. private $_rHexadecimalNumber = '\-?0[xX][0-9a-fA-F]+';
  91. private $_rDecimalNumber = '[0-9]+\.?[0-9]*';
  92. private $_rLanguageVar = '\$[a-zA-Z0-9\_]+@[a-zA-Z0-9\_]+';
  93. private $_rVariable = '(\$|@)[a-zA-Z0-9\_\.]*';
  94. private $_rOperators = '\-\>|!==|===|==|!=|\=\>|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+\+|\-\-|\+|\-|\*|\/|\[|\]|\.|\:\:|\{|\}|\'|\"|';
  95. private $_rIdentifier = '[a-zA-Z\_]{1}[a-zA-Z0-9\_\.]*';
  96. private $_rLanguageVarExtract = '\$([a-zA-Z0-9\_]+)@([a-zA-Z0-9\_]+)';
  97. // Help fields
  98. private $_charset = null;
  99. private $_translationConversion = null;
  100. private $_initialMemory = null;
  101. private $_comments = 0;
  102. private $_standalone = false;
  103. private $_dynamicBlocks = null;
  104. static private $_templates = array();
  105. /**
  106. * Creates a new instance of the template compiler. The compiler can
  107. * be created, using the settings from the main OPT class or another
  108. * compiler.
  109. *
  110. * @param Opt_Class|Opt_Compiler_Class $tpl The initial object.
  111. */
  112. public function __construct($tpl)
  113. {
  114. if($tpl instanceof Opt_Class)
  115. {
  116. $this->_tpl = $tpl;
  117. $this->_namespaces = $tpl->_getList('_namespaces');
  118. $this->_classes = $tpl->_getList('_classes');
  119. $this->_functions = $tpl->_getList('_functions');
  120. $this->_components = $tpl->_getList('_components');
  121. $this->_blocks = $tpl->_getList('_blocks');
  122. $this->_phpFunctions = $tpl->_getList('_phpFunctions');
  123. $this->_formats = $tpl->_getList('_formats');
  124. $this->_tf = $tpl->_getList('_tf');
  125. $this->_entities = $tpl->_getList('_entities');
  126. $this->_charset = strtoupper($tpl->charset);
  127. // Create the processors and call their configuration method in the constructors.
  128. $instructions = $tpl->_getList('_instructions');
  129. foreach($instructions as $instructionClass)
  130. {
  131. $obj = new $instructionClass($this, $tpl);
  132. $this->_processors[$obj->getName()] = $obj;
  133. // Add the tags and attributes registered by this processor.
  134. foreach($obj->getInstructions() as $item)
  135. {
  136. $this->_instructions[$item] = $obj;
  137. }
  138. foreach($obj->getAttributes() as $item)
  139. {
  140. $this->_attributes[$item] = $obj;
  141. }
  142. }
  143. }
  144. elseif($tpl instanceof Opt_Compiler_Class)
  145. {
  146. // Simply import the data structures from that compiler.
  147. $this->_tpl = $tpl->_tpl;
  148. $this->_namespaces = $tpl->_namespaces;
  149. $this->_classes = $tpl->_classes;
  150. $this->_functions = $tpl->_functions;
  151. $this->_components = $tpl->_components;
  152. $this->_blocks = $tpl->_blocks;
  153. $this->_inheritance = $tpl->_inheritance;
  154. $this->_formatInfo = $tpl->_formatInfo;
  155. $this->_formats = $tpl->_formats;
  156. $this->_tf = $tpl->_tf;
  157. $this->_processor = $tpl->_processors;
  158. $this->_instructions = $tpl->_instructions;
  159. $this->_attributes = $tpl->_attributes;
  160. $this->_charset = $tpl->_charset;
  161. $this->_entities = $tpl->_entities;
  162. $tpl = $this->_tpl;
  163. }
  164. if($tpl->unicodeNames)
  165. {
  166. // Register unicode name regular expressions
  167. $this->_rOpeningChar = '(\p{Lu}|\p{Ll}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Nl}|\_|\:)';
  168. $this->_rNameChar = '(\p{Lu}|\p{Ll}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Nl}|\p{Mc}|\p{Me}|\p{Mn}|\p{Lm}|\p{Nd}|\_|\:|\.|\-)';
  169. $this->_rModifiers = 'msiu';
  170. }
  171. // Register the rest of the expressions
  172. $this->_rNameExpression = '/('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)/'.$this->_rModifiers;
  173. $this->_rXmlTagExpression = '/(\<((\/)?('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)( [^\<\>]*)?(\/)?)\>)/'.$this->_rModifiers;
  174. $this->_rTagExpandExpression = '/^(\/)?('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)( [^\<\>]*)?(\/)?$/'.$this->_rModifiers;
  175. $this->_rQuirksTagExpression = '/(\<((\/)?(('.implode('|', $this->_namespaces).')\:'.$this->_rNameChar.'*)( [^\<\>]+)?(\/)?)\>)/'.$this->_rModifiers;
  176. // We've just thrown the performance away by loading the compiler, so this won't make things worse
  177. // but the user may be happy :). However, don't show this message, if we are in the performance mode.
  178. if(!is_writable($tpl->compileDir) && $tpl->_compileMode != Opt_Class::CM_PERFORMANCE)
  179. {
  180. throw new Opt_FilesystemAccess_Exception('compilation', 'writeable');
  181. }
  182. // If the debug console is active, preload the XML tree classes.
  183. // Without it, the debug console would show crazy things about the memory usage.
  184. if($this->_tpl->debugConsole && !class_exists('Opt_Xml_Root'))
  185. {
  186. Opl_Loader::load('Opt_Xml_Root');
  187. Opl_Loader::load('Opt_Xml_Text');
  188. Opl_Loader::load('Opt_Xml_Cdata');
  189. Opl_Loader::load('Opt_Xml_Element');
  190. Opl_Loader::load('Opt_Xml_Attribute');
  191. Opl_Loader::load('Opt_Xml_Expression');
  192. Opl_Loader::load('Opt_Xml_Prolog');
  193. Opl_Loader::load('Opt_Xml_Dtd');
  194. Opl_Loader::load('Opt_Format_Array');
  195. }
  196. } // end __construct();
  197. /**
  198. * Allows to clone the original compiler, creating new instruction
  199. * processors for the new instance.
  200. */
  201. public function __clone()
  202. {
  203. $this->_processors = array();
  204. $this->_tags = array();
  205. $this->_attributes = array();
  206. $this->_conversions = array();
  207. $instructions = $this->_tpl->_getList('_instructions');
  208. $cnt = sizeof($instructions);
  209. for($i = 0; $i < $cnt; $i++)
  210. {
  211. $obj = new $instructions[$i]($this, $tpl);
  212. $this->_processors[$obj->getName()] = $obj;
  213. }
  214. } // end __clone();
  215. /*
  216. * General purpose tools and utilities
  217. */
  218. /**
  219. * Returns the currently processed template file name.
  220. *
  221. * @static
  222. * @return String The currently processed template name
  223. */
  224. static public function getCurrentTemplate()
  225. {
  226. return end(self::$_templates);
  227. } // end getCurrentTemplate();
  228. /**
  229. * Cleans the compiler state after the template compilation.
  230. * It is necessary in the exception processing - if the exception
  231. * is thrown in the middle of the compilation, the compiler becomes
  232. * useless, because it is locked. The compilation algorithm automatically
  233. * filters the exceptions, cleans the compiler state and throws the captured
  234. * exceptions again, to the script.
  235. *
  236. * @static
  237. */
  238. static public function cleanCompiler()
  239. {
  240. self::$_recursionDetector = null;
  241. self::$_templates = array();
  242. } // end cleanCompiler();
  243. /**
  244. * Returns the value of the compiler state variable or
  245. * NULL if the variable is not set.
  246. *
  247. * @param String $name Compiler variable name
  248. * @return Mixed The compiler variable value.
  249. */
  250. public function get($name)
  251. {
  252. if(!isset($this->_attr[$name]))
  253. {
  254. return NULL;
  255. }
  256. return $this->_attr[$name];
  257. } // end get();
  258. /**
  259. * Creates or modifies the compiler state variable.
  260. *
  261. * @param String $name The name
  262. * @param Mixed $value The value
  263. */
  264. public function set($name, $value)
  265. {
  266. $this->_attr[$name] = $value;
  267. } // end set();
  268. /**
  269. * Adds the escaping formula to the specified expression using the current escaping
  270. * rules:
  271. *
  272. * 1. The $status variable.
  273. * 2. The current template settings.
  274. * 3. The OPT settings.
  275. *
  276. * @param String $expression The PHP expression to be escaped.
  277. * @param Boolean $status The status of escaping for this expression or NULL, if not set.
  278. * @return String The expression with the escaping formula added, if necessary.
  279. */
  280. public function escape($expression, $status = null)
  281. {
  282. // OPT Configuration
  283. $escape = $this->_tpl->escape;
  284. // Template configuration
  285. if(!is_null($this->get('escaping')))
  286. {
  287. $escape = ($this->get('escaping') == true ? true : false);
  288. }
  289. // Expression settings
  290. if(!is_null($status))
  291. {
  292. $escape = ($status == true ? true : false);
  293. }
  294. if($escape)
  295. {
  296. // The user may define a custom escaping function
  297. if($this->isFunction('escape'))
  298. {
  299. if(strpos($this->_functions['escape'], '#', 0) !== false)
  300. {
  301. throw new Opt_InvalidArgumentFormat_Exception('escape', 'escape');
  302. }
  303. return $this->_functions['escape'].'('.$expression.')';
  304. }
  305. return 'htmlspecialchars('.$expression.')';
  306. }
  307. return $expression;
  308. } // end escape();
  309. /**
  310. * Returns the format object for the specified variable.
  311. *
  312. * @param String $variable The variable identifier.
  313. * @param Boolean $restore optional Whether to load a previously created format object (false) or to create a new one.
  314. * @return Opt_Compiler_Format The format object.
  315. */
  316. public function getFormat($variable, $restore = false)
  317. {
  318. $hc = $this->_tpl->defaultFormat;
  319. if(isset($this->_formatInfo[$variable]))
  320. {
  321. $hc = $this->_formatInfo[$variable];
  322. }
  323. if($restore && isset($this->_formatObj[$hc]))
  324. {
  325. return $this->_formatObj[$hc];
  326. }
  327. $top = $this->createFormat($variable, $hc);
  328. if($restore)
  329. {
  330. $this->_formatObj[$hc] = $top;
  331. }
  332. return $top;
  333. } // end getFormat();
  334. /**
  335. * Creates a format object for the specified description string.
  336. *
  337. * @param String $variable The variable name (for debug purposes)
  338. * @param String $hc The description string.
  339. * @return Opt_Compiler_Format The newly created format object.
  340. */
  341. public function createFormat($variable, $hc)
  342. {
  343. // Decorate the objects, if necessary
  344. $expanded = explode('/', $hc);
  345. $obj = null;
  346. foreach($expanded as $class)
  347. {
  348. if(!isset($this->_formats[$class]))
  349. {
  350. throw new Opt_FormatNotFound_Exception($variable, $class);
  351. }
  352. $hcName = $this->_formats[$class];
  353. if(!is_null($obj))
  354. {
  355. $obj->decorate($obj2 = new $hcName($this->_tpl, $this));
  356. $obj = $obj2;
  357. }
  358. else
  359. {
  360. $top = $obj = new $hcName($this->_tpl, $this, $hc);
  361. }
  362. }
  363. return $top;
  364. } // end createFormat();
  365. /**
  366. * Allows to export the list of variables and their data formats to
  367. * the template compiler.
  368. *
  369. * @param Array $list An associative array of pairs "variable => format description"
  370. */
  371. public function setFormatList(Array $list)
  372. {
  373. $this->_formatInfo = $list;
  374. } // end setFormatList();
  375. /**
  376. * Converts the specified item into another string using one of the
  377. * registered patterns. If the pattern is not found, the method returns
  378. * the original item unmodified.
  379. *
  380. * @param String $item The item to be converted.
  381. * @return String
  382. */
  383. public function convert($item)
  384. {
  385. // the converter allows to convert one name into another and keep it, if there is no
  386. // conversion pattern. Used in connection with sections + snippets.
  387. if(isset($this->_conversions[$item]))
  388. {
  389. return $this->_conversions[$item];
  390. }
  391. return $item;
  392. } // end convert();
  393. /**
  394. * Creates a new conversion pattern. The string $from will be converted
  395. * into $to.
  396. *
  397. * @param String $from The original string
  398. * @param String $to The new string
  399. */
  400. public function setConversion($from, $to)
  401. {
  402. $this->_conversions[$from] = $to;
  403. } // end setConversion();
  404. /**
  405. * Removes the conversion pattern from the compiler memory.
  406. *
  407. * @param String $from The original string.
  408. * @return Boolean
  409. */
  410. public function unsetConversion($from)
  411. {
  412. if(isset($this->_conversions[$from]))
  413. {
  414. unset($this->_conversions[$from]);
  415. return true;
  416. }
  417. return false;
  418. } // end unsetConversion();
  419. /**
  420. * Registers the dynamic inheritance rules for the templates. The
  421. * array taken as a parameter must be an associative array of pairs
  422. * 'extending' => 'extended' file names.
  423. *
  424. * @param Array $inheritance The list of inheritance rules.
  425. */
  426. public function setInheritance(Array $inheritance)
  427. {
  428. $this->_inheritance = $inheritance;
  429. } // end setInheritance();
  430. /**
  431. * Parses the entities in the specified text.
  432. *
  433. * @param String $text The original text
  434. * @return String
  435. */
  436. public function parseEntities($text)
  437. {
  438. return preg_replace_callback('/\&(([a-zA-Z\_\:]{1}[a-zA-Z0-9\_\:\-\.]*)|(\#((x[a-fA-F0-9]+)|([0-9]+))))\;/', array($this, '_decodeEntity'), $text);
  439. // return htmlspecialchars_decode(str_replace(array_keys($this->_entities), array_values($this->_entities), $text));
  440. } // end parseEntities();
  441. /**
  442. * Replaces only OPT-specific entities &lb; and &rb; to the corresponding
  443. * characters.
  444. *
  445. * @param String $text Input text
  446. * @return String output text
  447. */
  448. public function parseShortEntities($text)
  449. {
  450. return str_replace(array('&lb;', '&rb;'), array('{', '}'), $text);
  451. } // end parseShortEntities();
  452. /**
  453. * Replaces the XML special characters back to entities with smart ommiting of &
  454. * that already creates an entity.
  455. *
  456. * @param String $text Input text.
  457. * @return String Output text.
  458. */
  459. public function parseSpecialChars($text)
  460. {
  461. return htmlspecialchars($text);
  462. return preg_replace_callback('/(\&\#?[a-zA-Z0-9]*\;)|\<|\>|\"|\&/', array($this, '_entitize'), $text);
  463. } // end parseSpecialChars();
  464. /**
  465. * Returns 'true', if the argument is a valid identifier. An identifier
  466. * must begin with a letter or underscore, and later, the numbers are also
  467. * allowed.
  468. *
  469. * @param String $id The tested string
  470. * @return Boolean
  471. */
  472. public function isIdentifier($id)
  473. {
  474. return preg_match($this->_rEncodingName, $id);
  475. } // end isIdentifier();
  476. /**
  477. * Checks whether the specified tag name is registered as an instruction.
  478. * Returns its processor in case of success or NULL.
  479. *
  480. * @param String $tag The tag name (with the namespace)
  481. * @return Opt_Compiler_Processor|NULL The processor that registered this tag.
  482. */
  483. public function isInstruction($tag)
  484. {
  485. if(isset($this->_instructions[$tag]))
  486. {
  487. return $this->_instructions[$tag];
  488. }
  489. return NULL;
  490. } // end isInstruction();
  491. /**
  492. * Returns true, if the argument is the name of an OPT attribute.
  493. *
  494. * @param String $tag The attribute name
  495. * @return Boolean
  496. */
  497. public function isOptAttribute($tag)
  498. {
  499. if(isset($this->_attributes[$tag]))
  500. {
  501. return $this->_attributes[$tag];
  502. }
  503. return NULL;
  504. } // end isOptAttribute();
  505. /**
  506. * Returns true, if the argument is the OPT function name.
  507. *
  508. * @param String $name The function name
  509. * @return Boolean
  510. */
  511. public function isFunction($name)
  512. {
  513. if(isset($this->_functions[$name]))
  514. {
  515. return $this->_functions[$name];
  516. }
  517. return NULL;
  518. } // end isFunction();
  519. /**
  520. * Returns true, if the argument is the name of the class
  521. * accepted by OPT.
  522. *
  523. * @param String $id The class name.
  524. * @return Boolean
  525. */
  526. public function isClass($id)
  527. {
  528. if(isset($this->_classes[$id]))
  529. {
  530. return $this->_classes[$id];
  531. }
  532. return NULL;
  533. } // end isClass();
  534. /**
  535. * Returns true, if the argument is the name of the namespace
  536. * processed by OPT.
  537. *
  538. * @param String $ns The namespace name
  539. * @return Boolean
  540. */
  541. public function isNamespace($ns)
  542. {
  543. return in_array($ns, $this->_namespaces);
  544. } // end isNamespace();
  545. /**
  546. * Returns true, if the argument is the name of the component tag.
  547. * @param String $component The component tag name
  548. * @return Boolean
  549. */
  550. public function isComponent($component)
  551. {
  552. return isset($this->_components[$component]);
  553. } // end isComponent();
  554. /**
  555. * Returns true, if the argument is the name of the block tag.
  556. * @param String $block The block tag name.
  557. * @return Boolean
  558. */
  559. public function isBlock($block)
  560. {
  561. return isset($this->_blocks[$block]);
  562. } // end isComponent();
  563. /**
  564. * Returns true, if the argument is the processor name.
  565. *
  566. * @param String $name The instruction processor name
  567. * @return Boolean
  568. */
  569. public function isProcessor($name)
  570. {
  571. if(!isset($this->_processors[$name]))
  572. {
  573. return NULL;
  574. }
  575. return $this->_processors[$name];
  576. } // end isProcessor();
  577. /**
  578. * Returns the processor object with the specified name. If
  579. * the processor does not exist, it generates an exception.
  580. *
  581. * @param String $name The processor name
  582. * @return Opt_Compiler_Processor
  583. */
  584. public function processor($name)
  585. {
  586. if(!isset($this->_processors[$name]))
  587. {
  588. throw new Opt_ObjectNotExists_Exception('processor', $name);
  589. }
  590. return $this->_processors[$name];
  591. } // end processor();
  592. /**
  593. * Returns the component class name assigned to the specified
  594. * XML tag. If the component class is not registered, it throws
  595. * an exception.
  596. *
  597. * @param String $name The component XML tag name.
  598. * @return Opt_Component_Interface
  599. */
  600. public function component($name)
  601. {
  602. if(!isset($this->_components[$name]))
  603. {
  604. throw new Opt_ObjectNotExists_Exception('component', $name);
  605. }
  606. return $this->_components[$name];
  607. } // end component();
  608. /**
  609. * Returns the block class name assigned to the specified
  610. * XML tag. If the block class is not registered, it throws
  611. * an exception.
  612. *
  613. * @param String $name The block XML tag name.
  614. * @return Opt_Block_Interface
  615. */
  616. public function block($name)
  617. {
  618. if(!isset($this->_blocks[$name]))
  619. {
  620. throw new Opt_ObjectNotExists_Exception('block', $name);
  621. }
  622. return $this->_blocks[$name];
  623. } // end block();
  624. /**
  625. * Returns the template name that is inherited by the template '$name'
  626. *
  627. * @param String $name The "current" template file name
  628. * @return String
  629. */
  630. public function inherits($name)
  631. {
  632. if(isset($this->_inheritance[$name]))
  633. {
  634. return $this->_inheritance[$name];
  635. }
  636. return NULL;
  637. } // end inherits();
  638. /**
  639. * Adds the template file name to the dependency list of the currently
  640. * compiled file, so that it could be checked for modifications during
  641. * the execution.
  642. *
  643. * @param String $template The template file name.
  644. */
  645. public function addDependantTemplate($template)
  646. {
  647. if(in_array($template, $this->_dependencies))
  648. {
  649. $exception = new Opt_InheritanceRecursion_Exception($template);
  650. $exception->setData($this->_dependencies);
  651. throw $exception;
  652. }
  653. $this->_dependencies[] = $template;
  654. } // end addDependantTemplate();
  655. /**
  656. * Imports the dependencies from another compiler object and adds them
  657. * to the actual dependency list.
  658. *
  659. * @param Opt_Compiler_Class $compiler Another compiler object.
  660. */
  661. public function importDependencies(Opt_Compiler_Class $compiler)
  662. {
  663. $this->_dependencies = array_merge($this->_dependencies, $compiler->_dependencies);
  664. } // end importDependencies();
  665. /*
  666. * Internal tools and utilities
  667. */
  668. /**
  669. * Compiles the attribute part of the opening tag and extracts the tag
  670. * attributes to an array. Moreover, it performs the entity conversion
  671. * to the corresponding characters.
  672. *
  673. * @internal
  674. * @param String $attrList The attribute list string
  675. * @param string $tagName The tag name for debug purposes
  676. * @return Array The list of attributes with the values.
  677. */
  678. protected function _compileAttributes($attrList, $tagName = '')
  679. {
  680. // Tokenize the list
  681. preg_match_all($this->_rAttributeTokens, $attrList, $match, PREG_SET_ORDER);
  682. $size = sizeof($match);
  683. $result = array();
  684. for($i = 0; $i < $size; $i++)
  685. {
  686. /**
  687. * The algorithm scans the tokens on the list and determines, where
  688. * the beginning and the end of the attribute is. We do not use the
  689. * regular expressions, because they are not able to capture the
  690. * invalid content between the expressions.
  691. *
  692. * The sub-loops can modify the iteration variable to skip the found
  693. * elements, white characters etc. This means that the main loop
  694. * does only a few iteration number, equal approximately the number
  695. * of attributes.
  696. */
  697. if(!ctype_space($match[$i][0]))
  698. {
  699. if(!preg_match($this->_rNameExpression, $match[$i][0]))
  700. {
  701. return false;
  702. }
  703. $vret = false;
  704. $name = $match[$i][0];
  705. if(substr_count($name, ':') > 1)
  706. {
  707. throw new Opt_InvalidNamespace_Exception($name);
  708. }
  709. $value = null;
  710. for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
  711. if($match[$i][0] != '=')
  712. {
  713. if($this->_tpl->htmlAttributes)
  714. {
  715. $result[$name] = $name;
  716. continue;
  717. }
  718. else
  719. {
  720. return false;
  721. }
  722. }
  723. // Look for the attribute value start
  724. for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
  725. if($match[$i][0] != '"' && $match[$i][0] != '\'')
  726. {
  727. return false;
  728. }
  729. // Save the delimiter, because we will use it to make the error checking
  730. $delimiter = $match[$i][0];
  731. $value = '';
  732. for($i++; $i < $size; $i++)
  733. {
  734. if($match[$i][0] == $delimiter)
  735. {
  736. break;
  737. }
  738. $value .= $match[$i][0];
  739. }
  740. if(!isset($match[$i][0]))
  741. {
  742. return false;
  743. }
  744. if($match[$i][0] != $delimiter)
  745. {
  746. return false;
  747. }
  748. // We return the decoded attribute values, because they are
  749. // stored without the entities.
  750. if(isset($result[$name]))
  751. {
  752. throw new Opt_XmlDuplicatedAttribute_Exception($name, $tagName);
  753. }
  754. $result[$name] = htmlspecialchars_decode($value);
  755. }
  756. }
  757. return $result;
  758. } // end _compileAttributes();
  759. /**
  760. * Parses the XML prolog and returns its attributes as an array. The parsing
  761. * algorith is the same, as in _compileAttributes().
  762. *
  763. * @internal
  764. * @param String $prolog The prolog string.
  765. * @return Array
  766. */
  767. protected function _compileProlog($prolog)
  768. {
  769. // Tokenize the list
  770. preg_match_all($this->_rPrologTokens, $prolog, $match, PREG_SET_ORDER);
  771. $size = sizeof($match);
  772. $result = array();
  773. for($i = 0; $i < $size; $i++)
  774. {
  775. if(!ctype_space($match[$i][0]))
  776. {
  777. // Traverse through a single attribute
  778. if(!preg_match($this->_rNameExpression, $match[$i][0]))
  779. {
  780. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  781. }
  782. $vret = false;
  783. $name = $match[$i][0];
  784. $value = null;
  785. for($i++; $i < $size && ctype_space($match[$i][0]); $i++){}
  786. if($i >= $size || $match[$i][0] != '=')
  787. {
  788. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  789. }
  790. for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
  791. if($match[$i][0] != '"' && $match[$i][0] != '\'')
  792. {
  793. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  794. }
  795. $opening = $match[$i][0];
  796. $value = '';
  797. for($i++; $i < $size; $i++)
  798. {
  799. if($match[$i][0] == $opening)
  800. {
  801. break;
  802. }
  803. $value .= $match[$i][0];
  804. }
  805. if(!isset($match[$i][0]) || $match[$i][0] != $opening)
  806. {
  807. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  808. }
  809. // If we are here, the attribute is correct. No shit on the way detected.
  810. $result[$name] = $value;
  811. }
  812. }
  813. $returnedResult = $result;
  814. // Check, whether the arguments are correct.
  815. if(isset($result['version']))
  816. {
  817. // There is no other version so far, so report a warning. For 99,9% this is a mistake.
  818. if($result['version'] != '1.0')
  819. {
  820. $this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: strange XML version: '.$result['version']);
  821. }
  822. unset($result['version']);
  823. }
  824. if(isset($result['encoding']))
  825. {
  826. if(!preg_match($this->_rEncodingName, $result['encoding']))
  827. {
  828. throw new Opt_XmlInvalidProlog_Exception('invalid encoding name format');
  829. }
  830. // The encoding should match the value we mentioned in the OPT configuration and sent to the browser.
  831. $result['encoding'] = strtolower($result['encoding']);
  832. $charset = is_null($this->_tpl->charset) ? null : strtolower($this->_tpl->charset);
  833. if($result['encoding'] != $charset && !is_null($charset))
  834. {
  835. $this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: the declared encoding: "'.$result['encoding'].'" differs from setContentType() setting: "'.$charset.'"');
  836. }
  837. unset($result['encoding']);
  838. }
  839. else
  840. {
  841. $this->_tpl->debugConsole and Opt_Support::warning('XML prolog warning: no encoding information. Remember your content must be pure UTF-8 or UTF-16 then.');
  842. }
  843. if(isset($result['standalone']))
  844. {
  845. if($result['standalone'] != 'yes' && $result['standalone'] != 'no')
  846. {
  847. throw new Opt_XmlInvalidProlog_Exception('invalid value for "standalone" attribute: "'.$result['standalone'].'"; expected: "yes", "no".');
  848. }
  849. unset($result['standalone']);
  850. }
  851. if(sizeof($result) > 0)
  852. {
  853. throw new Opt_XmlInvalidProlog_Exception('invalid attributes in prolog.');
  854. }
  855. return $returnedResult;
  856. } // end _compileProlog();
  857. /**
  858. * Adds the PHP code with dependencies to the code buffers in the tree
  859. * root node.
  860. *
  861. * @internal
  862. * @param Opt_Xml_Node $tree The tree root node.
  863. */
  864. protected function _addDependencies($tree)
  865. {
  866. // OK, there is really some info to include!
  867. $list = '';
  868. foreach($this->_dependencies as $a)
  869. {
  870. $list .= '\''.$a.'\',';
  871. }
  872. $tree->addBefore(Opt_Xml_Buffer::TAG_BEFORE, 'if(!$this->_massPreprocess($compileTime, array('.$list.'))){ ');
  873. $tree->addAfter(Opt_Xml_Buffer::TAG_AFTER, ' }else{ $compileTime = $this->_compile($this->_template); require(__FILE__); } ');
  874. } // end _addDependencies();
  875. /**
  876. * Compiles the current text block between two XML tags, creating a
  877. * complete Opt_Xml_Text node. It looks for the expressions in the
  878. * curly brackets, extracts them and packs as separate nodes.
  879. *
  880. * Moreover, it replaces the entities with the corresponding characters.
  881. *
  882. * @internal
  883. * @param Opt_Xml_Node $current The current XML node.
  884. * @param String $text The text block between two tags.
  885. * @param Boolean $noExpressions=false If true, do not look for the expressions.
  886. * @return Opt_Xml_Node The current XML node.
  887. */
  888. protected function _treeTextCompile($current, $text, $noExpressions = false)
  889. {
  890. // Yes, we parse entities, but the text itself should not contain
  891. // any special characters.
  892. if(strcspn($text, '<>') != strlen($text))
  893. {
  894. throw new Opt_XmlInvalidCharacter_Exception(htmlspecialchars($text));
  895. }
  896. if($noExpressions)
  897. {
  898. $current = $this->_treeTextAppend($current, $this->parseEntities($text));
  899. }
  900. preg_match_all($this->_rExpressionTag, $text, $result, PREG_SET_ORDER);
  901. $resultSize = sizeof($result);
  902. $offset = 0;
  903. for($i = 0; $i < $resultSize; $i++)
  904. {
  905. $id = strpos($text, $result[$i][0], $offset);
  906. if($id > $offset)
  907. {
  908. $current = $this->_treeTextAppend($current, $this->parseEntities(substr($text, $offset, $id - $offset)));
  909. }
  910. $offset = $id + strlen($result[$i][0]);
  911. $current = $this->_treeTextAppend($current, new Opt_Xml_Expression($this->parseEntities($result[$i][2])));
  912. }
  913. $i--;
  914. // Now the remaining content of the file
  915. if(strlen($text) > $offset)
  916. {
  917. $current = $this->_treeTextAppend($current, $this->parseEntities(substr($text, $offset, strlen($text) - $offset)));
  918. }
  919. return $current;
  920. } // end _treeTextCompile();
  921. /**
  922. * An utility method that simplifies inserting the text to the XML
  923. * tree. Depending on the last child type, it can create a new text
  924. * node or add the text to the existing one.
  925. *
  926. * @internal
  927. * @param Opt_Xml_Node $current The currently built XML node.
  928. * @param String|Opt_Xml_Node $text The text or the expression node.
  929. * @return Opt_Xml_Node The current XML node.
  930. */
  931. protected function _treeTextAppend($current, $text)
  932. {
  933. $last = $current->getLastChild();
  934. if(!is_object($last) || !($last instanceof Opt_Xml_Text))
  935. {
  936. if(!is_object($text))
  937. {
  938. $node = new Opt_Xml_Text($text);
  939. }
  940. else
  941. {
  942. $node = new Opt_Xml_Text();
  943. $node->appendChild($text);
  944. }
  945. $current->appendChild($node);
  946. }
  947. else
  948. {
  949. if(!is_object($text))
  950. {
  951. $last->appendData($text);
  952. }
  953. else
  954. {
  955. $last->appendChild($text);
  956. }
  957. }
  958. return $current;
  959. } // end _treeTextAppend();
  960. /**
  961. * A helper method for building the XML tree. It appends the
  962. * node to the current node and returns the new node that should
  963. * become the new current node.
  964. *
  965. * @internal
  966. * @param Opt_Xml_Node $current The current node.
  967. * @param Opt_Xml_Node $node The newly created node.
  968. * @param Boolean $goInto Whether we visit the new node.
  969. * @return Opt_Xml_Node
  970. */
  971. protected function _treeNodeAppend($current, $node, $goInto)
  972. {
  973. $current->appendChild($node);
  974. if($goInto)
  975. {
  976. return $node;
  977. }
  978. return $current;
  979. } // end _treeNodeAppend();
  980. /**
  981. * A helper method for building the XML tree. It jumps out of the
  982. * current node to the parent and switches to it.
  983. *
  984. * @internal
  985. * @param Opt_Xml_Node $current The current node.
  986. * @return Opt_Xml_Node
  987. */
  988. protected function _treeJumpOut($current)
  989. {
  990. $parent = $current->getParent();
  991. if(!is_null($parent))
  992. {
  993. return $parent;
  994. }
  995. return $current;
  996. } // end _treeJumpOut();
  997. /**
  998. * Looks for special OPT attributes in the element attribute list and
  999. * processes them. Returns the list of nodes that need to be postprocessed.
  1000. *
  1001. * @internal
  1002. * @param Opt_Xml_Element $node The scanned element.
  1003. * @param Boolean $specialNs Do we recognize "parse" and "str" namespaces?
  1004. * @return Array
  1005. */
  1006. protected function _processXml(Opt_Xml_Element $node, $specialNs = true)
  1007. {
  1008. if(!$node->hasAttributes())
  1009. {
  1010. return array();
  1011. }
  1012. $attributes = $node->getAttributes();
  1013. $pp = array();
  1014. // Look for special OPT attributes
  1015. foreach($attributes as $attr)
  1016. {
  1017. if($this->isNamespace($attr->getNamespace()))
  1018. {
  1019. $xml = $attr->getXmlName();
  1020. // Check the namespace we found
  1021. switch($attr->getNamespace())
  1022. {
  1023. case 'parse':
  1024. if($specialNs)
  1025. {
  1026. $result = $this->compileExpression((string)$attr, false, Opt_Compiler_Class::ESCAPE_BOTH);
  1027. $attr->addAfter(Opt_Xml_Buffer::ATTRIBUTE_VALUE, ' echo '.$result[0].'; ');
  1028. $attr->setNamespace(null);
  1029. }
  1030. break;
  1031. case 'str':
  1032. if($specialNs)
  1033. {
  1034. $attr->setNamespace(null);
  1035. }
  1036. break;
  1037. default:
  1038. if(isset($this->_attributes[$xml]))
  1039. {
  1040. $this->_attributes[$xml]->processAttribute($node, $attr);
  1041. if($attr->get('postprocess'))
  1042. {
  1043. $pp[] = array($this->_attributes[$xml], $attr);
  1044. }
  1045. }
  1046. $node->removeAttribute($xml);
  1047. }
  1048. }
  1049. }
  1050. return $pp;
  1051. } // end _processXml();
  1052. /**
  1053. * Runs the postprocessors for the specified attributes.
  1054. *
  1055. * @internal
  1056. * @param Opt_Xml_Node $node The scanned node.
  1057. * @param Array $list The list of XML attribute processors that need to be postprocessed.
  1058. */
  1059. protected function _postprocessXml(Opt_Xml_Node $node, Array $list)
  1060. {
  1061. $cnt = sizeof($list);
  1062. for($i = 0; $i < $cnt; $i++)
  1063. {
  1064. $list[$i][0]->postprocessAttribute($node, $list[$i][1]);
  1065. }
  1066. } // end _postprocessXml();
  1067. /**
  1068. * An utility method for the stage 2 and 3 of the compilation. It is
  1069. * used to create a non-recursive depth-first search algorithm. The
  1070. * current queue is sent to a stack, and the new queue if initialized,
  1071. * if $item contains children.
  1072. *
  1073. * @internal
  1074. * @param SplStack $stack The processing stack.
  1075. * @param SplQueue $queue The processing queue.
  1076. * @param Opt_Xml_Scannable $item The item, where to import the nodes from.
  1077. * @param Boolean $pp The postprocess flag.
  1078. * @return SplQueue The new queue (or the old one, if none has been created).
  1079. */
  1080. protected function _pushQueue($stack, $queue, $item, $pp)
  1081. {
  1082. if($item->hasChildren())
  1083. {
  1084. $stack->push(array($item, $queue, $pp));
  1085. $pp = NULL;
  1086. $queue = new SplQueue;
  1087. foreach($item as $child)
  1088. {
  1089. $queue->enqueue($child);
  1090. }
  1091. }
  1092. return $queue;
  1093. } // end _pushQueue();
  1094. /**
  1095. * Does the postprocessing in the second stage of compilation.
  1096. *
  1097. * @internal
  1098. * @param Opt_Xml_Node|Null $item The postprocessed node.
  1099. * @param Array $pp The list of postprocessed attributes.
  1100. */
  1101. protected function _doPostprocess($item, $pp)
  1102. {
  1103. // Postprocess code for the compilation stage 2
  1104. // Packed into a method, because it is used twice.
  1105. if(is_null($item))
  1106. {
  1107. return;
  1108. }
  1109. if(sizeof($pp) > 0)
  1110. {
  1111. $this->_postprocessXml($item, $pp);
  1112. }
  1113. if($item->get('postprocess'))
  1114. {
  1115. if(!is_null($processor = $this->isInstruction($item->getXmlName())))
  1116. {
  1117. $processor->postprocessNode($item);
  1118. }
  1119. elseif($this->isComponent($item->getXmlName()))
  1120. {
  1121. $processor = $this->processor('component');
  1122. $processor->postprocessComponent($item);
  1123. }
  1124. elseif($this->isBlock($item->getXmlName()))
  1125. {
  1126. $processor = $this->processor('block');
  1127. $processor->postprocessBlock($item);
  1128. }
  1129. else
  1130. {
  1131. throw new Opt_UnknownProcessor_Exception($item->getXmlName());
  1132. }
  1133. }
  1134. } // end _doPostprocess();
  1135. /**
  1136. * Does the post-linking for the third stage of the compilation and returns
  1137. * the linked code.
  1138. *
  1139. * @internal
  1140. * @param Opt_Xml_Node $item The linked item.
  1141. * @return String
  1142. */
  1143. protected function _doPostlinking($item)
  1144. {
  1145. // Post code
  1146. if(is_null($item))
  1147. {
  1148. return '';
  1149. }
  1150. // This prevents from displaying </> if the HTML node was hidden.
  1151. if($item->get('hidden') !== false)
  1152. {
  1153. return '';
  1154. }
  1155. if($item->get('_skip_postlinking') == true)
  1156. {
  1157. return '';
  1158. }
  1159. $output = '';
  1160. switch($item->getType())
  1161. {
  1162. case 'Opt_Xml_Text':
  1163. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
  1164. break;
  1165. case 'Opt_Xml_Element':
  1166. if($this->isNamespace($item->getNamespace()))
  1167. {
  1168. if($item->get('single'))
  1169. {
  1170. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_SINGLE_AFTER, Opt_Xml_Buffer::TAG_AFTER);
  1171. }
  1172. else
  1173. {
  1174. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_AFTER, Opt_Xml_Buffer::TAG_CLOSING_BEFORE,
  1175. Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
  1176. }
  1177. }
  1178. else
  1179. {
  1180. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_AFTER, Opt_Xml_Buffer::TAG_CLOSING_BEFORE).'</'.$item->get('_name').'>'.$item->buildCode(Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
  1181. $item->set('_name', NULL);
  1182. }
  1183. break;
  1184. case 'Opt_Xml_Root':
  1185. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
  1186. break;
  1187. }
  1188. $this->_closeComments($item, $output);
  1189. return $output;
  1190. } // end _doPostlinking();
  1191. /**
  1192. * Closes the XML comment for the commented item.
  1193. *
  1194. * @internal
  1195. * @param Opt_Xml_Node $item The commented item.
  1196. * @param String &$output The reference to the output buffer.
  1197. */
  1198. protected function _closeComments($item, &$output)
  1199. {
  1200. if($item->get('commented'))
  1201. {
  1202. $this->_comments--;
  1203. if($this->_comments == 0)
  1204. {
  1205. // According to the XML grammar, the construct "--->" is not allowed.
  1206. if(strlen($output) > 0 && $output[strlen($output)-1] == '-')
  1207. {
  1208. throw new Opt_XmlComment_Exception('--->');
  1209. }
  1210. $output .= '-->';
  1211. }
  1212. }
  1213. } // end _closeComments();
  1214. /**
  1215. * Links the element attributes into a valid XML code and returns
  1216. * the output code.
  1217. *
  1218. * @internal
  1219. * @param Opt_Xml_Element $subitem The XML element.
  1220. * @return String
  1221. */
  1222. protected function _linkAttributes($subitem)
  1223. {
  1224. // Links the attributes into the PHP code
  1225. if($subitem->hasAttributes() || $subitem->bufferSize(Opt_Xml_Buffer::TAG_BEGINNING_ATTRIBUTES) > 0 || $subitem->bufferSize(Opt_Xml_Buffer::TAG_ENDING_ATTRIBUTES) > 0)
  1226. {
  1227. $code = $subitem->buildCode(Opt_Xml_Buffer::TAG_ATTRIBUTES_BEFORE, Opt_Xml_Buffer::TAG_BEGINNING_ATTRIBUTES);
  1228. $attrList = $subitem->getAttributes();
  1229. // Link attributes into a string
  1230. foreach($attrList as $attribute)
  1231. {
  1232. $s = $attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_NAME);
  1233. switch($s)
  1234. {
  1235. case 0:
  1236. $code .= $attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_BEGIN).' '.$attribute->getXmlName();
  1237. break;
  1238. case 1:
  1239. $code .= ($attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_BEGIN) == 0 ? ' ' : '').$attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_BEGIN, ' ', Opt_Xml_Buffer::ATTRIBUTE_NAME);
  1240. break;
  1241. default:
  1242. throw new Opt_CompilerCodeBufferConflict_Exception(1, 'ATTRIBUTE_NAME', $subitem->getXmlName());
  1243. }
  1244. if($attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_VALUE) == 0)
  1245. {
  1246. // Static value
  1247. if(!($this->_tpl->htmlAttributes && $attribute->getValue() == $attribute->getName()))
  1248. {
  1249. $code .= '="'.htmlspecialchars($attribute->getValue()).'"';
  1250. }
  1251. }
  1252. else
  1253. {
  1254. $code .= '="'.$attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_VALUE).'"';
  1255. }
  1256. $code .= $attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_END);
  1257. }
  1258. return $code.$subitem->buildCode(Opt_Xml_Buffer::TAG_ENDING_ATTRIBUTES, Opt_Xml_Buffer::TAG_ATTRIBUTES_AFTER);
  1259. }
  1260. return '';
  1261. } // end _linkAttributes();
  1262. /*
  1263. * Main compilation methods
  1264. */
  1265. /**
  1266. * The compilation launcher. It executes the proper compilation steps
  1267. * according to the inheritance rules etc.
  1268. *
  1269. * @param String $code The source code to be compiled.
  1270. * @param String $filename The source template filename.
  1271. * @param String $compiledFilename The output template filename.
  1272. * @param Int $mode The compilation mode.
  1273. */
  1274. public function compile($code, $filename, $compiledFilename, $mode)
  1275. {
  1276. try
  1277. {
  1278. // We cannot compile two templates at the same time
  1279. if(!is_null($this->_template))
  1280. {
  1281. throw new Opt_CompilerLocked_Exception($filename, $this->_template);
  1282. }
  1283. // Detecting recursive inclusion
  1284. if(is_null(self::$_recursionDetector))
  1285. {
  1286. self::$_recursionDetector = array(0 => $filename);
  1287. $weFree = true;
  1288. }
  1289. else
  1290. {
  1291. if(in_array($filename, self::$_recursionDetector))
  1292. {
  1293. $exception = new Opt_CompilerRecursion_Exception($filename);
  1294. $exception->setData(self::$_recursionDetector);
  1295. throw $exception;
  1296. }
  1297. self::$_recursionDetector[] = $filename;
  1298. $weFree = false;
  1299. }
  1300. // Cleaning up the processors
  1301. foreach($this->_processors as $proc)
  1302. {
  1303. $proc->reset();
  1304. }
  1305. // Initializing the template launcher
  1306. $this->set('template', $this->_template = $filename);
  1307. $this->set('mode', $mode);
  1308. $this->set('currentTemplate', $this->_template);
  1309. array_push(self::$_templates, $filename);
  1310. $this->_stack = new SplStack;
  1311. $i = 0;
  1312. $extend = $filename;
  1313. $memory = 0;
  1314. // The inheritance loop
  1315. do
  1316. {
  1317. // Stage 1 - code compilation
  1318. if($this->_tpl->debugConsole)
  1319. {
  1320. $initial = memory_get_usage();
  1321. $tree = $this->_stage1($code, $extend, $mode);
  1322. // Stage 2 - PHP tree processing
  1323. $this->_stack = array();
  1324. $this->_stage2($tree);
  1325. $this->set('escape', NULL);
  1326. unset($this->_stack);
  1327. $memory += (memory_get_usage() - $initial);
  1328. unset($code);
  1329. }
  1330. else
  1331. {
  1332. $tree = $this->_stage1($code, $extend, $mode);
  1333. unset($code);
  1334. // Stage 2 - PHP tree processing
  1335. $this->_stack = array();
  1336. $this->_stage2($tree);
  1337. $this->set('escape', NULL);
  1338. unset($this->_stack);
  1339. }
  1340. // if the template extends something, load it and also process
  1341. if(isset($extend) && $extend != $filename)
  1342. {
  1343. $this->addDependantTemplate($extend);
  1344. }
  1345. if(!is_null($snippet = $tree->get('snippet')))
  1346. {
  1347. $tree->dispose();
  1348. unset($tree);
  1349. // Change the specified snippet into a root node.
  1350. $tree = new Opt_Xml_Root;
  1351. $attribute = new Opt_Xml_Attribute('opt:use', $snippet);
  1352. $this->processor('snippet')->processAttribute($tree, $attribute);
  1353. $this->processor('snippet')->postprocessAttribute($tree, $attribute);
  1354. $this->_stage2($tree, true);
  1355. break;
  1356. }
  1357. if(!is_null($extend = $tree->get('extend')))
  1358. {
  1359. $tree->dispose();
  1360. unset($tree);
  1361. $this->set('currentTemplate', $extend);
  1362. array_pop(self::$_templates);
  1363. array_push(self::$_templates, $extend);
  1364. $code = $this->_tpl->_getSource($extend);
  1365. }
  1366. $i++;
  1367. }
  1368. while(!is_null($extend));
  1369. // There are some dependant templates. We must add a suitable PHP code
  1370. // to the output.
  1371. if(sizeof($this->_dependencies) > 0)
  1372. {
  1373. $this->_addDependencies($tree);
  1374. }
  1375. if($this->_tpl->debugConsole)
  1376. {
  1377. Opt_Support::addCompiledTemplate($this->_template, $memory);
  1378. }
  1379. // Stage 3 - linking the last tree
  1380. if(!is_null($compiledFilename))
  1381. {
  1382. $output = '';
  1383. $this->_dynamicBlocks = array();
  1384. $this->_stage3($output, $tree);
  1385. $tree->dispose();
  1386. unset($tree);
  1387. $output = str_replace('?><'.'?php', '', $output);
  1388. // Build the directories, if needed.
  1389. if(($pos = strrpos($compiledFilename, '/')) !== false)
  1390. {
  1391. $path = $this->_tpl->compileDir.substr($compiledFilename, 0, $pos);
  1392. if(!is_dir($path))
  1393. {
  1394. mkdir($path, 0750, true);
  1395. }
  1396. }
  1397. // Save the file
  1398. if(sizeof($this->_dynamicBlocks) > 0)
  1399. {
  1400. file_put_contents($this->_tpl->compileDir.$compiledFilename.'.dyn', serialize($this->_dynamicBlocks));
  1401. }
  1402. file_put_contents($this->_tpl->compileDir.$compiledFilename, $output);
  1403. }
  1404. else
  1405. {
  1406. $tree->dispose();
  1407. }
  1408. array_pop(self::$_templates);
  1409. $this->_inheritance = array();
  1410. if($weFree)
  1411. {
  1412. // Do the cleanup.
  1413. $this->_dependencies = array();
  1414. self::$_recursionDetector = NULL;
  1415. foreach($this->_processors as $processor)
  1416. {
  1417. $processor->reset();
  1418. }
  1419. }
  1420. $this->_template = NULL;
  1421. // Run the new garbage collector, if it is available.
  1422. /* if(version_compare(PHP_VERSION, '5.3.0', '>='))
  1423. {
  1424. gc_collect_cycles();
  1425. }*/
  1426. }
  1427. catch(Exception $e)
  1428. {
  1429. // Free the memory
  1430. if(isset($tree))
  1431. {
  1432. $tree->dispose();
  1433. }
  1434. // Clean the compiler state in case of exception
  1435. $this->_template = NULL;
  1436. $this->_dependencies = array();
  1437. self::$_recursionDetector = NULL;
  1438. foreach($this->_processors as $processor)
  1439. {
  1440. $processor->reset();
  1441. }
  1442. // Run the new garbage collector, if it is available.
  1443. /* if(version_compare(PHP_VERSION, '5.3.0', '>='))
  1444. {
  1445. gc_collect_cycles();
  1446. }*/
  1447. // And throw it forward.
  1448. throw $e;
  1449. }
  1450. } // end compile();
  1451. /**
  1452. * Compilation - stage 1 - parsing the input template and
  1453. * building an XML tree.
  1454. *
  1455. * @internal
  1456. * @param String &$code The code to be parsed
  1457. * @param String $filename Currently unused.
  1458. * @param Int $mode The compilation mode.
  1459. * @return Opt_Xml_Root The root node of the new tree.
  1460. */
  1461. protected function _stage1(&$code, $filename, $mode)
  1462. {
  1463. $current = $tree = new Opt_Xml_Root;
  1464. $codeSize = strlen($code);
  1465. $encoding = $this->_tpl->charset;
  1466. // First we have to find the prolog and DTD. Then we will be able to parse tags.
  1467. if($mode != Opt_Class::QUIRKS_MODE)
  1468. {
  1469. // Find and parse XML prolog
  1470. $endProlog = 0;
  1471. $endDoctype = 0;
  1472. if(substr($code, 0, 5) == '<?xml')
  1473. {
  1474. $endProlog = strpos($code, '?>', 5);
  1475. if($endProlog === false)
  1476. {
  1477. throw new Opt_XmlInvalidProlog_Exception('prolog ending is missing');
  1478. }
  1479. $values = $this->_compileProlog(substr($code, 5, $endProlog - 5));
  1480. $endProlog += 2;
  1481. if(!$this->_tpl->prologRequired)
  1482. {
  1483. // The prolog must be displayed
  1484. $tree->setProlog(new Opt_Xml_Prolog($values));
  1485. }
  1486. }
  1487. // Skip white spaces
  1488. for($i = $endProlog; $i < $codeSize; $i++)
  1489. {
  1490. if($code[$i] != ' ' && $code[$i] != ' ' && $code[$i] != "\r" && $code[$i] != "\n")
  1491. {
  1492. break;
  1493. }
  1494. }
  1495. // Try to find doctype at the new position.
  1496. $possibleDoctype = substr($code, $i, 9);
  1497. if($possibleDoctype == '<!doctype' || $possibleDoctype == '<!DOCTYPE')
  1498. {
  1499. // OK, we've found it, now determine the doctype end.
  1500. $bracketCounter = 0;
  1501. $doctypeStart = $i;
  1502. for($i += 9; $i < $codeSize; $i++)
  1503. {
  1504. if($code[$i] == '<')
  1505. {
  1506. $bracketCounter++;
  1507. }
  1508. else if($code[$i] == '>')
  1509. {
  1510. if($bracketCounter == 0)
  1511. {
  1512. $endDoctype = $i;
  1513. break;
  1514. }
  1515. $bracketCounter--;
  1516. }
  1517. }
  1518. if($endDoctype == 0)
  1519. {
  1520. throw new Opt_XmlInvalidDoctype_Exception('doctype ending is missing');
  1521. }
  1522. if(!$this->_tpl->prologRequired)
  1523. {
  1524. $tree->setDtd(new Opt_Xml_Dtd(substr($code, $doctypeStart, $i - $doctypeStart + 1)));
  1525. }
  1526. $endDoctype++;
  1527. }
  1528. else
  1529. {
  1530. $endDoctype = $endProlog;
  1531. }
  1532. // OK, now skip that part.
  1533. $code = substr($code, $endDoctype, $codeSize);
  1534. // In the quirks mode, some results from the regular expression parser are
  1535. // moved by one position, so we must add some dynamics here.
  1536. $attributeCell = 5;
  1537. $endingSlashCell = 6;
  1538. $tagExpression = $this->_rXmlTagExpression;
  1539. }
  1540. else
  1541. {
  1542. $tagExpression = $this->_rQuirksTagExpression;
  1543. $attributeCell = 6;
  1544. $endingSlashCell = 7;
  1545. }
  1546. // Split through the general groups (cdata-content)
  1547. $groups = preg_split($this->_rCDataExpression, $code, 0, PREG_SPLIT_DELIM_CAPTURE);
  1548. $groupCnt = sizeof($groups);
  1549. $groupState = 0;
  1550. Opt_Xml_Cdata::$mode = $mode;
  1551. for($k = 0; $k < $groupCnt; $k++)
  1552. {
  1553. // Process CDATA
  1554. if($groupState == 0 && $groups[$k] == '<![CDATA[')
  1555. {
  1556. $cdata = new Opt_Xml_Cdata('');
  1557. $cdata->set('cdata', true);
  1558. $groupState = 1;
  1559. continue;
  1560. }
  1561. if($groupState == 1)
  1562. {
  1563. if($groups[$k] == ']]>')
  1564. {
  1565. $current = $this->_treeTextAppend($current, $cdata);
  1566. $groupState = 0;
  1567. }
  1568. else
  1569. {
  1570. $cdata->appendData($groups[$k]);
  1571. }
  1572. continue;
  1573. }
  1574. $subgroups = preg_split($this->_rCommentExpression, $groups[$k], 0, PREG_SPLIT_DELIM_CAPTURE);
  1575. $subgroupCnt = sizeof($subgroups);
  1576. $subgroupState = 0;
  1577. for($i = 0; $i < $subgroupCnt; $i++)
  1578. {
  1579. // Process comments
  1580. if($subgroupState == 0 && $subgroups[$i] == '<!--')
  1581. {
  1582. $commentNode = new Opt_Xml_Comment();
  1583. $subgroupState = 1;
  1584. continue;
  1585. }
  1586. if($subgroupState == 1)
  1587. {
  1588. if($subgroups[$i] == '-->')
  1589. {
  1590. $current->appendChild($commentNode);
  1591. $subgroupState = 0;
  1592. }
  1593. else
  1594. {
  1595. $commentNode->appendData($subgroups[$i]);
  1596. }
  1597. continue;
  1598. }
  1599. elseif($subgroups[$i] == '-->')
  1600. {
  1601. throw new Opt_XmlInvalidCharacter_Exception('--&gt;');
  1602. }
  1603. // Find XML tags
  1604. preg_match_all($tagExpression, $subgroups[$i], $result, PREG_SET_ORDER);
  1605. /*
  1606. * Output field description for $result array:
  1607. * 0 - original content
  1608. * 1 - tag content (without delimiters)
  1609. * 2 - /, if enclosing tag
  1610. * 3 - name
  1611. * 4 - arguments (5 in quirks mode)
  1612. * 5 - /, if enclosing tag without subcontent (6 in quirks mode)
  1613. */
  1614. $resultSize = sizeof($result);
  1615. $offset = 0;
  1616. for($j = 0; $j < $resultSize; $j++)
  1617. {
  1618. // Copy the remaining text to the text node
  1619. $id = strpos($subgroups[$i], $result[$j][0], $offset);
  1620. if($id > $offset)
  1621. {
  1622. $current = $this->_treeTextCompile($current, substr($subgroups[$i], $offset, $id - $offset));
  1623. }
  1624. $offset = $id + strlen($result[$j][0]);
  1625. if(!isset($result[$j][$endingSlashCell]))
  1626. {
  1627. $result[$j][$endingSlashCell] = '';
  1628. }
  1629. // Process the argument list
  1630. $attributes = array();
  1631. if(!empty($result[$j][$attributeCell]))
  1632. {
  1633. // Just for sure...
  1634. $result[$j][$attributeCell] = trim($result[$j][$attributeCell]);
  1635. $oldLength = strlen($result[$j][$attributeCell]);
  1636. $result[$j][$attributeCell] = rtrim($result[$j][$attributeCell], '/');
  1637. if(strlen($result[$j][$attributeCell]) != $oldLength)
  1638. {
  1639. $result[$j][$endingSlashCell] = '/';
  1640. }
  1641. $attributes = $this->_compileAttributes($result[$j][$attributeCell], $result[$j][4]);
  1642. if(!is_array($attributes))
  1643. {
  1644. throw new Opt_XmlInvalidAttribute_Exception($result[$j][0]);
  1645. }
  1646. }
  1647. // Recognize the tag type
  1648. if(substr_count($result[$j][4], ':') > 1)
  1649. {
  1650. throw new Opt_InvalidNamespace_Exception($result[$j][4]);
  1651. }
  1652. if($result[$j][3] != '/')
  1653. {
  1654. // Opening tag
  1655. $node = new Opt_Xml_Element($result[$j][4]);
  1656. $node->set('single', $result[$j][$endingSlashCell] == '/');
  1657. foreach($attributes as $name => $value)
  1658. {
  1659. $node->addAttribute($anode = new Opt_Xml_Attribute($name, $value));
  1660. }
  1661. $current = $this->_treeNodeAppend($current, $node, $result[$j][$endingSlashCell] != '/');
  1662. }
  1663. elseif($result[$j][3] == '/')
  1664. {
  1665. if(sizeof($attributes) > 0)
  1666. {
  1667. throw new Opt_XmlInvalidTagStructure_Exception($result[$j][0]);
  1668. }
  1669. if($current instanceof Opt_Xml_Element)
  1670. {
  1671. if($current->getXmlName() != $result[$j][4])
  1672. {
  1673. throw new Opt_XmlInvalidOrder_Exception($result[$j][4], $current->getXmlName());
  1674. }
  1675. }
  1676. else
  1677. {
  1678. throw new Opt_XmlInvalidOrder_Exception($result[$j][4], 'NULL');
  1679. }
  1680. $current = $this->_treeJumpOut($current);
  1681. }
  1682. else
  1683. {
  1684. throw new Opt_XmlInvalidTagStructure_Exception($result[$j][0]);
  1685. }
  1686. }
  1687. if(strlen($subgroups[$i]) > $offset)
  1688. {
  1689. $current = $this->_treeTextCompile($current, substr($subgroups[$i], $offset, strlen($subgroups[$i]) - $offset));
  1690. }
  1691. }
  1692. }
  1693. // Testing if everything was closed.
  1694. if($current !== $tree)
  1695. {
  1696. // Error handling - determine the name of the unclosed tag.
  1697. while(! $current instanceof Opt_Xml_Element)
  1698. {
  1699. $current = $current->getParent();
  1700. }
  1701. throw new Opt_UnclosedTag_Exception($current->getXmlName());
  1702. }
  1703. // Testing the single root node.
  1704. if($mode == Opt_Class::XML_MODE && $this->_tpl->singleRootNode)
  1705. {
  1706. // TODO: The current code does not check the contents of Opt_Text_Nodes and other root elements
  1707. // that may contain invalid and valid XML syntax at the same time.
  1708. // For now, this code is frozen, we'll think a bit about it in the future. Maybe nobody
  1709. // will notice this :)
  1710. $elementFound = false;
  1711. foreach($tree as $item)
  1712. {
  1713. if($item instanceof Opt_Xml_Element)
  1714. {
  1715. if($elementFound)
  1716. {
  1717. // Oops, there is already another root node!
  1718. throw new Opt_XmlRootElement_Exception($item->getXmlName());
  1719. }
  1720. $elementFound = true;
  1721. }
  1722. }
  1723. }
  1724. return $tree;
  1725. } // end _stage1();
  1726. /**
  1727. * Compilation - stage 2. Traversing through the tree and doing something
  1728. * with the tree and the nodes. The method is recursion-safe.
  1729. *
  1730. * @internal
  1731. * @param Opt_Xml_Node $node The initial node.
  1732. */
  1733. protected function _stage2(Opt_Xml_Node $node)
  1734. {
  1735. $queue = new SplQueue;
  1736. $stack = new SplStack;
  1737. $queue->enqueue($node);
  1738. while(true)
  1739. {
  1740. $item = NULL;
  1741. if($queue->count() > 0)
  1742. {
  1743. $item = $queue->dequeue();
  1744. }
  1745. $pp = array();
  1746. // We set the "hidden" state unless it is set.
  1747. try
  1748. {
  1749. if(is_null($item))
  1750. {
  1751. throw new Opl_Goto_Exception;
  1752. }
  1753. $stateSet = false;
  1754. if(is_null($item->get('hidden')))
  1755. {
  1756. $item->set('hidden', true);
  1757. $stateSet = true;
  1758. }
  1759. // Proper processing
  1760. switch($item->getType())
  1761. {
  1762. case 'Opt_Xml_Cdata':
  1763. $stateSet and $item->set('hidden', false);
  1764. break;
  1765. case 'Opt_Xml_Text':
  1766. $stateSet and $item->set('hidden', false);
  1767. if($item->hasChildren())
  1768. {
  1769. $stack->push(array($item, $queue, $pp));
  1770. $pp = NULL;
  1771. $queue = new SplQueue;
  1772. foreach($item as $child)
  1773. {
  1774. $queue->enqueue($child);
  1775. }
  1776. continue 2;
  1777. }
  1778. break;
  1779. case 'Opt_Xml_Element':
  1780. if($this->isNamespace($item->getNamespace()))
  1781. {
  1782. $name = $item->getXmlName();
  1783. $pp = $this->_processXml($item, false);
  1784. // Look for the processor
  1785. if(!is_null($processor = $this->isInstruction($name)))
  1786. {
  1787. $processor->processNode($item);
  1788. }
  1789. elseif($this->isComponent($name))
  1790. {
  1791. $processor = $this->processor('component');
  1792. $processor->processComponent($item);
  1793. }
  1794. elseif($this->isBlock($name))
  1795. {
  1796. $processor = $this->processor('block');
  1797. $processor->processBlock($item);
  1798. }
  1799. if(is_object($processor))
  1800. {
  1801. $stateSet and $item->set('hidden', false);
  1802. $result = $processor->getQueue();
  1803. if(!is_null($result))
  1804. {
  1805. $stack->push(array($item, $queue, $pp));
  1806. $queue = $result;
  1807. continue 2;
  1808. }
  1809. }
  1810. elseif($item->get('processAll'))
  1811. {
  1812. $stateSet and $item->set('hidden', false);
  1813. $stack->push(array($item, $queue, $pp));
  1814. $pp = NULL;
  1815. $queue = new SplQueue;
  1816. foreach($item as $child)
  1817. {
  1818. $queue->enqueue($child);
  1819. }
  1820. continue 2;
  1821. }
  1822. unset($processor);
  1823. }
  1824. else
  1825. {
  1826. $pp = $this->_processXml($item, true);
  1827. $stateSet and $item->set('hidden', false);
  1828. if($item->hasChildren())
  1829. {
  1830. $stack->push(array($item, $queue, $pp));
  1831. $pp = NULL;
  1832. $queue = new SplQueue;
  1833. foreach($item as $child)
  1834. {
  1835. $queue->enqueue($child);
  1836. }
  1837. continue 2;
  1838. }
  1839. }
  1840. break;
  1841. case 'Opt_Xml_Expression':
  1842. $stateSet and $item->set('hidden', false);
  1843. // Empty expressions will be caught by the try... catch.
  1844. try
  1845. {
  1846. $result = $this->compileExpression((string)$item, true);
  1847. if(!$result[1])
  1848. {
  1849. $item->addAfter(Opt_Xml_Buffer::TAG_BEFORE, 'echo '.$result[0].'; ');
  1850. }
  1851. else
  1852. {
  1853. $item->addAfter(Opt_Xml_Buffer::TAG_BEFORE, $result[0].';');
  1854. }
  1855. }
  1856. catch(Opt_EmptyExpression_Exception $e){}
  1857. break;
  1858. case 'Opt_Xml_Root':
  1859. $stateSet and $item->set('hidden', false);
  1860. $queue = $this->_pushQueue($stack, $queue, $item, array());
  1861. break;
  1862. case 'Opt_Xml_Comment':
  1863. if($this->_tpl->printComments)
  1864. {
  1865. $stateSet and $item->set('hidden', false);
  1866. }
  1867. break;
  1868. }
  1869. }
  1870. catch(Opl_Goto_Exception $e){}
  1871. $this->_doPostprocess($item, $pp);
  1872. if($queue->count() == 0)
  1873. {
  1874. unset($queue);
  1875. if($stack->count() == 0)
  1876. {
  1877. break;
  1878. }
  1879. list($item, $queue, $pp) = $stack->pop();
  1880. $this->_doPostprocess($item, $pp);
  1881. }
  1882. }
  1883. } // end _stage2();
  1884. /**
  1885. * Links the tree into a valid XML file with embedded PHP commands
  1886. * from the tag buffers. The method is recursion-free.
  1887. *
  1888. * @internal
  1889. * @param String &$output Where to store the output.
  1890. * @param Opt_Xml_Node $node The initial node.
  1891. */
  1892. protected function _stage3(&$output, Opt_Xml_Node $node)
  1893. {
  1894. // To avoid the recursion, we need a queue and a stack.
  1895. $queue = new SplQueue;
  1896. $stack = new SplStack;
  1897. $queue->enqueue($node);
  1898. // Reset the output
  1899. $realOutput = &$output;
  1900. $output = '';
  1901. $wasElement = false;
  1902. // We will be processing the tags in a "infinite" loop
  1903. // In fact, the stop condition can be found within the loop.
  1904. while(true)
  1905. {
  1906. // Obtain the new item to process from the queue.
  1907. $item = NULL;
  1908. if($queue->count() > 0)
  1909. {
  1910. $item = $queue->dequeue();
  1911. }
  1912. // Note that this code uses Opl_Goto_Exception to simulate
  1913. // "goto" instruction.
  1914. try
  1915. {
  1916. // Should we display this node?
  1917. if(is_null($item) || $item->get('hidden') !== false)
  1918. {
  1919. throw new Opl_Goto_Exception; // Goto postprocess;
  1920. }
  1921. // If the tag has the "commented" flag, we comment it
  1922. // and its content.
  1923. if($item->get('commented'))
  1924. {
  1925. $this->_comments++;
  1926. if($this->_comments == 1)
  1927. {
  1928. $output .= '<!--';
  1929. }
  1930. }
  1931. // If the block is dynamic, then replace the output with some other variable.
  1932. if($item->get('dynamic'))
  1933. {
  1934. $i = sizeof($this->_dynamicBlocks);
  1935. $this->_dynamicBlocks[$i] = '';
  1936. $output = &$this->_dynamicBlocks[$i];
  1937. }
  1938. /* Note that some of the node types must execute some code both before
  1939. * processing their children and after them. This method processes only
  1940. * the code executed BEFORE the children are parsed. The latter situation
  1941. * is implemented in the _doPostlinking() method.
  1942. */
  1943. $doPostlinking = false;
  1944. switch($item->getType())
  1945. {
  1946. case 'Opt_Xml_Cdata':
  1947. if($item->get('cdata'))
  1948. {
  1949. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE).'<![CDATA['.$item.']]>'.$item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
  1950. break;
  1951. }
  1952. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);
  1953. // We strip the white spaces at the linking level.
  1954. if($this->_tpl->stripWhitespaces)
  1955. {
  1956. // The CDATA composed of white characters only is reduced to a single space.
  1957. if(ctype_space((string)$item))
  1958. {
  1959. if($wasElement)
  1960. {
  1961. $output .= ' ';
  1962. }
  1963. }
  1964. else
  1965. {
  1966. // In the opposite case reduce all the groups of the white characters
  1967. // to single spaces in the text.
  1968. if($item->get('noEntitize') === true)
  1969. {
  1970. $output .= preg_replace('/\s\s+/', ' ', (string)$item);
  1971. }
  1972. else
  1973. {
  1974. $output .= $this->parseSpecialChars(preg_replace('/(\s){1,}/', ' ', (string)$item));
  1975. }
  1976. }
  1977. }
  1978. else
  1979. {
  1980. $output .= ($item->get('noEntitize') ? (string)$item : $this->parseSpecialChars($item));
  1981. }
  1982. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
  1983. $this->_closeComments($item, $output);
  1984. break;
  1985. case 'Opt_Xml_Text':
  1986. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);
  1987. $queue = $this->_pushQueue($stack, $queue, $item, NULL);
  1988. // Next part in the post-process section
  1989. break;
  1990. case 'Opt_Xml_Element':
  1991. if($this->isNamespace($item->getNamespace()))
  1992. {
  1993. // This code handles the XML elements that represent the
  1994. // OPT instructions. They have shorter code, because
  1995. // we do not need to display their tags.
  1996. if(!$item->hasChildren() && $item->get('single'))
  1997. {
  1998. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_SINGLE_BEFORE);
  1999. $doPostlinking = true;
  2000. }
  2001. elseif($item->hasChildren())
  2002. {
  2003. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_OPENING_BEFORE,
  2004. Opt_Xml_Buffer::TAG_OPENING_AFTER, Opt_Xml_Buffer::TAG_CONTENT_BEFORE);
  2005. $queue = $this->_pushQueue($stack, $queue, $item, NULL);
  2006. // Next part in the post-process section
  2007. }
  2008. else
  2009. {
  2010. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_OPENING_BEFORE,
  2011. Opt_Xml_Buffer::TAG_OPENING_AFTER, Opt_Xml_Buffer::TAG_CONTENT_BEFORE);
  2012. $doPostlinking = true;
  2013. }
  2014. }
  2015. else
  2016. {
  2017. $wasElement = true;
  2018. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_OPENING_BEFORE);
  2019. if($item->bufferSize(Opt_Xml_Buffer::TAG_NAME) == 0)
  2020. {
  2021. $name = $item->getXmlName();
  2022. }
  2023. elseif($item->bufferSize(Opt_Xml_Buffer::TAG_NAME) == 1)
  2024. {
  2025. $name = $item->buildCode(Opt_Xml_Buffer::TAG_NAME);
  2026. }
  2027. else
  2028. {
  2029. throw new Opt_CompilerCodeBufferConflict_Exception(1, 'TAG_NAME', $item->getXmlName());
  2030. }
  2031. if(!$item->hasChildren() && $item->bufferSize(Opt_Xml_Buffer::TAG_CONTENT) == 0 && $item->get('single'))
  2032. {
  2033. $output .= '<'.$name.$this->_linkAttributes($item).' />'.$item->buildCode(Opt_Xml_Buffer::TAG_SINGLE_AFTER,Opt_Xml_Buffer::TAG_AFTER);
  2034. $item = null;
  2035. }
  2036. else
  2037. {
  2038. $output .= '<'.$name.$this->_linkAttributes($item).'>'.$item->buildCode(Opt_Xml_Buffer::TAG_OPENING_AFTER);
  2039. $item->set('_name', $name);
  2040. if($item->bufferSize(Opt_Xml_Buffer::TAG_CONTENT) > 0)
  2041. {
  2042. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_BEFORE, Opt_Xml_Buffer::TAG_CONTENT, Opt_Xml_Buffer::TAG_CONTENT_AFTER);
  2043. }
  2044. elseif($item->hasChildren())
  2045. {
  2046. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_BEFORE);
  2047. $queue = $this->_pushQueue($stack, $queue, $item, NULL);
  2048. // Next part in the post-process section
  2049. break;
  2050. }
  2051. else
  2052. {
  2053. // The postlinking is already done here, so skip this part
  2054. // in the linker
  2055. $item->set('_skip_postlinking', true);
  2056. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_CLOSING_BEFORE).'</'.$name.'>'.$item->buildCode(Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
  2057. }
  2058. }
  2059. }
  2060. break;
  2061. case 'Opt_Xml_Expression':
  2062. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);
  2063. $this->_closeComments($item, $output);
  2064. break;
  2065. case 'Opt_Xml_Root':
  2066. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);
  2067. // Display the prolog and DTD, if it was set in the node.
  2068. // Such construct ensures us that they will appear in the
  2069. // valid place in the output document.
  2070. if($item->hasProlog())
  2071. {
  2072. $output .= str_replace('<?xml', '<<?php echo \'?\'; ?>xml', $item->getProlog()->getProlog())."\r\n";
  2073. }
  2074. if($item->hasDtd())
  2075. {
  2076. $output .= $item->getDtd()->getDoctype()."\r\n";
  2077. }
  2078. // And go to their children.
  2079. $queue = $this->_pushQueue($stack, $queue, $item, NULL);
  2080. break;
  2081. case 'Opt_Xml_Comment':
  2082. // The comment tags are added automatically.
  2083. $output .= (string)$item;
  2084. $this->_closeComments($item, $output);
  2085. break;
  2086. }
  2087. }
  2088. catch(Opl_Goto_Exception $goto){} // postprocess:
  2089. if($doPostlinking)
  2090. {
  2091. $output .= $this->_doPostlinking($item);
  2092. if(is_object($item) && $item->get('dynamic'))
  2093. {
  2094. $realOutput .= $output;
  2095. $output = &$realOutput;
  2096. }
  2097. }
  2098. if($queue->count() == 0)
  2099. {
  2100. if($stack->count() == 0)
  2101. {
  2102. break;
  2103. }
  2104. /**
  2105. * !!!!!!!!!!!!THIS IS WRONG!!!!!!!!!!!!!!!!
  2106. *
  2107. * Some items get this called only if they are
  2108. * the last in the queue.
  2109. */
  2110. if(!$doPostlinking)
  2111. {
  2112. $output .= $this->_doPostlinking($item);
  2113. if(is_object($item) && $item->get('dynamic'))
  2114. {
  2115. $realOutput .= $output;
  2116. $output = &$realOutput;
  2117. }
  2118. }
  2119. list($item, $queue, $pp) = $stack->pop();
  2120. $output .= $this->_doPostlinking($item);
  2121. if($item->get('dynamic'))
  2122. {
  2123. $realOutput .= $output;
  2124. $output = &$realOutput;
  2125. }
  2126. }
  2127. }
  2128. if($this->_tpl->stripWhitespaces)
  2129. {
  2130. $output = rtrim($output);
  2131. }
  2132. } // end _stage3();
  2133. /*
  2134. * Expression compiler
  2135. */
  2136. /**
  2137. * Compiles the template expression to the PHP code and checks the syntax
  2138. * errors. The method is recursion-free.
  2139. *
  2140. * @param String $expr The expression
  2141. * @param Boolean $allowAssignment=false True, if the assignments are allowed.
  2142. * @param Int $escape=self::ESCAPE_ON The HTML escaping policy for this expression.
  2143. * @return Array An array consisting of four elements: the compiled expression,
  2144. * the assignment status and the variable status (if the expression is in fact
  2145. * a single variable). If the escaping is controlled by the template or the
  2146. * script, the fourth element contains also an unescaped PHP expression.
  2147. */
  2148. public function compileExpression($expr, $allowAssignment = false, $escape = self::ESCAPE_ON)
  2149. {
  2150. // The expression modifier must not be tokenized, so we
  2151. // capture it before doing anything with the expression.
  2152. $modifier = '';
  2153. if(preg_match('/^([^\'])\:[^\:]/', $expr, $found))
  2154. {
  2155. $modifier = $found[1];
  2156. if($modifier != 'e' && $modifier != 'u')
  2157. {
  2158. throw new Opt_InvalidExpressionModifier_Exception($modifier, $expr);
  2159. }
  2160. $expr = substr($expr, 2, strlen($expr) - 2);
  2161. }
  2162. // cat $expr > /dev/oracle > $result > happy programmer :)
  2163. preg_match_all('/(?:'.
  2164. $this->_rSingleQuoteString.'|'.
  2165. $this->_rBacktickString.'|'.
  2166. $this->_rHexadecimalNumber.'|'.
  2167. $this->_rDecimalNumber.'|'.
  2168. $this->_rLanguageVar.'|'.
  2169. $this->_rVariable.'|'.
  2170. $this->_rOperators.'|'.
  2171. $this->_rIdentifier.')/x', $expr, $match);
  2172. // Skip the whitespaces and create the translation units
  2173. $cnt = sizeof($match[0]);
  2174. $stack = new SplStack;
  2175. $tu = array(0 => array());
  2176. $tuid = 0;
  2177. $maxTuid = 0;
  2178. $prev = '';
  2179. $chr = chr(18);
  2180. $assignments = array();
  2181. /* The translation units allow to avoid recursive compilation of the
  2182. * expression. Each sub-expression within parentheses and that is a
  2183. * function call parameter, becomes a separate translation unit. The
  2184. * loop below scans the array of tokens, looking for translation
  2185. * unit separators and builds suitable arrays of tokens for each
  2186. * TU.
  2187. */
  2188. for($i = 0; $i < $cnt; $i++)
  2189. {
  2190. if(ctype_space($match[0][$i]) || $match[0][$i] == '')
  2191. {
  2192. continue;
  2193. }
  2194. switch($match[0][$i])
  2195. {
  2196. case ',':
  2197. if($prev == '(' || $prev == ',')
  2198. {
  2199. throw new Opt_Expression_Exception('OP_COMMA', $match[0][$i], $expr);
  2200. }
  2201. $tuid = $stack->pop();
  2202. if(in_array($tuid, $assignments))
  2203. {
  2204. $tuid = $stack->pop();
  2205. }
  2206. case '[':
  2207. case '(':
  2208. case 'is':
  2209. case '=':
  2210. if($match[0][$i] == '=' || $match[0][$i] == 'is')
  2211. {
  2212. $assignments[] = $tuid;
  2213. }
  2214. $tu[$tuid][] = $match[0][$i];
  2215. ++$maxTuid;
  2216. $tu[$tuid][] = $chr.$maxTuid; // A fake token that marks the translation unit which goes here.
  2217. $stack->push($tuid);
  2218. $tuid = $maxTuid;
  2219. $tu[$tuid] = array();
  2220. break;
  2221. case ']':
  2222. case ')':
  2223. // If we have a situation like (), we can remove the TU we've just created,
  2224. // because it's empty and will confuse the expression compiler later.
  2225. if($prev == '(')
  2226. {
  2227. unset($tu[$tuid]);
  2228. --$maxTuid;
  2229. }
  2230. if($stack->count() > 0)
  2231. {
  2232. $tuid = $stack->pop();
  2233. if(in_array($tuid, $assignments))
  2234. {
  2235. $tuid = $stack->pop();
  2236. }
  2237. }
  2238. if($prev == '(')
  2239. {
  2240. array_pop($tu[$tuid]);
  2241. }
  2242. if($prev == ',')
  2243. {
  2244. throw new Opt_Expression_Exception('OP_BRACKET', $match[0][$i], $expr);
  2245. }
  2246. $tu[$tuid][] = $match[0][$i];
  2247. break;
  2248. default:
  2249. $tu[$tuid][] = $match[0][$i];
  2250. }
  2251. $prev = $match[0][$i];
  2252. }
  2253. if(sizeof($tu[0]) == 0)
  2254. {
  2255. throw new Opt_EmptyExpression_Exception();
  2256. }
  2257. /*
  2258. * Now we have an array of translation units and their tokens and
  2259. * we can process it linearly, thus avoiding recursive calls.
  2260. */
  2261. foreach($tu as $id => &$tuItem)
  2262. {
  2263. $tuItem = $this->_compileExpression($expr, $allowAssignment, $tuItem, $id);
  2264. }
  2265. $assign = $tu[0][1];
  2266. $variable = $tu[0][2];
  2267. /*
  2268. * Finally, we have to link all the subexpressions into an output
  2269. * expression. We use SPL stack to achieve this, because we need
  2270. * to store the current subexpression status while finding a new one.
  2271. */
  2272. $tuid = 0;
  2273. $i = -1;
  2274. $cnt = sizeof($tu[0][0]);
  2275. $stack = new SplStack;
  2276. $prev = null;
  2277. $expression = '';
  2278. while(true)
  2279. {
  2280. $i++;
  2281. $token = &$tu[$tuid][0][$i];
  2282. // If we've found a translation unit, we must stop for a while the current one
  2283. // and link the new.
  2284. if(strlen($token) > 0 && $token[0] == $chr)
  2285. {
  2286. $wasAssignment = in_array($tuid, $assignments);
  2287. $stack->push(Array($tuid, $i, $cnt));
  2288. $tuid = (int)ltrim($token, $chr);
  2289. $i = -1;
  2290. $cnt = sizeof($tu[$tuid][0]);
  2291. if($cnt == 0 && $wasAssignment)
  2292. {
  2293. throw new Opt_Expression_Exception('OP_NULL', '', $expr);
  2294. }
  2295. continue;
  2296. }
  2297. else
  2298. {
  2299. $expression .= $token;
  2300. }
  2301. if($i >= $cnt)
  2302. {
  2303. if($stack->count() == 0)
  2304. {
  2305. break;
  2306. }
  2307. // OK, current TU is ready. Check, whether there are unfinished upper-level TUs
  2308. // on the stack
  2309. unset($tu[$tuid]);
  2310. list($tuid, $i, $cnt) = $stack->pop();
  2311. }
  2312. $prev = $token;
  2313. }
  2314. /*
  2315. * Now it's time to apply the escaping policy to this expression. We check
  2316. * the expression for the "e:" and "u:" modifiers and redirect the task to
  2317. * the escape() method.
  2318. */
  2319. $result = $expression;
  2320. if($escape != self::ESCAPE_OFF && !$assign)
  2321. {
  2322. if($modifier != '')
  2323. {
  2324. $result = $this->escape($result, $modifier == 'e');
  2325. }
  2326. else
  2327. {
  2328. $result = $this->escape($result);
  2329. }
  2330. }
  2331. // Pack everything
  2332. if($escape != self::ESCAPE_BOTH)
  2333. {
  2334. return array(0 => $result, $assign, $variable, NULL);
  2335. }
  2336. else
  2337. {
  2338. return array(0 => $result, $assign, $variable, $expression);
  2339. }
  2340. } // end compileExpression();
  2341. /**
  2342. * Compiles a single translation unit in the expression.
  2343. *
  2344. * @internal
  2345. * @param String &$expr A reference to the compiled expressions for debug purposes.
  2346. * @param Boolean $allowAssignment True, if the assignments are allowed in this unit.
  2347. * @param Array &$tokens A reference to the array of tokens for this translation unit.
  2348. * @param String $tu The number of the current translation unit.
  2349. * @return Array An array build of three items: the compiled expression, the assignment status
  2350. * and the variable status (whether the expression is in fact a single variable).
  2351. */
  2352. protected function _compileExpression(&$expr, $allowAssignment, Array &$tokens, $tu)
  2353. {
  2354. /* The method processes a single translation unit (TU). For example, in the expression
  2355. * $a is ($b + $c) * $d
  2356. * we have the following translation units:
  2357. * 1. $a is #TU2 * $d
  2358. * 2. $b + $c
  2359. *
  2360. * They are compiled separately and automatically, so you do not have to do this on
  2361. * your own. This has been done to remove the recursion from the source code, and moreover
  2362. * it allows, for example, to manage the argument order in the functions.
  2363. */
  2364. // Operator mappings
  2365. $wordOperators = array(
  2366. 'eq' => '==',
  2367. 'eqt' => '===',
  2368. 'ne' => '!=',
  2369. 'net' => '!==',
  2370. 'neq' => '!=',
  2371. 'neqt' => '!==',
  2372. 'lt' => '<',
  2373. 'le' => '<=',
  2374. 'lte' => '<=',
  2375. 'gt' => '>',
  2376. 'ge' => '>=',
  2377. 'gte' => '>=',
  2378. 'and' => '&&',
  2379. 'or' => '||',
  2380. 'xor' => 'xor',
  2381. 'not' => '!',
  2382. 'mod' => '%',
  2383. 'div' => '/',
  2384. 'add' => '+',
  2385. 'sub' => '-',
  2386. 'mul' => '*',
  2387. 'shl' => '<<',
  2388. 'shr' => '>>'
  2389. );
  2390. // Previous token information
  2391. $previous = array(
  2392. 'token' => null,
  2393. 'source' => null,
  2394. 'result' => null
  2395. );
  2396. // Some standard "next token sets"
  2397. $valueSet = self::OP_VARIABLE | self::OP_LANGUAGE_VAR | self::OP_STRING | self::OP_NUMBER |
  2398. self::OP_IDENTIFIER | self::OP_PRE_OPERATOR | self::OP_OBJMAN | self::OP_BRACKET;
  2399. $operatorSet = self::OP_OPERATOR | self::OP_POST_OPERATOR | self::OP_NULL;
  2400. // Initial state
  2401. $state = array(
  2402. 'next' => $valueSet | self::OP_NULL, // What token must occur next.
  2403. 'step' => 0, // This flag helps processing brackets by saving some extra token information.
  2404. 'func' => 0, // The function call type: 0 - OPT function (with "$this" as the first argument); 1 - ordinary function
  2405. 'oper' => false, // The assignment flag. The value must be assigned to a variable, so on the left side there must not be any operator (false).
  2406. 'clone' => 0, // We've already used "clone"
  2407. 'preop' => false, // Prefix operators ++ and -- found. This flag is cancelled by any other operator.
  2408. 'rev' => NULL, // Changing the argument order options
  2409. 'assign_func' => false, // Informing the bracket parser that the first argument must be a language block, which must be processed separately.
  2410. 'tu' => 0, // What has opened a translation unit? The field contains the token type.
  2411. 'variable' => NULL, // To detect if the expression is a single variable or not.
  2412. 'function' => NULL // Function name for the argument checker errors
  2413. );
  2414. $chr = chr(18); // Which ASCII code marks the translation unit
  2415. $result = array(); // Here we put the compilation result
  2416. $void = false; // This is a fake variable for a recursive call, as a last argument (reference)
  2417. $assign = false;
  2418. $to = sizeof($tokens);
  2419. // Loop through the token list.
  2420. for($i = 0; $i < $to; $i++)
  2421. {
  2422. // Some initializing stuff.
  2423. $token = &$tokens[$i];
  2424. $parsefunc = false;
  2425. $current = array(
  2426. 'token' => null, // Symbolic token type. Look at the file header to find the token definitions.
  2427. 'source' => $token, // Original form of the token is also remembered.
  2428. 'result' => null, // Here we have to put the result PHP code generated from the token.
  2429. );
  2430. // Find out, what it is and process it.
  2431. switch($token)
  2432. {
  2433. case '[':
  2434. // This code checks, whether the token is properly used. We have to assign it to one of the token groups.
  2435. if(!($state['next'] & self::OP_SQ_BRACKET))
  2436. {
  2437. throw new Opt_Expression_Exception('OP_SQ_BRACKET', $token, $expr);
  2438. }
  2439. $result[] = '[';
  2440. $state['tu'] = self::OP_SQ_BRACKET_E;
  2441. $state['next'] = self::OP_TU;
  2442. $state['step'] = self::OP_VARIABLE;
  2443. continue;
  2444. case ']':
  2445. if(!($state['next'] & self::OP_SQ_BRACKET_E))
  2446. {
  2447. throw new Opt_Expression_Exception('OP_SQ_BRACKET_E', $token, $expr);
  2448. }
  2449. $current['token'] = $state['step'];
  2450. $current['result'] = ']';
  2451. $state['step'] = 0;
  2452. // This is the way we mark, what tokens can occur next.
  2453. $state['next'] = self::OP_OPERATOR | self::OP_NULL | self::OP_SQ_BRACKET;
  2454. if($state['clone'] == 1)
  2455. {
  2456. $state['next'] = self::OP_NULL | self::OP_SQ_BRACKET;
  2457. }
  2458. break;
  2459. // These tokens are invalid and must produce an error
  2460. case '\'':
  2461. case '"':
  2462. case '{':
  2463. case '}':
  2464. throw new Opt_Expression_Exception('OP_CURLY_BRACKET', $token, $expr);
  2465. break;
  2466. // Text operators.
  2467. case 'add':
  2468. case 'sub':
  2469. case 'mul':
  2470. case 'div':
  2471. case 'mod':
  2472. case 'shl':
  2473. case 'shr':
  2474. case 'eq':
  2475. case 'neq':
  2476. case 'eqt':
  2477. case 'neqt':
  2478. case 'ne':
  2479. case 'net':
  2480. case 'lt':
  2481. case 'le':
  2482. case 'lte':
  2483. case 'gt':
  2484. case 'gte':
  2485. case 'ge':
  2486. // These guys can be also method names, if in proper context
  2487. if($previous['token'] == self::OP_CALL)
  2488. {
  2489. $this->_compileIdentifier($token, $previous['token'], $previous['result'],
  2490. isset($tokens[$i+1]) ? $tokens[$i+1] : null, $operatorSet, $expr, $current, $state);
  2491. break;
  2492. }
  2493. case 'and':
  2494. case 'or':
  2495. case 'xor':
  2496. $this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);
  2497. // And these three ones - only strings.
  2498. if($state['next'] & self::OP_STRING)
  2499. {
  2500. $current['result'] = '\''.$token.'\'';
  2501. $current['token'] = self::OP_STRING;
  2502. $state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
  2503. }
  2504. else
  2505. {
  2506. if(!($state['next'] & self::OP_OPERATOR))
  2507. {
  2508. throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
  2509. }
  2510. $current['result'] = $wordOperators[$token];
  2511. $current['token'] = self::OP_OPERATOR;
  2512. $state['next'] = $valueSet;
  2513. $state['preop'] = false;
  2514. }
  2515. $state['variable'] = false;
  2516. break;
  2517. case 'not':
  2518. if(!($state['next'] & self::OP_PRE_OPERATOR))
  2519. {
  2520. throw new Opt_Expression_Exception('OP_PRE_OPERATOR', $token, $expr);
  2521. }
  2522. $current['token'] = self::OP_PRE_OPERATOR;
  2523. $current['result'] = $wordOperators[$token];
  2524. $state['next'] = $valueSet;
  2525. $state['variable'] = false;
  2526. break;
  2527. case 'new':
  2528. case 'clone':
  2529. // These operators are active only if the directive advancedOOP is true.
  2530. if(!$this->_tpl->advancedOOP)
  2531. {
  2532. throw new Opt_ExpressionOptionDisabled_Exception($token, 'security reasons');
  2533. }
  2534. if(!($state['next'] & self::OP_OBJMAN))
  2535. {
  2536. throw new Opt_Expression_Exception('OP_OBJMAN', $token, $expr);
  2537. }
  2538. $current['result'] = $token.' ';
  2539. $current['token'] = self::OP_OBJMAN;
  2540. $state['next'] = ($token == 'new' ? self::OP_IDENTIFIER : self::OP_BLOCK);
  2541. $state['clone'] = 1;
  2542. $state['variable'] = false;
  2543. break;
  2544. case 'is':
  2545. if($state['next'] & self::OP_STRING)
  2546. {
  2547. $current['result'] = '\''.$token.'\'';
  2548. $state['next'] = $operatorSet | self::OP_SQ_BRACKET_E | self::OP_TU;
  2549. break;
  2550. }
  2551. case '=':
  2552. if(!$allowAssignment)
  2553. {
  2554. throw new Opt_ExpressionOptionDisabled_Exception('Assignments', 'compiler requirements');
  2555. }
  2556. // We have to assign the data to the variable or object field.
  2557. if(($previous['token'] == self::OP_VARIABLE || $previous['token'] == self::OP_FIELD) && !$state['oper'] && $previous['token'] != self::OP_LANGUAGE_VAR)
  2558. {
  2559. $current['result'] = '';
  2560. $current['token'] = self::OP_ASSIGN;
  2561. $state['variable'] = false;
  2562. $state['next'] = self::OP_TU;
  2563. $state['tu'] = self::OP_NULL;
  2564. $assign = true;
  2565. }
  2566. else
  2567. {
  2568. throw new Opt_Expression_Exception('OP_ASSIGN', $token, $expr);
  2569. }
  2570. break;
  2571. case '!==':
  2572. case '==':
  2573. case '===':
  2574. case '!=':
  2575. case '+':
  2576. case '*':
  2577. case '/':
  2578. case '%':
  2579. if(!($state['next'] & self::OP_OPERATOR))
  2580. {
  2581. throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
  2582. }
  2583. $this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);
  2584. $current['result'] = $token;
  2585. $state['next'] = $valueSet;
  2586. $state['oper'] = true;
  2587. $state['preop'] = false;
  2588. $state['variable'] = false;
  2589. break;
  2590. case '-':
  2591. if($state['next'] & self::OP_OPERATOR)
  2592. {
  2593. $this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);
  2594. $current['result'] = $token;
  2595. $state['oper'] = true;
  2596. $state['next'] = $valueSet;
  2597. $state['preop'] = false;
  2598. }
  2599. elseif($state['next'] & self::OP_NUMBER | self::OP_VARIABLE | self::OP_IDENTIFIER)
  2600. {
  2601. $current['result'] = $token;
  2602. $state['next'] = self::OP_NUMBER | self::OP_VARIABLE | self::OP_IDENTIFIER;
  2603. }
  2604. else
  2605. {
  2606. throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
  2607. }
  2608. $state['variable'] = false;
  2609. break;
  2610. case '~':
  2611. if(!($state['next'] & self::OP_OPERATOR))
  2612. {
  2613. throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
  2614. }
  2615. $current['result'] = '.';
  2616. $state['next'] = $valueSet;
  2617. $state['oper'] = true;
  2618. $state['preop'] = false;
  2619. $state['variable'] = false;
  2620. break;
  2621. case '++':
  2622. case '--':
  2623. $current['token'] = self::OP_PRE_OPERATOR;
  2624. if(!($state['next'] & self::OP_PRE_OPERATOR))
  2625. {
  2626. $current['token'] = self::OP_POST_OPERATOR;
  2627. if(!($state['next'] & self::OP_POST_OPERATOR))
  2628. {
  2629. throw new Opt_Expression_Exception('OP_POST_OPERATOR', $token, $expr);
  2630. }
  2631. else
  2632. {
  2633. $state['next'] = self::OP_OPERATOR | self::OP_NULL;
  2634. }
  2635. }
  2636. else
  2637. {
  2638. $state['next'] = self::OP_VARIABLE | self::OP_LANGUAGE_VAR | self::OP_NUMBER;
  2639. $state['preop'] = true;
  2640. }
  2641. $state['oper'] = true;
  2642. $state['variable'] = false;
  2643. $current['result'] = $token;
  2644. break;
  2645. case '!':
  2646. if(!($state['next'] & self::OP_PRE_OPERATOR))
  2647. {
  2648. throw new Opt_Expression_Exception('OP_PRE_OPERATOR', $token, $expr);
  2649. }
  2650. $current['result'] = $token;
  2651. $current['token'] = self::OP_PRE_OPERATOR;
  2652. $state['variable'] = false;
  2653. break;
  2654. case 'null':
  2655. case 'false':
  2656. case 'true':
  2657. // These special values are treated as numbers by the compiler.
  2658. if(!($state['next'] & self::OP_NUMBER))
  2659. {
  2660. throw new Opt_Expression_Exception('OP_NUMBER', $token, $expr);
  2661. }
  2662. $current['token'] = self::OP_NUMBER;
  2663. $current['result'] = $token;
  2664. $state['next'] = $operatorSet;
  2665. break;
  2666. case '.':
  2667. throw new Opt_Expression_Exception('.', $token, $expr);
  2668. break;
  2669. case '::':
  2670. if(!($state['next'] & self::OP_CALL))
  2671. {
  2672. throw new Opt_Expression_Exception('OP_CALL', $token, $expr);
  2673. }
  2674. if(!$this->_tpl->basicOOP)
  2675. {
  2676. throw new Opt_NotSupported_Exception('object-oriented programming', 'disabled');
  2677. }
  2678. // OPT decides from the context, whether "::" means a static
  2679. // or dynamic call.
  2680. if($previous['token'] == self::OP_CLASS)
  2681. {
  2682. $current['result'] = '::';
  2683. $state['call'] = 0;
  2684. }
  2685. else
  2686. {
  2687. $current['result'] = '->';
  2688. }
  2689. $current['token'] = self::OP_CALL;
  2690. $state['next'] = self::OP_IDENTIFIER;
  2691. break;
  2692. case '(':
  2693. // Check, if the parenhesis begins a function/method argument list
  2694. if($previous['token'] == self::OP_METHOD || $previous['token'] == self::OP_FUNCTION || $previous['token'] == self::OP_CLASS)
  2695. {
  2696. // Yes, this is a function call, so we need to find its arguments.
  2697. $args = array();
  2698. for($j = $i + 1; $j < $to && $tokens[$j] != ')'; $j++)
  2699. {
  2700. if($tokens[$j][0] == $chr)
  2701. {
  2702. $args[] = $tokens[$j];
  2703. }
  2704. elseif($tokens[$j] != ',')
  2705. {
  2706. throw new Opt_Expression_Exception('OP_UNKNOWN', $tokens[$j], $expr);
  2707. }
  2708. }
  2709. $argNum = sizeof($args);
  2710. // Optionally, change the argument order
  2711. if(!is_null($state['rev']))
  2712. {
  2713. $this->_reverseArgs($args, $state['rev'], $state['function']);
  2714. $state['rev'] = null;
  2715. $argNum = sizeof($args);
  2716. }
  2717. // Put the parenthesis to the compiled token list.
  2718. $result[] = '(';
  2719. // If we have a call of the assign() function, we need to store the
  2720. // number of the translation unit in the _translationConversion field.
  2721. // This will allow the language variable compiler to notice that here
  2722. // we should have a language call that must be treated in a bit different
  2723. // way.
  2724. if($argNum > 0 && $state['assign_func'])
  2725. {
  2726. $this->_translationConversion = (int)trim($args[0], $chr);
  2727. }
  2728. // Build the argument list.
  2729. for($k = 0; $k < $argNum; $k++)
  2730. {
  2731. $result[] = $args[$k];
  2732. if($k < $argNum - 1)
  2733. {
  2734. $result[] = ',';
  2735. }
  2736. }
  2737. $i = $j-1;
  2738. $state['next'] = self::OP_BRACKET_E;
  2739. $state['step'] = $previous['token'];
  2740. continue;
  2741. }
  2742. else
  2743. {
  2744. if(!($state['next'] & self::OP_BRACKET))
  2745. {
  2746. throw new Opt_Expression_Exception('OP_BRACKET', $token, $expr);
  2747. }
  2748. $result[] = '(';
  2749. $state['tu'] = self::OP_BRACKET_E;
  2750. $state['next'] = self::OP_TU;
  2751. $state['step'] = self::OP_VARIABLE;
  2752. }
  2753. break;
  2754. case ')':
  2755. if($state['step'] == 0)
  2756. {
  2757. throw new Opt_Expression_Exception('OP_BRACKET', $token, $expr);
  2758. }
  2759. else
  2760. {
  2761. if(!($state['next'] & self::OP_BRACKET_E))
  2762. {
  2763. throw new Opt_Expression_Exception('OP_BRACKET_E', $token, $expr);
  2764. }
  2765. $current['token'] = $state['step'];
  2766. $current['result'] = ')';
  2767. $state['step'] = 0;
  2768. $state['next'] = self::OP_OPERATOR | self::OP_NULL | self::OP_CALL;
  2769. if($state['clone'] == 1)
  2770. {
  2771. $state['next'] = self::OP_NULL | self::OP_CALL;
  2772. }
  2773. }
  2774. break;
  2775. default:
  2776. if($token[0] == $chr)
  2777. {
  2778. // We've found another translation unit.
  2779. if(!($state['next'] & self::OP_TU))
  2780. {
  2781. throw new Opt_Expression_Exception('OP_TU', 'Translation unit #'.ltrim($token, $chr), $expr);
  2782. }
  2783. if($previous['token'] != self::OP_ASSIGN)
  2784. {
  2785. $result[] = $token;
  2786. }
  2787. $state['next'] = $state['tu'];
  2788. }
  2789. elseif(preg_match('/^'.$this->_rVariable.'$/', $token))
  2790. {
  2791. // Variable call.
  2792. if(!($state['next'] & self::OP_VARIABLE))
  2793. {
  2794. throw new Opt_Expression_Exception('OP_VARIABLE', $token, $expr);
  2795. }
  2796. // We do the first character test manually, because
  2797. // in regular expression the parser would receive too much rubbish.
  2798. if(!ctype_alpha($token[1]) && $token[1] != '_')
  2799. {
  2800. throw new Opt_Expression_Exception('OP_VARIABLE', $token, $expr);
  2801. }
  2802. // Moreover, we need to know the future (assignments)
  2803. $assignment = null;
  2804. if(isset($tokens[$i+1]) && ($tokens[$i+1] == '=' || $tokens[$i+1] == 'is'))
  2805. {
  2806. $assignment = $tokens[$i+2];
  2807. }
  2808. $out = $this->_compileVariable($token, $assignment);
  2809. if(is_array($out))
  2810. {
  2811. foreach($out as $t)
  2812. {
  2813. $result[] = $t;
  2814. }
  2815. $current['result'] = '';
  2816. $current['token'] = self::OP_VARIABLE;
  2817. }
  2818. else
  2819. {
  2820. $current['result'] = $out;
  2821. $current['token'] = self::OP_VARIABLE;
  2822. }
  2823. if(is_null($state['variable']))
  2824. {
  2825. $state['variable'] = true;
  2826. }
  2827. // Hmmm... and what is the purpose of this IF? Seriously, I forgot.
  2828. // So better do not touch it; it must have been very important.
  2829. if($state['clone'] == 1)
  2830. {
  2831. $state['next'] = self::OP_SQ_BRACKET | self::OP_CALL | self::OP_NULL;
  2832. }
  2833. else
  2834. {
  2835. $state['next'] = $operatorSet | self::OP_SQ_BRACKET | self::OP_CALL;
  2836. }
  2837. }
  2838. elseif(preg_match('/^'.$this->_rLanguageVarExtract.'$/', $token, $found))
  2839. {
  2840. // Extracting the language var.
  2841. if(!($state['next'] & self::OP_LANGUAGE_VAR))
  2842. {
  2843. throw new Opt_Expression_Exception('OP_LANGUAGE_VAR', $token, $expr);
  2844. }
  2845. $current['result'] = $this->_compileLanguageVar($found[1], $found[2], $tu);
  2846. $current['token'] = self::OP_LANGUAGE_VAR;
  2847. $state['next'] = $operatorSet;
  2848. }
  2849. elseif(preg_match('/^'.$this->_rDecimalNumber.'$/', $token))
  2850. {
  2851. // Handling the decimal numbers.
  2852. if(!($state['next'] & self::OP_NUMBER))
  2853. {
  2854. throw new Opt_Expression_Exception('OP_NUMBER', $token, $expr);
  2855. }
  2856. $current['result'] = $token;
  2857. $state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
  2858. }
  2859. elseif(preg_match('/^'.$this->_rHexadecimalNumber.'$/', $token))
  2860. {
  2861. // Hexadecimal, too.
  2862. if(!($state['next'] & self::OP_NUMBER))
  2863. {
  2864. throw new Opt_Expression_Exception('OP_NUMBER', $token, $expr);
  2865. }
  2866. $current['result'] = $token;
  2867. $state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
  2868. }
  2869. elseif(preg_match('/^'.$this->_rSingleQuoteString.'$/', $token))
  2870. {
  2871. if(!($state['next'] & self::OP_STRING))
  2872. {
  2873. throw new Opt_Expression_Exception('OP_STRING', $token, $expr);
  2874. }
  2875. $current['result'] = $this->_compileString($token);
  2876. $state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
  2877. }
  2878. elseif(preg_match('/^'.$this->_rBacktickString.'$/', $token))
  2879. {
  2880. if(!($state['next'] & self::OP_STRING))
  2881. {
  2882. throw new Opt_Expression_Exception('OP_STRING', $token, $expr);
  2883. }
  2884. $current['result'] = $this->_compileString($token);
  2885. $state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
  2886. }
  2887. elseif(preg_match('/^'.$this->_rIdentifier.'$/', $token))
  2888. {
  2889. $this->_compileIdentifier($token, $previous['token'], $previous['result'],
  2890. isset($tokens[$i+1]) ? $tokens[$i+1] : null, $operatorSet, $expr, $current, $state);
  2891. }
  2892. }
  2893. $previous = $current;
  2894. if($current['result'] != '')
  2895. {
  2896. $result[] = $current['result'];
  2897. }
  2898. }
  2899. // Finally, test if the pre- operators have been used properly.
  2900. $this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);
  2901. // And if we are allowed to finish here...
  2902. if(!($state['next'] & self::OP_NULL))
  2903. {
  2904. throw new Opt_Expression_Exception('OP_NULL', $token, $expr);
  2905. }
  2906. // TODO: For variable detection: check also class/object fields!
  2907. return array($result, $assign, $state['variable']);
  2908. } // end _compileExpression();
  2909. /**
  2910. * An utility function that allows to test the preincrementation
  2911. * operators, if they are used in the right place. In case of
  2912. * problems, it generates an exception.
  2913. *
  2914. * @internal
  2915. * @param Int $previous The previous token type.
  2916. * @param Boolean $state The state of the "preop" expression parser flag.
  2917. * @param String $token The current token provided for debug purposes.
  2918. * @param String &$expr The reference to the parsed expression for debug purposes.
  2919. */
  2920. protected function _testPreOperators($previous, $state, &$token, &$expr)
  2921. {
  2922. if(($previous == self::OP_METHOD || $previous == self::OP_FUNCTION || $previous == self::OP_EXPRESSION) && $state)
  2923. {
  2924. // Invalid use of prefix operators!
  2925. throw new Opt_Expression_Exception('OP_PRE_OPERATOR', $token, $expr);
  2926. }
  2927. } // end _testPreOperators();
  2928. /**
  2929. * Compiles the template variable into the PHP code. It can be
  2930. * generated in two contexts: read and save. The method supports
  2931. * all the special variables, local template variables and
  2932. * chooses the correct data formats. Moreover, it provides a
  2933. * build-in support for sections.
  2934. *
  2935. * @internal
  2936. * @param String $name Variable call
  2937. * @param String $newValue Null or the new value to assign
  2938. * @return String The output PHP code.
  2939. */
  2940. protected function _compileVariable($name, $saveContext = null)
  2941. {
  2942. $value = substr($name, 1, strlen($name) - 1);
  2943. $result = '';
  2944. if(strpos($value, '.') !== FALSE)
  2945. {
  2946. $ns = explode('.', $value);
  2947. }
  2948. else
  2949. {
  2950. $ns = array(0 => $value);
  2951. }
  2952. if($name[0] == '@')
  2953. {
  2954. // The instruction may wish to handle this variable somehow differently.
  2955. if(($to = $this->convert('##var_'.$ns[0])) == '##var_'.$ns[0])
  2956. {
  2957. $result = 'self::$_vars'; // Standard handler
  2958. }
  2959. else
  2960. {
  2961. $result = $to; // Programmer-defined handler
  2962. unset($ns[0]); // We assume that the variable name is already included into the handler.
  2963. }
  2964. // Link the rest of the array call.
  2965. foreach($ns as $item)
  2966. {
  2967. if(ctype_digit($item))
  2968. {
  2969. $result .= '['.$item.']';
  2970. }
  2971. else
  2972. {
  2973. $result .= '[\''.$item.'\']';
  2974. }
  2975. }
  2976. if($saveContext !== null)
  2977. {
  2978. return array($result.'=', $saveContext);
  2979. }
  2980. return $result;
  2981. }
  2982. else
  2983. {
  2984. /*
  2985. * This is the variable scanner that parses things like "$var.foo.bar.joe".
  2986. * Each segment of the name can be parsed in different format, depending on
  2987. * the programmer settings. Moreover, it recognizes the special calls, like "opt"/"system"
  2988. * or section element calls.
  2989. */
  2990. $path = '';
  2991. $previous = null;
  2992. $code = '';
  2993. $count = sizeof($ns);
  2994. $state = array(
  2995. 'access' => $this->_tpl->variableAccess,
  2996. 'section' => null,
  2997. 'first' => false
  2998. );
  2999. // Check the first element for special keywords.
  3000. switch($ns[0])
  3001. {
  3002. case 'opt':
  3003. case 'sys':
  3004. case 'system':
  3005. if($saveContext !== null)
  3006. {
  3007. throw new Opt_AssignNotSupported_Exception($name);
  3008. }
  3009. return $this->_compileSys($ns);
  3010. case 'this':
  3011. $state['access'] = Opt_Class::ACCESS_LOCAL;
  3012. unset($ns[0]);
  3013. break;
  3014. case 'global':
  3015. $state['access'] = Opt_Class::ACCESS_GLOBAL;
  3016. unset($ns[0]);
  3017. break;
  3018. }
  3019. // Scan the rest of the name
  3020. $final = sizeof($ns) - 1;
  3021. foreach($ns as $id => $item)
  3022. {
  3023. $previous = $path;
  3024. if($path == '')
  3025. {
  3026. // Parsing the first element. First, check the conversions.
  3027. if(($to = $this->convert('##simplevar_'.$item)) != '##simplevar_'.$item)
  3028. {
  3029. $item = $to;
  3030. }
  3031. $path = $item;
  3032. $state['first'] = true;
  3033. }
  3034. else
  3035. {
  3036. // Parsing one of the later elements
  3037. $path .= '.'.$item;
  3038. $state['first'] = false;
  3039. }
  3040. // Processing sections
  3041. if(!is_null($this->isProcessor('section')))
  3042. {
  3043. if(is_null($state['section']))
  3044. {
  3045. // Check if any section with the specified name exists.
  3046. $proc = $this->processor('section');
  3047. $sectionName = $this->convert($item);
  3048. if(!is_null($section = $proc->getSection($sectionName)))
  3049. {
  3050. $path = $sectionName;
  3051. $state['section'] = $section;
  3052. if($id == $count - 1)
  3053. {
  3054. // This is the last name element.
  3055. if($saveContext !== null)
  3056. {
  3057. if(!$section['format']->property('section:itemAssign'))
  3058. {
  3059. throw new Opt_AssignNotSupported_Exception($name);
  3060. }
  3061. $format->assign('value', $saveContext);
  3062. return $section['format']->get('section:itemAssign');
  3063. }
  3064. return $section['format']->get('section:item');
  3065. }
  3066. continue;
  3067. }
  3068. }
  3069. else
  3070. {
  3071. // The section has been found, we need to process the item.
  3072. $state['section']['format']->assign('item', $item);
  3073. if($saveContext !== null && $id == $final)
  3074. {
  3075. if(!$state['section']['format']->property('section:variableAssign'))
  3076. {
  3077. throw new Opt_AssignNotSupported_Exception($name);
  3078. }
  3079. $state['section']['format']->assign('value', $saveContext);
  3080. $code = $state['section']['format']->get('section:variableAssign');
  3081. }
  3082. else
  3083. {
  3084. $code = $state['section']['format']->get('section:variable');
  3085. }
  3086. $state['section'] = null;
  3087. continue;
  3088. }
  3089. }
  3090. // Now, the normal variables
  3091. if($state['first'])
  3092. {
  3093. if($state['access'] == Opt_Class::ACCESS_GLOBAL)
  3094. {
  3095. $format = $this->getFormat('global.'.$path, true);
  3096. }
  3097. else
  3098. {
  3099. $format = $this->getFormat($path, true);
  3100. }
  3101. if(!$format->supports('variable'))
  3102. {
  3103. throw new Opt_FormatNotSupported_Exception($format->getName(), 'variable');
  3104. }
  3105. $format->assign('access', $state['access']);
  3106. if($format->property('variable:captureAll'))
  3107. {
  3108. // With this property, the data format may capture
  3109. // the whole namespace and process it in a more complex
  3110. // way
  3111. $format->assign('items', $ns);
  3112. if($saveContext !== null)
  3113. {
  3114. if(!$format->property('variable:assign'))
  3115. {
  3116. throw new Opt_AssignNotSupported_Exception($name);
  3117. }
  3118. $format->assign('value', $saveContext);
  3119. $code = $format->get('variable:captureAssign');
  3120. }
  3121. else
  3122. {
  3123. $code = $format->get('variable:capture');
  3124. }
  3125. break;
  3126. }
  3127. else
  3128. {
  3129. // An ordinary call - the format captures only
  3130. // the first item, the others are processed
  3131. // by different "item" formats.
  3132. $format->assign('item', $item);
  3133. if($final == $id && $saveContext !== null)
  3134. {
  3135. if(!$format->property('variable:assign'))
  3136. {
  3137. throw new Opt_AssignNotSupported_Exception($name);
  3138. }
  3139. $format->assign('value', $saveContext);
  3140. $code = $format->get('variable:assign');
  3141. }
  3142. else
  3143. {
  3144. $code = $format->get('variable:main');
  3145. }
  3146. }
  3147. }
  3148. else
  3149. {
  3150. // The subitems are processed with the upper-item format
  3151. if($state['access'] == Opt_Class::ACCESS_GLOBAL)
  3152. {
  3153. $format = $this->getFormat('global.'.$previous, true);
  3154. }
  3155. else
  3156. {
  3157. $format = $this->getFormat($previous, true);
  3158. }
  3159. if(!$format->supports('item'))
  3160. {
  3161. throw new Opt_FormatNotSupported_Exception($format->getName(), 'item');
  3162. }
  3163. if($final == $id && $saveContext !== null)
  3164. {
  3165. if(!$format->property('item:assign'))
  3166. {
  3167. throw new Opt_AssignNotSupported_Exception($name);
  3168. }
  3169. $format->assign('item', $item);
  3170. $format->assign('value', $saveContext);
  3171. $code .= $format->get('item:assign');
  3172. }
  3173. else
  3174. {
  3175. $format->assign('item', $item);
  3176. $code .= $format->get('item:item');
  3177. }
  3178. }
  3179. }
  3180. if($saveContext !== null)
  3181. {
  3182. $out = explode($saveContext, $code);
  3183. if(sizeof($out) != 2)
  3184. {
  3185. return $code;
  3186. }
  3187. return array(0 => $out[0], $saveContext, $out[1]);
  3188. }
  3189. return $code;
  3190. }
  3191. } // end _compileVariable();
  3192. /**
  3193. * Compiles the call to the language variable into the PHP code.
  3194. *
  3195. * @param String $group Group name
  3196. * @param String $id Message identifier name within a group
  3197. * @param String $tu The ID of the current translation unit for handling the assign() function properly.
  3198. * @return String The output PHP code.
  3199. */
  3200. protected function _compileLanguageVar($group, $id, $tu)
  3201. {
  3202. if(is_null($this->_tf))
  3203. {
  3204. throw new Opl_NoTranslationInterface_Exception('OPT template compiler');
  3205. }
  3206. if($tu === $this->_translationConversion)
  3207. {
  3208. $this->_translationConversion = null;
  3209. return '\''.$group.'\',\''.$id.'\'';
  3210. }
  3211. return '$this->_tf->_(\''.$group.'\',\''.$id.'\')';
  3212. } // end _compileLanguageVar();
  3213. /**
  3214. * Compiles the special $sys variable to PHP code.
  3215. *
  3216. * @param Array $ns The $sys call splitted into array.
  3217. * @return String The output PHP code.
  3218. */
  3219. protected function _compileSys(Array $ns)
  3220. {
  3221. switch($ns[1])
  3222. {
  3223. case 'version':
  3224. return '\''.Opt_Class::VERSION.'\'';
  3225. case 'const':
  3226. return 'constant(\''.$ns[2].'\')';
  3227. default:
  3228. if(!is_null($this->isProcessor($ns[1])))
  3229. {
  3230. return $this->processor($ns[1])->processSystemVar($ns);
  3231. }
  3232. throw new Opt_SysVariableUnknown_Exception('$'.implode('.', $ns));
  3233. }
  3234. } // end _compileSys();
  3235. /**
  3236. * Compiles the string call in the expression to a suitable PHP source code.
  3237. *
  3238. * @internal
  3239. * @param String $str The "string" string (with the delimiting characters)
  3240. * @return String The output PHP code.
  3241. */
  3242. protected function _compileString($str)
  3243. {
  3244. // TODO: Fix
  3245. // COMMENT: Fix what?
  3246. switch($str[0])
  3247. {
  3248. case '\'':
  3249. return $str;
  3250. case '`':
  3251. if(is_null($this->_tpl->backticks))
  3252. {
  3253. throw new Opt_NotSupported_Exception('backticks', 'not configured');
  3254. }
  3255. elseif(is_string($this->_tpl->backticks))
  3256. {
  3257. // A redirect to a function
  3258. return $this->_tpl->backticks.'(\''.str_replace('\'', '\\\'', stripslashes(substr($str, 1, strlen($str) - 2))).'\')';
  3259. }
  3260. elseif(is_array($this->_tpl->backticks) && is_object($this->_tpl->backticks[0]))
  3261. {
  3262. // A redirect to an object method
  3263. return '$this->_tpl->backticks[0]->'.$this->_tpl->backticks[1].'(\''.str_replace('\'', '\\\'', stripslashes(substr($str, 1, strlen($str) - 2))).'\')';
  3264. }
  3265. else
  3266. {
  3267. throw new Opt_InvalidCallback_Exception('backticks');
  3268. }
  3269. default:
  3270. return '\''.$str.'\'';
  3271. }
  3272. } // end _compileString();
  3273. /**
  3274. * Compiles the specified identifier encountered in the expression
  3275. * to the PHP code.
  3276. *
  3277. * @internal
  3278. * @param String $token The encountered token.
  3279. * @param Int $previous Previous token
  3280. * @param String $pt Used for OOP parsing to determine whether we have a static call.
  3281. * @param String $next The next token in the list
  3282. * @param Int $operatorSet The flag of allowed opcodes at this position.
  3283. * @param String &$expr The current expression (for debug purposes)
  3284. * @param Array &$current Reference to the current token information
  3285. * @param Array &$state Reference to the parser state flags.
  3286. */
  3287. protected function _compileIdentifier($token, $previous, $pt, $next, $operatorSet, &$expr, &$current, &$state)
  3288. {
  3289. if($previous == self::OP_OBJMAN)
  3290. {
  3291. // Class constructor call
  3292. if(isset($this->_classes[$token]) && $this->_tpl->basicOOP)
  3293. {
  3294. $current['result'] = $this->_classes[$token];
  3295. $current['token'] = self::OP_CLASS;
  3296. $state['next'] = self::OP_BRACKET | self::OP_NULL;
  3297. if($next == '(')
  3298. {
  3299. $state['func'] = 1;
  3300. }
  3301. }
  3302. else
  3303. {
  3304. throw new Opt_ItemNotAllowed_Exception('Class', $token);
  3305. }
  3306. }
  3307. elseif($next == '(')
  3308. {
  3309. // Function/method call
  3310. if($previous == self::OP_CALL)
  3311. {
  3312. $current['result'] = $token;
  3313. $current['token'] = self::OP_METHOD;
  3314. $state['next'] = self::OP_BRACKET;
  3315. $state['func'] = 1;
  3316. }
  3317. elseif(isset($this->_functions[$token]))
  3318. {
  3319. $name = $this->_functions[$token];
  3320. if($name[0] == '#')
  3321. {
  3322. $pos = strpos($name, '#', 1);
  3323. if($pos === false)
  3324. {
  3325. throw new Opt_InvalidArgumentFormat_Exception($name, $token);
  3326. }
  3327. $state['rev'] = substr($name, 1, $pos - 1);
  3328. $name = substr($name, $pos+1, strlen($name));
  3329. }
  3330. $current['result'] = $name;
  3331. $current['token'] = self::OP_FUNCTION;
  3332. $state['next'] = self::OP_BRACKET;
  3333. $state['function'] = $token;
  3334. }
  3335. elseif($token == 'assign')
  3336. {
  3337. $current['result'] = '$this->_tf->assign';
  3338. $current['token'] = self::OP_FUNCTION;
  3339. $state['next'] = self::OP_BRACKET;
  3340. $state['assign_func'] = true;
  3341. $state['function'] = $token;
  3342. }
  3343. else
  3344. {
  3345. throw new Opt_ItemNotAllowed_Exception('Function', $token);
  3346. }
  3347. }
  3348. elseif($previous == self::OP_CALL)
  3349. {
  3350. // Class/object field call, check whether static or not.
  3351. $current['result'] = ($pt == '::' ? '$'.$token : $token);
  3352. $current['token'] = self::OP_FIELD;
  3353. $state['next'] = $operatorSet | self::OP_SQ_BRACKET | self::OP_CALL;
  3354. if($state['clone'] == 1)
  3355. {
  3356. $state['next'] = self::OP_SQ_BRACKET | self::OP_CALL | self::OP_NULL;
  3357. }
  3358. }
  3359. elseif($next == '::')
  3360. {
  3361. // Static class call
  3362. if(isset($this->_classes[$token]))
  3363. {
  3364. $current['result'] = $this->_classes[$token];
  3365. $current['token'] = self::OP_CLASS;
  3366. $state['next'] = self::OP_CALL;
  3367. }
  3368. else
  3369. {
  3370. throw new Opt_ItemNotAllowed_Exception('Class', $token);
  3371. }
  3372. }
  3373. else
  3374. {
  3375. // An ending string.
  3376. if(!($state['next'] & self::OP_STRING))
  3377. {
  3378. throw new Opt_Expression_Exception('OP_STRING', $token, $expr);
  3379. }
  3380. $state['next'] = self::OP_NULL;
  3381. $current['token'] = self::OP_STRING;
  3382. $current['result'] = '\''.$token.'\'';
  3383. }
  3384. } // end _compileIdentifier();
  3385. /**
  3386. * Processes the argument order change functionality for function
  3387. * parsing in expressions.
  3388. *
  3389. * @internal
  3390. * @param Array &$args Reference to a list of function arguments.
  3391. * @param String $format The new order format code.
  3392. * @param String $function The function name provided for debugging purposes.
  3393. */
  3394. protected function _reverseArgs(&$args, $format, $function)
  3395. {
  3396. $codes = explode(',', $format);
  3397. $newArgs = array();
  3398. $i = 0;
  3399. foreach($codes as $code)
  3400. {
  3401. $data = explode(':', $code);
  3402. if(!isset($args[$i]))
  3403. {
  3404. if(!isset($data[1]))
  3405. {
  3406. throw new Opt_FunctionArgument_Exception($i, $function);
  3407. }
  3408. $newArgs[(int)$data[0]-1] = $data[1];
  3409. }
  3410. else
  3411. {
  3412. $newArgs[(int)$data[0]-1] = $args[$i];
  3413. }
  3414. $i++;
  3415. }
  3416. $args = $newArgs;
  3417. } // end _reverseArgs();
  3418. /**
  3419. * Smart special character replacement that leaves entities
  3420. * unmodified. Used by parseSpecialChars().
  3421. *
  3422. * @internal
  3423. * @param Array $text Matching string
  3424. * @return String Modified text
  3425. */
  3426. protected function _entitize($text)
  3427. {
  3428. switch($text[0])
  3429. {
  3430. case '&': return '&amp;';
  3431. case '>': return '&gt;';
  3432. case '<': return '&lt;';
  3433. case '"': return '&quot;';
  3434. default: return $text[0];
  3435. }
  3436. } // end _entitize();
  3437. /**
  3438. * Smart entity replacement that makes use of
  3439. *
  3440. * @internal
  3441. * @param Array $text Matching string
  3442. * @return String Modified text
  3443. */
  3444. protected function _decodeEntity($text)
  3445. {
  3446. switch($text[1])
  3447. {
  3448. case 'amp': return '&';
  3449. case 'quot': return '"';
  3450. case 'lt': return '<';
  3451. case 'gt': return '>';
  3452. case 'apos': return "'";
  3453. default:
  3454. if(isset($this->_entities[$text[1]]))
  3455. {
  3456. return $this->_entities[$text[1]];
  3457. }
  3458. if($text[1][0] == '#')
  3459. {
  3460. return html_entity_decode($text[0], ENT_COMPAT, $this->_tpl->charset);
  3461. }
  3462. elseif($this->_tpl->htmlEntities && $text[0] != ($result = html_entity_decode($text[0], ENT_COMPAT, $this->_tpl->charset)))
  3463. {
  3464. return $result;
  3465. }
  3466. throw new Opt_UnknownEntity_Exception(htmlspecialchars($text[0]));
  3467. }
  3468. } // end _entitize();
  3469. } // end Opt_Compiler_Class;