PageRenderTime 54ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/application/libraries/Lex/Parser.php

https://github.com/miky82/matriculas-bonfire
PHP | 741 lines | 413 code | 107 blank | 221 comment | 63 complexity | 656546045d526ae74f57d375e21df388 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * Part of the Lex Template Parser.
  4. *
  5. * @author Dan Horrigan
  6. * @license MIT License
  7. * @copyright 2011 - 2012 Dan Horrigan
  8. */
  9. //namespace Lex;
  10. class Lex_Parser
  11. {
  12. protected $allowPhp = false;
  13. protected $regexSetup = false;
  14. protected $scopeGlue = '.';
  15. protected $tagRegex = '';
  16. protected $cumulativeNoparse = false;
  17. protected $inCondition = false;
  18. protected $variableRegex = '';
  19. protected $variableLoopRegex = '';
  20. protected $variableTagRegex = '';
  21. protected $callbackTagRegex = '';
  22. protected $callbackLoopTagRegex = '';
  23. protected $noparseRegex = '';
  24. protected $conditionalRegex = '';
  25. protected $conditionalElseRegex = '';
  26. protected $conditionalEndRegex = '';
  27. protected $conditionalData = array();
  28. protected static $extractions = array(
  29. 'noparse' => array(),
  30. );
  31. protected static $data = null;
  32. protected static $callbackData = array();
  33. /**
  34. * The main Lex parser method. Essentially acts as dispatcher to
  35. * all of the helper parser methods.
  36. *
  37. * @param string $text Text to parse
  38. * @param array|object $data Array or object to use
  39. * @param mixed $callback Callback to use for Callback Tags
  40. * @return string
  41. */
  42. public function parse($text, $data = array(), $callback = false, $allowPhp = false)
  43. {
  44. $this->setupRegex();
  45. $this->allowPhp = $allowPhp;
  46. // Is this the first time parse() is called?
  47. if (self::$data === null) {
  48. // Let's store the local data array for later use.
  49. self::$data = $data;
  50. } else {
  51. // Let's merge the current data array with the local scope variables
  52. // So you can call local variables from within blocks.
  53. $data = array_merge(self::$data, $data);
  54. // Since this is not the first time parse() is called, it's most definately a callback,
  55. // let's store the current callback data with the the local data
  56. // so we can use it straight after a callback is called.
  57. self::$callbackData = $data;
  58. }
  59. // The parseConditionals method executes any PHP in the text, so clean it up.
  60. if (! $allowPhp) {
  61. $text = str_replace(array('<?', '?>'), array('&lt;?', '?&gt;'), $text);
  62. }
  63. $text = $this->parseComments($text);
  64. $text = $this->extractNoparse($text);
  65. $text = $this->extractLoopedTags($text, $data, $callback);
  66. // Order is important here. We parse conditionals first as to avoid
  67. // unnecessary code from being parsed and executed.
  68. $text = $this->parseConditionals($text, $data, $callback);
  69. $text = $this->injectExtractions($text, 'looped_tags');
  70. $text = $this->parseVariables($text, $data, $callback);
  71. $text = $this->injectExtractions($text, 'callback_blocks');
  72. if ($callback) {
  73. $text = $this->parseCallbackTags($text, $data, $callback);
  74. }
  75. // To ensure that {{ noparse }} is never parsed even during consecutive parse calls
  76. // set $cumulativeNoparse to true and use self::injectNoparse($text); immediately
  77. // before the final output is sent to the browser
  78. if (! $this->cumulativeNoparse) {
  79. $text = $this->injectExtractions($text);
  80. }
  81. return $text;
  82. }
  83. /**
  84. * Removes all of the comments from the text.
  85. *
  86. * @param string $text Text to remove comments from
  87. * @return string
  88. */
  89. public function parseComments($text)
  90. {
  91. $this->setupRegex();
  92. return preg_replace('/\{\{#.*?#\}\}/s', '', $text);
  93. }
  94. /**
  95. * Recursivly parses all of the variables in the given text and
  96. * returns the parsed text.
  97. *
  98. * @param string $text Text to parse
  99. * @param array|object $data Array or object to use
  100. * @return string
  101. */
  102. public function parseVariables($text, $data, $callback = null)
  103. {
  104. $this->setupRegex();
  105. /**
  106. * $data_matches[][0][0] is the raw data loop tag
  107. * $data_matches[][0][1] is the offset of raw data loop tag
  108. * $data_matches[][1][0] is the data variable (dot notated)
  109. * $data_matches[][1][1] is the offset of data variable
  110. * $data_matches[][2][0] is the content to be looped over
  111. * $data_matches[][2][1] is the offset of content to be looped over
  112. */
  113. if (preg_match_all($this->variableLoopRegex, $text, $data_matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE)) {
  114. foreach ($data_matches as $index => $match) {
  115. if ($loop_data = $this->getVariable($match[1][0], $data)) {
  116. $looped_text = '';
  117. foreach ($loop_data as $item_data) {
  118. $str = $this->extractLoopedTags($match[2][0], $item_data, $callback);
  119. $str = $this->parseConditionals($str, $item_data, $callback);
  120. $str = $this->injectExtractions($str, 'looped_tags');
  121. $str = $this->parseVariables($str, $item_data, $callback);
  122. if ($callback !== null) {
  123. $str = $this->parseCallbackTags($str, $item_data, $callback);
  124. }
  125. $looped_text .= $str;
  126. }
  127. $text = preg_replace('/'.preg_quote($match[0][0], '/').'/m', addcslashes($looped_text, '\\$'), $text, 1);
  128. } else { // It's a callback block.
  129. // Let's extract it so it doesn't conflict
  130. // with the local scope variables in the next step.
  131. $text = $this->createExtraction('callback_blocks', $match[0][0], $match[0][0], $text);
  132. }
  133. }
  134. }
  135. /**
  136. * $data_matches[0] is the raw data tag
  137. * $data_matches[1] is the data variable (dot notated)
  138. */
  139. if (preg_match_all($this->variableTagRegex, $text, $data_matches)) {
  140. foreach ($data_matches[1] as $index => $var) {
  141. if (($val = $this->getVariable($var, $data, '__lex_no_value__')) !== '__lex_no_value__') {
  142. $text = str_replace($data_matches[0][$index], $val, $text);
  143. }
  144. }
  145. }
  146. return $text;
  147. }
  148. /**
  149. * Parses all Callback tags, and sends them through the given $callback.
  150. *
  151. * @param string $text Text to parse
  152. * @param mixed $callback Callback to apply to each tag
  153. * @param bool $inConditional Whether we are in a conditional tag
  154. * @return string
  155. */
  156. public function parseCallbackTags($text, $data, $callback)
  157. {
  158. $this->setupRegex();
  159. $inCondition = $this->inCondition;
  160. if ($inCondition) {
  161. $regex = '/\{\s*('.$this->variableRegex.')(\s+.*?)?\s*\}/ms';
  162. } else {
  163. $regex = '/\{\{\s*('.$this->variableRegex.')(\s+.*?)?\s*(\/)?\}\}/ms';
  164. }
  165. /**
  166. * $match[0][0] is the raw tag
  167. * $match[0][1] is the offset of raw tag
  168. * $match[1][0] is the callback name
  169. * $match[1][1] is the offset of callback name
  170. * $match[2][0] is the parameters
  171. * $match[2][1] is the offset of parameters
  172. * $match[3][0] is the self closure
  173. * $match[3][1] is the offset of closure
  174. */
  175. while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE)) {
  176. $selfClosed = false;
  177. $parameters = array();
  178. $tag = $match[0][0];
  179. $start = $match[0][1];
  180. $name = $match[1][0];
  181. if (isset($match[2])) {
  182. $cb_data = $data;
  183. if ( !empty(self::$callbackData)) {
  184. $cb_data = array_merge(self::$callbackData, $data);
  185. }
  186. $raw_params = $this->injectExtractions($match[2][0], '__cond_str');
  187. $parameters = $this->parseParameters($raw_params, $cb_data, $callback);
  188. }
  189. if (isset($match[3])) {
  190. $selfClosed = true;
  191. }
  192. $content = '';
  193. $temp_text = substr($text, $start + strlen($tag));
  194. if (preg_match('/\{\{\s*\/'.preg_quote($name, '/').'\s*\}\}/m', $temp_text, $match, PREG_OFFSET_CAPTURE) && ! $selfClosed) {
  195. $content = substr($temp_text, 0, $match[0][1]);
  196. $tag .= $content.$match[0][0];
  197. // Is there a nested block under this one existing with the same name?
  198. $nested_regex = '/\{\{\s*('.preg_quote($name, '/').')(\s.*?)\}\}(.*?)\{\{\s*\/\1\s*\}\}/ms';
  199. if (preg_match($nested_regex, $content.$match[0][0], $nested_matches)) {
  200. $nested_content = preg_replace('/\{\{\s*\/'.preg_quote($name, '/').'\s*\}\}/m', '', $nested_matches[0]);
  201. $content = $this->createExtraction('nested_looped_tags', $nested_content, $nested_content, $content);
  202. }
  203. }
  204. $replacement = call_user_func_array($callback, array($name, $parameters, $content));
  205. $replacement = $this->parseRecursives($replacement, $content, $callback);
  206. if ($inCondition) {
  207. $replacement = $this->valueToLiteral($replacement);
  208. }
  209. $text = preg_replace('/'.preg_quote($tag, '/').'/m', addcslashes($replacement, '\\$'), $text, 1);
  210. $text = $this->injectExtractions($text, 'nested_looped_tags');
  211. }
  212. return $text;
  213. }
  214. /**
  215. * Parses all conditionals, then executes the conditionals.
  216. *
  217. * @param string $text Text to parse
  218. * @param mixed $data Data to use when executing conditionals
  219. * @param mixed $callback The callback to be used for tags
  220. * @return string
  221. */
  222. public function parseConditionals($text, $data, $callback)
  223. {
  224. $this->setupRegex();
  225. preg_match_all($this->conditionalRegex, $text, $matches, PREG_SET_ORDER);
  226. $this->conditionalData = $data;
  227. /**
  228. * $matches[][0] = Full Match
  229. * $matches[][1] = Either 'if', 'unless', 'elseif', 'elseunless'
  230. * $matches[][2] = Condition
  231. */
  232. foreach ($matches as $match) {
  233. $this->inCondition = true;
  234. $condition = $match[2];
  235. // Extract all literal string in the conditional to make it easier
  236. if (preg_match_all('/(["\']).*?(?<!\\\\)\1/', $condition, $str_matches)) {
  237. foreach ($str_matches[0] as $m) {
  238. $condition = $this->createExtraction('__cond_str', $m, $m, $condition);
  239. }
  240. }
  241. $condition = preg_replace($this->conditionalNotRegex, '$1!$2', $condition);
  242. if (preg_match_all($this->conditionalExistsRegex, $condition, $existsMatches, PREG_SET_ORDER)) {
  243. foreach ($existsMatches as $m) {
  244. $exists = 'true';
  245. if ($this->getVariable($m[2], $data, '__doesnt_exist__') === '__doesnt_exist__') {
  246. $exists = 'false';
  247. }
  248. $condition = $this->createExtraction('__cond_exists', $m[0], $m[1].$exists.$m[3], $condition);
  249. }
  250. }
  251. $condition = preg_replace_callback('/\b('.$this->variableRegex.')\b/', array($this, 'processConditionVar'), $condition);
  252. if ($callback) {
  253. $condition = preg_replace('/\b(?!\{\s*)('.$this->callbackNameRegex.')(?!\s+.*?\s*\})\b/', '{$1}', $condition);
  254. $condition = $this->parseCallbackTags($condition, $data, $callback);
  255. }
  256. // Re-extract the strings that have now been possibly added.
  257. if (preg_match_all('/(["\']).*?(?<!\\\\)\1/', $condition, $str_matches)) {
  258. foreach ($str_matches[0] as $m) {
  259. $condition = $this->createExtraction('__cond_str', $m, $m, $condition);
  260. }
  261. }
  262. // Re-process for variables, we trick processConditionVar so that it will return null
  263. $this->inCondition = false;
  264. $condition = preg_replace_callback('/\b('.$this->variableRegex.')\b/', array($this, 'processConditionVar'), $condition);
  265. $this->inCondition = true;
  266. // Re-inject any strings we extracted
  267. $condition = $this->injectExtractions($condition, '__cond_str');
  268. $condition = $this->injectExtractions($condition, '__cond_exists');
  269. $conditional = '<?php ';
  270. if ($match[1] == 'unless') {
  271. $conditional .= 'if ( ! ('.$condition.'))';
  272. } elseif ($match[1] == 'elseunless') {
  273. $conditional .= 'elseif ( ! ('.$condition.'))';
  274. } else {
  275. $conditional .= $match[1].' ('.$condition.')';
  276. }
  277. $conditional .= ': ?>';
  278. $text = preg_replace('/'.preg_quote($match[0], '/').'/m', addcslashes($conditional, '\\$'), $text, 1);
  279. }
  280. $text = preg_replace($this->conditionalElseRegex, '<?php else: ?>', $text);
  281. $text = preg_replace($this->conditionalEndRegex, '<?php endif; ?>', $text);
  282. $text = $this->parsePhp($text);
  283. $this->inCondition = false;
  284. return $text;
  285. }
  286. /**
  287. * Goes recursively through a callback tag with a passed child array.
  288. *
  289. * @param string $text - The replaced text after a callback.
  290. * @param string $orig_text - The original text, before a callback is called.
  291. * @param mixed $callback
  292. * @return string $text
  293. */
  294. public function parseRecursives($text, $orig_text, $callback)
  295. {
  296. // Is there a {{ *recursive [array_key]* }} tag here, let's loop through it.
  297. if (preg_match($this->recursiveRegex, $text, $match)) {
  298. $array_key = $match[1];
  299. $tag = $match[0];
  300. $next_tag = null;
  301. $children = self::$callbackData[$array_key];
  302. $child_count = count($children);
  303. $count = 1;
  304. // Is the array not multi-dimensional? Let's make it multi-dimensional.
  305. if ($child_count == count($children, COUNT_RECURSIVE)) {
  306. $children = array($children);
  307. $child_count = 1;
  308. }
  309. foreach ($children as $child) {
  310. $has_children = true;
  311. // If this is a object let's convert it to an array.
  312. is_array($child) OR $child = (array) $child;
  313. // Does this child not contain any children?
  314. // Let's set it as empty then to avoid any errors.
  315. if ( ! array_key_exists($array_key, $child)) {
  316. $child[$array_key] = array();
  317. $has_children = false;
  318. }
  319. $replacement = $this->parse($orig_text, $child, $callback, $this->allowPhp);
  320. // If this is the first loop we'll use $tag as reference, if not
  321. // we'll use the previous tag ($next_tag)
  322. $current_tag = ($next_tag !== null) ? $next_tag : $tag;
  323. // If this is the last loop set the next tag to be empty
  324. // otherwise hash it.
  325. $next_tag = ($count == $child_count) ? '' : md5($tag.$replacement);
  326. $text = str_replace($current_tag, $replacement.$next_tag, $text);
  327. if ($has_children) {
  328. $text = $this->parseRecursives($text, $orig_text, $callback);
  329. }
  330. $count++;
  331. }
  332. }
  333. return $text;
  334. }
  335. /**
  336. * Gets or sets the Scope Glue
  337. *
  338. * @param string|null $glue The Scope Glue
  339. * @return string
  340. */
  341. public function scopeGlue($glue = null)
  342. {
  343. if ($glue !== null) {
  344. $this->regexSetup = false;
  345. $this->scopeGlue = $glue;
  346. }
  347. return $this->scopeGlue;
  348. }
  349. /**
  350. * Sets the noparse style. Immediate or cumulative.
  351. *
  352. * @param bool $mode
  353. * @return void
  354. */
  355. public function cumulativeNoparse($mode)
  356. {
  357. $this->cumulativeNoparse = $mode;
  358. }
  359. /**
  360. * Injects noparse extractions.
  361. *
  362. * This is so that multiple parses can store noparse
  363. * extractions and all noparse can then be injected right
  364. * before data is displayed.
  365. *
  366. * @param string $text Text to inject into
  367. * @return string
  368. */
  369. public static function injectNoparse($text)
  370. {
  371. if (isset(self::$extractions['noparse'])) {
  372. foreach (self::$extractions['noparse'] AS $hash => $replacement) {
  373. if (strpos($text, "noparse_{$hash}") !== FALSE) {
  374. $text = str_replace("noparse_{$hash}", $replacement, $text);
  375. }
  376. }
  377. }
  378. return $text;
  379. }
  380. /**
  381. * This is used as a callback for the conditional parser. It takes a variable
  382. * and returns the value of it, properly formatted.
  383. *
  384. * @param array $match A match from preg_replace_callback
  385. * @return string
  386. */
  387. protected function processConditionVar($match)
  388. {
  389. $var = is_array($match) ? $match[0] : $match;
  390. if (in_array(strtolower($var), array('true', 'false', 'null', 'or', 'and')) or
  391. strpos($var, '__cond_str') === 0 or
  392. strpos($var, '__cond_exists') === 0 or
  393. is_numeric($var))
  394. {
  395. return $var;
  396. }
  397. $value = $this->getVariable($var, $this->conditionalData, '__processConditionVar__');
  398. if ($value === '__processConditionVar__') {
  399. return $this->inCondition ? $var : 'null';
  400. }
  401. return $this->valueToLiteral($value);
  402. }
  403. /**
  404. * This is used as a callback for the conditional parser. It takes a variable
  405. * and returns the value of it, properly formatted.
  406. *
  407. * @param array $match A match from preg_replace_callback
  408. * @return string
  409. */
  410. protected function processParamVar($match)
  411. {
  412. return $match[1].$this->processConditionVar($match[2]);
  413. }
  414. /**
  415. * Takes a value and returns the literal value for it for use in a tag.
  416. *
  417. * @param string $value Value to convert
  418. * @return string
  419. */
  420. protected function valueToLiteral($value)
  421. {
  422. if (is_object($value) and is_callable(array($value, '__toString'))) {
  423. return var_export((string) $value, true);
  424. } elseif (is_array($value)) {
  425. return !empty($value) ? "true" : "false";
  426. } else {
  427. return var_export($value, true);
  428. }
  429. }
  430. /**
  431. * Sets up all the global regex to use the correct Scope Glue.
  432. *
  433. * @return void
  434. */
  435. protected function setupRegex()
  436. {
  437. if ($this->regexSetup) {
  438. return;
  439. }
  440. $glue = preg_quote($this->scopeGlue, '/');
  441. $this->variableRegex = $glue === '\\.' ? '[a-zA-Z0-9_'.$glue.']+' : '[a-zA-Z0-9_\.'.$glue.']+';
  442. $this->callbackNameRegex = $this->variableRegex.$glue.$this->variableRegex;
  443. $this->variableLoopRegex = '/\{\{\s*('.$this->variableRegex.')\s*\}\}(.*?)\{\{\s*\/\1\s*\}\}/ms';
  444. $this->variableTagRegex = '/\{\{\s*('.$this->variableRegex.')\s*\}\}/m';
  445. $this->callbackBlockRegex = '/\{\{\s*('.$this->variableRegex.')(\s.*?)\}\}(.*?)\{\{\s*\/\1\s*\}\}/ms';
  446. $this->recursiveRegex = '/\{\{\s*\*recursive\s*('.$this->variableRegex.')\*\s*\}\}/ms';
  447. $this->noparseRegex = '/\{\{\s*noparse\s*\}\}(.*?)\{\{\s*\/noparse\s*\}\}/ms';
  448. $this->conditionalRegex = '/\{\{\s*(if|unless|elseif|elseunless)\s*((?:\()?(.*?)(?:\))?)\s*\}\}/ms';
  449. $this->conditionalElseRegex = '/\{\{\s*else\s*\}\}/ms';
  450. $this->conditionalEndRegex = '/\{\{\s*endif\s*\}\}/ms';
  451. $this->conditionalExistsRegex = '/(\s+|^)exists\s+('.$this->variableRegex.')(\s+|$)/ms';
  452. $this->conditionalNotRegex = '/(\s+|^)not(\s+|$)/ms';
  453. $this->regexSetup = true;
  454. // This is important, it's pretty unclear by the documentation
  455. // what the default value is on <= 5.3.6
  456. ini_set('pcre.backtrack_limit', 1000000);
  457. }
  458. /**
  459. * Extracts the noparse text so that it is not parsed.
  460. *
  461. * @param string $text The text to extract from
  462. * @return string
  463. */
  464. protected function extractNoparse($text)
  465. {
  466. /**
  467. * $matches[][0] is the raw noparse match
  468. * $matches[][1] is the noparse contents
  469. */
  470. if (preg_match_all($this->noparseRegex, $text, $matches, PREG_SET_ORDER)) {
  471. foreach ($matches as $match) {
  472. $text = $this->createExtraction('noparse', $match[0], $match[1], $text);
  473. }
  474. }
  475. return $text;
  476. }
  477. /**
  478. * Extracts the looped tags so that we can parse conditionals then re-inject.
  479. *
  480. * @param string $text The text to extract from
  481. * @return string
  482. */
  483. protected function extractLoopedTags($text, $data = array(), $callback = null)
  484. {
  485. /**
  486. * $matches[][0] is the raw match
  487. */
  488. if (preg_match_all($this->callbackBlockRegex, $text, $matches, PREG_SET_ORDER)) {
  489. foreach ($matches as $match) {
  490. // Does this callback block contain parameters?
  491. if ($this->parseParameters($match[2], $data, $callback)) {
  492. // Let's extract it so it doesn't conflict with local variables when
  493. // parseVariables() is called.
  494. $text = $this->createExtraction('callback_blocks', $match[0], $match[0], $text);
  495. } else {
  496. $text = $this->createExtraction('looped_tags', $match[0], $match[0], $text);
  497. }
  498. }
  499. }
  500. return $text;
  501. }
  502. /**
  503. * Extracts text out of the given text and replaces it with a hash which
  504. * can be used to inject the extractions replacement later.
  505. *
  506. * @param string $type Type of extraction
  507. * @param string $extraction The text to extract
  508. * @param string $replacement Text that will replace the extraction when re-injected
  509. * @param string $text Text to extract out of
  510. * @return string
  511. */
  512. protected function createExtraction($type, $extraction, $replacement, $text)
  513. {
  514. $hash = md5($replacement);
  515. self::$extractions[$type][$hash] = $replacement;
  516. return str_replace($extraction, "{$type}_{$hash}", $text);
  517. }
  518. /**
  519. * Injects all of the extractions.
  520. *
  521. * @param string $text Text to inject into
  522. * @return string
  523. */
  524. protected function injectExtractions($text, $type = null)
  525. {
  526. if ($type === null) {
  527. foreach (self::$extractions as $type => $extractions) {
  528. foreach ($extractions as $hash => $replacement) {
  529. if (strpos($text, "{$type}_{$hash}") !== false) {
  530. $text = str_replace("{$type}_{$hash}", $replacement, $text);
  531. unset(self::$extractions[$type][$hash]);
  532. }
  533. }
  534. }
  535. } else {
  536. if ( ! isset(self::$extractions[$type])) {
  537. return $text;
  538. }
  539. foreach (self::$extractions[$type] as $hash => $replacement) {
  540. if (strpos($text, "{$type}_{$hash}") !== false) {
  541. $text = str_replace("{$type}_{$hash}", $replacement, $text);
  542. unset(self::$extractions[$type][$hash]);
  543. }
  544. }
  545. }
  546. return $text;
  547. }
  548. /**
  549. * Takes a dot-notated key and finds the value for it in the given
  550. * array or object.
  551. *
  552. * @param string $key Dot-notated key to find
  553. * @param array|object $data Array or object to search
  554. * @param mixed $default Default value to use if not found
  555. * @return mixed
  556. */
  557. protected function getVariable($key, $data, $default = null)
  558. {
  559. if (strpos($key, $this->scopeGlue) === false) {
  560. $parts = explode('.', $key);
  561. } else {
  562. $parts = explode($this->scopeGlue, $key);
  563. }
  564. foreach ($parts as $key_part) {
  565. if (is_array($data)) {
  566. if ( ! array_key_exists($key_part, $data)) {
  567. return $default;
  568. }
  569. $data = $data[$key_part];
  570. } elseif (is_object($data)) {
  571. if ( ! isset($data->{$key_part})) {
  572. return $default;
  573. }
  574. $data = $data->{$key_part};
  575. } else {
  576. return $default;
  577. }
  578. }
  579. return $data;
  580. }
  581. /**
  582. * Evaluates the PHP in the given string.
  583. *
  584. * @param string $text Text to evaluate
  585. * @return string
  586. */
  587. protected function parsePhp($text)
  588. {
  589. ob_start();
  590. $result = eval('?>'.$text.'<?php ');
  591. if ($result === false) {
  592. $output = 'You have a syntax error in your Lex tags. The offending code: ';
  593. throw new ParsingException($output.str_replace(array('?>', '<?php '), '', $text));
  594. }
  595. return ob_get_clean();
  596. }
  597. /**
  598. * Parses a parameter string into an array
  599. *
  600. * @param string The string of parameters
  601. * @return array
  602. */
  603. protected function parseParameters($parameters, $data, $callback)
  604. {
  605. $this->conditionalData = $data;
  606. $this->inCondition = true;
  607. // Extract all literal string in the conditional to make it easier
  608. if (preg_match_all('/(["\']).*?(?<!\\\\)\1/', $parameters, $str_matches)) {
  609. foreach ($str_matches[0] as $m) {
  610. $parameters = $this->createExtraction('__param_str', $m, $m, $parameters);
  611. }
  612. }
  613. $parameters = preg_replace_callback(
  614. '/(.*?\s*=\s*(?!__))('.$this->variableRegex.')/is',
  615. array($this, 'processParamVar'),
  616. $parameters
  617. );
  618. if ($callback) {
  619. $parameters = preg_replace('/(.*?\s*=\s*(?!\{\s*)(?!__))('.$this->callbackNameRegex.')(?!\s*\})\b/', '$1{$2}', $parameters);
  620. $parameters = $this->parseCallbackTags($parameters, $data, $callback);
  621. }
  622. // Re-inject any strings we extracted
  623. $parameters = $this->injectExtractions($parameters, '__param_str');
  624. $this->inCondition = false;
  625. if (preg_match_all('/(.*?)\s*=\s*(\'|"|&#?\w+;)(.*?)(?<!\\\\)\2/s', trim($parameters), $matches)) {
  626. $return = array();
  627. foreach ($matches[1] as $i => $attr) {
  628. $return[trim($matches[1][$i])] = stripslashes($matches[3][$i]);
  629. }
  630. return $return;
  631. }
  632. return array();
  633. }
  634. }