PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/assets/snippets/ditto/classes/phx.parser.class.inc.php

http://modx-ja.googlecode.com/
PHP | 459 lines | 400 code | 16 blank | 43 comment | 12 complexity | 2d4fc1bb7ece0524cd5b0c93cb5e41b6 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /*####
  3. #
  4. # Name: PHx (Placeholders Xtended)
  5. # Version: 2.1.4
  6. # Modified by Nick to include external files
  7. # Author: Armand "bS" Pondman (apondman@zerobarrier.nl)
  8. # Date: July 13, 2007
  9. #
  10. ####*/
  11. class PHxParser {
  12. var $placeholders = array();
  13. function PHxParser($debug=0,$maxpass=50) {
  14. global $modx;
  15. $this->name = "PHx";
  16. $this->version = "2.1.4";
  17. $this->user["mgrid"] = intval($_SESSION['mgrInternalKey']);
  18. $this->user["usrid"] = intval($_SESSION['webInternalKey']);
  19. $this->user["id"] = ($this->user["usrid"] > 0 ) ? (-$this->user["usrid"]) : $this->user["mgrid"];
  20. $this->cache["cm"] = array();
  21. $this->cache["ui"] = array();
  22. $this->cache["mo"] = array();
  23. $this->safetags[0][0] = '~(?<![\[]|^\^)\[(?=[^\+\*\(\[]|$)~s';
  24. $this->safetags[0][1] = '~(?<=[^\+\*\)\]]|^)\](?=[^\]]|$)~s';
  25. $this->safetags[1][0] = '&_PHX_INTERNAL_091_&';
  26. $this->safetags[1][1] = '&_PHX_INTERNAL_093_&';
  27. $this->safetags[2][0] = '[';
  28. $this->safetags[2][1] = ']';
  29. $this->console = array();
  30. $this->debug = ($debug!='') ? $debug : 0;
  31. $this->debugLog = false;
  32. $this->curPass = 0;
  33. $this->maxPasses = ($maxpass!='') ? $maxpass : 50;
  34. $this->swapSnippetCache = array();
  35. $modx->setPlaceholder("phx", "&_PHX_INTERNAL_&");
  36. }
  37. // Plugin event hook for MODx
  38. function OnParseDocument() {
  39. global $modx;
  40. // Get document output from MODx
  41. $template = $modx->documentOutput;
  42. // To the parse cave .. let's go! *insert batman tune here*
  43. $template = $this->Parse($template);
  44. // Set processed document output in MODx
  45. $modx->documentOutput = $template;
  46. }
  47. // Parser: Preparation, cleaning and checkup
  48. function Parse($template='') {
  49. global $modx;
  50. // If we already reached max passes don't get at it again.
  51. if ($this->curPass == $this->maxPasses) return $template;
  52. // Set template pre-process hash
  53. $st = md5($template);
  54. // Replace non-call characters in the template: [, ]
  55. $template = preg_replace($this->safetags[0],$this->safetags[1],$template);
  56. // To the parse mobile.. let's go! *insert batman tune here*
  57. $template = $this->ParseValues($template);
  58. // clean up unused placeholders that have modifiers attached (MODx can't clean them)
  59. preg_match_all('~\[(\+|\*|\()([^:\+\[\]]+)([^\[\]]*?)(\1|\))\]~s', $template, $matches);
  60. if ($matches[0]) {
  61. $template = str_replace($matches[0], '', $template);
  62. $this->Log("Cleaning unsolved tags: \n" . implode("\n",$matches[2]) );
  63. }
  64. // Restore non-call characters in the template: [, ]
  65. $template = str_replace($this->safetags[1],$this->safetags[2],$template);
  66. // Set template post-process hash
  67. $et = md5($template);
  68. // If template has changed, parse it once more...
  69. if ($st!=$et) $template = $this->Parse($template);
  70. // Write an event log if debugging is enabled and there is something to log
  71. if ($this->debug && $this->debugLog) {
  72. $modx->logEvent($this->curPass,1,$this->createEventLog(), $this->name.' '.$this->version);
  73. $this->debugLog = false;
  74. }
  75. // Return the processed template
  76. return $template;
  77. }
  78. // Parser: Tag detection and replacements
  79. function ParseValues($template='') {
  80. global $modx;
  81. $this->curPass = $this->curPass + 1;
  82. $st = md5($template);
  83. //$this->LogSource($template);
  84. $this->LogPass();
  85. // MODx Chunks
  86. $this->Log("MODx Chunks -> Merging all chunk tags");
  87. $template = $modx->mergeChunkContent($template);
  88. // MODx Snippets
  89. //if ( preg_match_all('~\[(\[|!)([^\[]*?)(!|\])\]~s',$template, $matches)) {
  90. if ( preg_match_all('~\[(\[)([^\[]*?)(\])\]~s',$template, $matches)) {
  91. $count = count($matches[0]);
  92. $var_search = array();
  93. $var_replace = array();
  94. // for each detected snippet
  95. for($i=0; $i<$count; $i++) {
  96. $snippet = $matches[2][$i]; // snippet call
  97. $this->Log("MODx Snippet -> ".$snippet);
  98. // Let MODx evaluate snippet
  99. $replace = $modx->evalSnippets("[[".$snippet."]]");
  100. $this->LogSnippet($replace);
  101. // Replace values
  102. $var_search[] = $matches[0][$i];
  103. $var_replace[] = $replace;
  104. }
  105. $template = str_replace($var_search, $var_replace, $template);
  106. }
  107. // PHx / MODx Tags
  108. if ( preg_match_all('~\[(\+|\*|\()([^:\+\[\]]+)([^\[\]]*?)(\1|\))\]~s',$template, $matches)) {
  109. //$matches[0] // Complete string that's need to be replaced
  110. //$matches[1] // Type
  111. //$matches[2] // The placeholder(s)
  112. //$matches[3] // The modifiers
  113. //$matches[4] // Type (end character)
  114. $count = count($matches[0]);
  115. $var_search = array();
  116. $var_replace = array();
  117. for($i=0; $i<$count; $i++) {
  118. $replace = NULL;
  119. $match = $matches[0][$i];
  120. $type = $matches[1][$i];
  121. $type_end = $matches[4][$i];
  122. $input = $matches[2][$i];
  123. $modifiers = $matches[3][$i];
  124. $var_search[] = $match;
  125. switch($type) {
  126. // Document / Template Variable eXtended
  127. case "*":
  128. $this->Log("MODx TV/DV: " . $input);
  129. $input = $modx->mergeDocumentContent("[*".$input."*]");
  130. $replace = $this->Filter($input,$modifiers);
  131. break;
  132. // MODx Setting eXtended
  133. case "(":
  134. $this->Log("MODx Setting variable: " . $input);
  135. $input = $modx->mergeSettingsContent("[(".$input.")]");
  136. $replace = $this->Filter($input,$modifiers);
  137. break;
  138. // MODx Placeholder eXtended
  139. default:
  140. $this->Log("MODx / PHx placeholder variable: " . $input);
  141. // Check if placeholder is set
  142. if ( !array_key_exists($input, $this->placeholders) && !array_key_exists($input, $modx->placeholders) ) {
  143. // not set so try again later.
  144. $replace = $match;
  145. $this->Log(" |--- Skipping - hasn't been set yet.");
  146. }
  147. else {
  148. // is set, get value and run filter
  149. $input = $this->getPHxVariable($input);
  150. $replace = $this->Filter($input,$modifiers);
  151. }
  152. break;
  153. }
  154. $var_replace[] = $replace;
  155. }
  156. $template = str_replace($var_search, $var_replace, $template);
  157. }
  158. $et = md5($template); // Post-process template hash
  159. // Log an event if this was the maximum pass
  160. if ($this->curPass == $this->maxPasses) $this->Log("Max passes reached. infinite loop protection so exiting.\n If you need the extra passes set the max passes to the highest count of nested tags in your template.");
  161. // If this pass is not at maximum passes and the template hash is not the same, get at it again.
  162. if (($this->curPass < $this->maxPasses) && ($st!=$et)) $template = $this->ParseValues($template);
  163. return $template;
  164. }
  165. // Parser: modifier detection and eXtended processing if needed
  166. function Filter($input, $modifiers) {
  167. global $modx;
  168. $output = $input;
  169. $this->Log(" |--- Input = '". $output ."'");
  170. if (preg_match_all('~:([^:=]+)(?:=`(.*?)`(?=:[^:=]+|$))?~s',$modifiers, $matches)) {
  171. $modifier_cmd = $matches[1]; // modifier command
  172. $modifier_value = $matches[2]; // modifier value
  173. $count = count($modifier_cmd);
  174. $condition = array();
  175. for($i=0; $i<$count; $i++) {
  176. $output = trim($output);
  177. $this->Log(" |--- Modifier = '". $modifier_cmd[$i] ."'");
  178. if ($modifier_value[$i] != '') $this->Log(" |--- Options = '". $modifier_value[$i] ."'");
  179. switch ($modifier_cmd[$i]) {
  180. ##### Conditional Modifiers
  181. case "input": case "if": $output = $modifier_value[$i]; break;
  182. case "equals": case "is": case "eq": $condition[] = intval(($output==$modifier_value[$i])); break;
  183. case "notequals": case "isnot": case "isnt": case "ne":$condition[] = intval(($output!=$modifier_value[$i]));break;
  184. case "isgreaterthan": case "isgt": case "eg": $condition[] = intval(($output>=$modifier_value[$i]));break;
  185. case "islowerthan": case "islt": case "el": $condition[] = intval(($output<=$modifier_value[$i]));break;
  186. case "greaterthan": case "gt": $condition[] = intval(($output>$modifier_value[$i]));break;
  187. case "lowerthan": case "lt":$condition[] = intval(($output<$modifier_value[$i]));break;
  188. case "isinrole": case "ir": case "memberof": case "mo": // Is Member Of (same as inrole but this one can be stringed as a conditional)
  189. if ($output == "&_PHX_INTERNAL_&") $output = $this->user["id"];
  190. $grps = (strlen($modifier_value) > 0 ) ? explode(",",$modifier_value[$i]) :array();
  191. $condition[] = intval($this->isMemberOfWebGroupByUserId($output,$grps));
  192. break;
  193. case "or":$condition[] = "||";break;
  194. case "and": $condition[] = "&&";break;
  195. case "show":
  196. $conditional = implode(' ',$condition);
  197. $isvalid = intval(eval("return (". $conditional. ");"));
  198. if (!$isvalid) { $output = NULL;}
  199. case "then":
  200. $conditional = implode(' ',$condition);
  201. $isvalid = intval(eval("return (". $conditional. ");"));
  202. if ($isvalid) { $output = $modifier_value[$i]; }
  203. else { $output = NULL; }
  204. break;
  205. case "else":
  206. $conditional = implode(' ',$condition);
  207. $isvalid = intval(eval("return (". $conditional. ");"));
  208. if (!$isvalid) { $output = $modifier_value[$i]; }
  209. break;
  210. case "select":
  211. $raw = explode("&",$modifier_value[$i]);
  212. $map = array();
  213. for($m=0; $m<(count($raw)); $m++) {
  214. $mi = explode("=",$raw[$m]);
  215. $map[$mi[0]] = $mi[1];
  216. }
  217. $output = $map[$output];
  218. break;
  219. ##### End of Conditional Modifiers
  220. ##### String Modifiers
  221. case "lcase": case "strtolower": $output = strtolower($output); break;
  222. case "ucase": case "strtoupper": $output = strtoupper($output); break;
  223. case "htmlent": case "htmlentities": $output = htmlentities($output,ENT_QUOTES,$modx->config['etomite_charset']); break;
  224. case "html_entity_decode": $output = html_entity_decode($output,ENT_QUOTES,$modx->config['etomite_charset']); break;
  225. case "esc":
  226. $output = preg_replace("/&amp;(#[0-9]+|[a-z]+);/i", "&$1;", htmlspecialchars($output));
  227. $output = str_replace(array("[","]","`"),array("&#91;","&#93;","&#96;"),$output);
  228. break;
  229. case "strip": $output = preg_replace("~([\n\r\t\s]+)~"," ",$output); break;
  230. case "notags": case "strip_tags": $output = strip_tags($output); break;
  231. case "length": case "len": case "strlen": $output = strlen($output); break;
  232. case "reverse": case "strrev": $output = strrev($output); break;
  233. case "wordwrap": // default: 70
  234. $wrapat = intval($modifier_value[$i]) ? intval($modifier_value[$i]) : 70;
  235. $output = preg_replace("~(\b\w+\b)~e","wordwrap('\\1',\$wrapat,' ',1)",$output);
  236. break;
  237. case "limit": // default: 100
  238. $limit = intval($modifier_value[$i]) ? intval($modifier_value[$i]) : 100;
  239. $output = substr($output,0,$limit);
  240. break;
  241. case "str_shuffle": case "shuffle": $output = str_shuffle($output); break;
  242. case "str_word_count": case "word_count": case "wordcount": $output = str_word_count($output); break;
  243. // These are all straight wrappers for PHP functions
  244. case "ucfirst":
  245. case "lcfirst":
  246. case "ucwords":
  247. case "addslashes":
  248. case "ltrim":
  249. case "rtrim":
  250. case "trim":
  251. case "nl2br":
  252. case "md5": $output = $modifier_cmd[$i]($output); break;
  253. ##### Special functions
  254. case "math":
  255. $filter = preg_replace("~([a-zA-Z\n\r\t\s])~","",$modifier_value[$i]);
  256. $filter = str_replace("?",$output,$filter);
  257. $output = eval("return ".$filter.";");
  258. break;
  259. case "ifempty": if (empty($output)) $output = $modifier_value[$i]; break;
  260. case "date": $output = $this->mb_strftime($modifier_value[$i],0+$output); break;
  261. case "set":
  262. $c = $i+1;
  263. if ($count>$c&&$modifier_cmd[$c]=="value") $output = preg_replace("~([^a-zA-Z0-9])~","",$modifier_value[$i]);
  264. break;
  265. case "value":
  266. if ($i>0&&$modifier_cmd[$i-1]=="set") { $modx->SetPlaceholder("phx.".$output,$modifier_value[$i]); }
  267. $output = NULL;
  268. break;
  269. case "userinfo":
  270. if ($output == "&_PHX_INTERNAL_&") $output = $this->user["id"];
  271. $output = $this->ModUser($output,$modifier_value[$i]);
  272. break;
  273. case "inrole": // deprecated
  274. if ($output == "&_PHX_INTERNAL_&") $output = $this->user["id"];
  275. $grps = (strlen($modifier_value) > 0 ) ? explode(",",$modifier_value[$i]) :array();
  276. $output = intval($this->isMemberOfWebGroupByUserId($output,$grps));
  277. break;
  278. // If we haven't yet found the modifier, let's look elsewhere
  279. default:
  280. // Is a snippet defined?
  281. if (!array_key_exists($modifier_cmd[$i], $this->cache["cm"])) {
  282. $sql = "SELECT snippet FROM " . $modx->getFullTableName("site_snippets") . " WHERE " . $modx->getFullTableName("site_snippets") . ".name='phx:" . $modifier_cmd[$i] . "';";
  283. $result = $modx->dbQuery($sql);
  284. if ($modx->recordCount($result) == 1) {
  285. $row = $modx->fetchRow($result);
  286. $cm = $this->cache["cm"][$modifier_cmd[$i]] = $row["snippet"];
  287. $this->Log(" |--- DB -> Custom Modifier");
  288. } else if ($modx->recordCount($result) == 0){ // If snippet not found, look in the modifiers folder
  289. $filename = $modx->config['rb_base_dir'] . 'plugins/phx/modifiers/'.$modifier_cmd[$i].'.phx.php';
  290. if (@file_exists($filename)) {
  291. $file_contents = @file_get_contents($filename);
  292. $file_contents = str_replace('<?php', '', $file_contents);
  293. $file_contents = str_replace('?>', '', $file_contents);
  294. $file_contents = str_replace('<?', '', $file_contents);
  295. $cm = $this->cache["cm"][$modifier_cmd[$i]] = $file_contents;
  296. $this->Log(" |--- File ($filename) -> Custom Modifier");
  297. } else {
  298. $cm = '';
  299. $this->Log(" |--- PHX Error: {$modifier_cmd[$i]} could not be found");
  300. }
  301. }
  302. } else {
  303. $cm = $this->cache["cm"][$modifier_cmd[$i]];
  304. $this->Log(" |--- Cache -> Custom Modifier");
  305. }
  306. ob_start();
  307. $options = $modifier_value[$i];
  308. $custom = eval($cm);
  309. $msg = ob_get_contents();
  310. $output = $msg.$custom;
  311. ob_end_clean();
  312. break;
  313. }
  314. if (count($condition)) $this->Log(" |--- Condition = '". $condition[count($condition)-1] ."'");
  315. $this->Log(" |--- Output = '". $output ."'");
  316. }
  317. }
  318. return $output;
  319. }
  320. // Event logging (debug)
  321. function createEventLog() {
  322. if($this->console) {
  323. $console = implode("\n",$this->console);
  324. $this->console = array();
  325. return '<pre style="overflow: auto;">' . $console . '</pre>';
  326. }
  327. }
  328. // Returns a cleaned string escaping the HTML and special MODx characters
  329. function LogClean($string) {
  330. $string = preg_replace("/&amp;(#[0-9]+|[a-z]+);/i", "&$1;", htmlspecialchars($string));
  331. $string = str_replace(array("[","]","`"),array("&#91;","&#93;","&#96;"),$string);
  332. return $string;
  333. }
  334. // Simple log entry
  335. function Log($string) {
  336. if ($this->debug) {$this->debugLog = true; $this->console[] = (count($this->console)+1-$this->curPass). " [". $this->mb_strftime("%H:%M:%S",time()). "] " . $this->LogClean($string);}
  337. }
  338. // Log snippet output
  339. function LogSnippet($string) {
  340. if ($this->debug) {$this->debugLog = true; $this->console[] = (count($this->console)+1-$this->curPass). " [". $this->mb_strftime("%H:%M:%S",time()). "] " . " |--- Returns: <div style='margin: 10px;'>".$this->LogClean($string)."</div>";}
  341. }
  342. // Log pass
  343. function LogPass() {
  344. $this->console[] = "<div style='margin: 2px;margin-top: 5px;border-bottom: 1px solid black;'>Pass " . $this->curPass . "</div>";
  345. }
  346. // Log pass
  347. function LogSource($string) {
  348. $this->console[] = "<div style='margin: 2px;margin-top: 5px;border-bottom: 1px solid black;'>Source:</div>" . $this->LogClean($string);
  349. }
  350. // Returns the specified field from the user record
  351. // positive userid = manager, negative integer = webuser
  352. function ModUser($userid,$field) {
  353. global $modx;
  354. if (!array_key_exists($userid, $this->cache["ui"])) {
  355. if (intval($userid) < 0) {
  356. $user = $modx->getWebUserInfo(-($userid));
  357. } else {
  358. $user = $modx->getUserInfo($userid);
  359. }
  360. $this->cache["ui"][$userid] = $user;
  361. } else {
  362. $user = $this->cache["ui"][$userid];
  363. }
  364. return $user[$field];
  365. }
  366. // Returns true if the user id is in one the specified webgroups
  367. function isMemberOfWebGroupByUserId($userid=0,$groupNames=array()) {
  368. global $modx;
  369. // if $groupNames is not an array return false
  370. if(!is_array($groupNames)) return false;
  371. // if the user id is a negative number make it positive
  372. if (intval($userid) < 0) { $userid = -($userid); }
  373. // Creates an array with all webgroups the user id is in
  374. if (!array_key_exists($userid, $this->cache["mo"])) {
  375. $tbl = $modx->getFullTableName("webgroup_names");
  376. $tbl2 = $modx->getFullTableName("web_groups");
  377. $sql = "SELECT wgn.name FROM $tbl wgn INNER JOIN $tbl2 wg ON wg.webgroup=wgn.id AND wg.webuser='".$userid."'";
  378. $this->cache["mo"][$userid] = $grpNames = $modx->db->getColumn("name",$sql);
  379. } else {
  380. $grpNames = $this->cache["mo"][$userid];
  381. }
  382. // Check if a supplied group matches a webgroup from the array we just created
  383. foreach($groupNames as $k=>$v)
  384. if(in_array(trim($v),$grpNames)) return true;
  385. // If we get here the above logic did not find a match, so return false
  386. return false;
  387. }
  388. // Returns the value of a PHx/MODx placeholder.
  389. function getPHxVariable($name) {
  390. global $modx;
  391. // Check if this variable is created by PHx
  392. if (array_key_exists($name, $this->placeholders)) {
  393. // Return the value from PHx
  394. return $this->placeholders[$name];
  395. } else {
  396. // Return the value from MODx
  397. return $modx->getPlaceholder($name);
  398. }
  399. }
  400. // Sets a placeholder variable which can only be access by PHx
  401. function setPHxVariable($name, $value) {
  402. if ($name != "phx") $this->placeholders[$name] = $value;
  403. }
  404. function mb_strftime($format='', $timestamp='')
  405. {
  406. global $modx;
  407. if(empty($format)) $format = $modx->toDateFormat(null, 'formatOnly') . ' %H:%M';
  408. if(method_exists($modx,'mb_strftime'))
  409. {
  410. $str = $modx->mb_strftime($format,$timestamp);
  411. }
  412. else $str = strftime($format,$timestamp);
  413. return $str;
  414. }
  415. }
  416. ?>