PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/system/cms/libraries/Lex/Parser.php

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