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

/system/classes/hiengine.php

https://github.com/HabariMag/habarimag-old
PHP | 478 lines | 289 code | 44 blank | 145 comment | 33 complexity | d304b500286d0c69509acfd80b970fde MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /**
  3. * @package Habari
  4. *
  5. */
  6. /**
  7. *
  8. * Habari HiEngine class
  9. *
  10. * The HiEngine is a subclass of the RawPHPEngine class
  11. * which is intended for those theme designers who want to use
  12. * simple {hi:*} tags four output instead of PHP
  13. *
  14. * To use this engine, specify "hiengine" in the theme.xml of a theme.
  15. *
  16. * This engine behaves exactly like RawPHPEngine after the template tags are
  17. * processed, so if an existing RawPHPEngine template is switched to HiEngine,
  18. * it should still run without issue.
  19. */
  20. class HiEngine extends RawPHPEngine {
  21. /**
  22. *
  23. * Constructor for HiEngine
  24. *
  25. * Sets up the stream protocol handler
  26. */
  27. public function __construct()
  28. {
  29. $streams = stream_get_wrappers();
  30. if ( ! in_array( 'hi', $streams ) ) {
  31. stream_wrapper_register( "hi", "HiEngineParser" )
  32. or die( _t( "Failed to register HiEngine stream protocol" ) );
  33. }
  34. }
  35. /**
  36. *
  37. * A function which outputs the result of a transposed
  38. * template to the output stream
  39. * @param template $ Name of template to display
  40. */
  41. public function display( $template )
  42. {
  43. extract( $this->engine_vars );
  44. //Utils::debug($this->engine_vars);die();
  45. if ( $this->template_exists( $template ) ) {
  46. $template_file = isset( $this->template_map[$template] ) ? $this->template_map[$template] : null;
  47. $template_file = Plugins::filter( 'include_template_file', $template_file, $template, __CLASS__ );
  48. $template_file = 'hi://' . $template_file;
  49. $fc = file_get_contents( $template_file );
  50. //echo($fc);
  51. eval( '?'.'>' . $fc );
  52. //include $template_file; // stopped working properly in PHP 5.2.8
  53. }
  54. }
  55. }
  56. /**
  57. * HiEngineParser - A stream filtering class for the HiEngine
  58. */
  59. class HiEngineParser
  60. {
  61. protected $file;
  62. protected $filename;
  63. protected $position;
  64. protected $contexts;
  65. protected $strings = array();
  66. /**
  67. * Open a HiEngineParser stream
  68. *
  69. * @param string $path Path of the opened resource, including the protocol specifier
  70. * @param string $mode Mode used to open the file
  71. * @param integer $options Bitmask options for opening this stream
  72. * @param string $opened_path The actual path opened if using relative path, by reference
  73. * @return boolean true on success
  74. */
  75. function stream_open( $path, $mode, $options, &$opened_path )
  76. {
  77. $this->filename = substr( $path, 5 );
  78. $this->file = file_get_contents( $this->filename );
  79. // This processed value should cache and invalidate if a checksum of the template changes! :)
  80. $this->file = $this->process( $this->file );
  81. $this->position = 0;
  82. return true;
  83. }
  84. /**
  85. * Read data from a HiEngineParser stream
  86. *
  87. * @param integer $count Number of characters to read from the current position
  88. * @return string Characters read from the stream
  89. */
  90. function stream_read( $count )
  91. {
  92. if ( $this->stream_eof() ) {
  93. return false;
  94. }
  95. $ret = substr( $this->file, $this->position, $count );
  96. $this->position += strlen( $ret );
  97. return $ret;
  98. }
  99. /**
  100. * Srite data to a HiEngineParser stream
  101. *
  102. * @param string $data Data to write
  103. * @return boolean false, since this stream type is read-only
  104. */
  105. function stream_write( $data )
  106. {
  107. // HiEngineParser streams are read-only
  108. return false;
  109. }
  110. /**
  111. * Report the position in the stream
  112. *
  113. * @return integer the position in the stream
  114. */
  115. function stream_tell()
  116. {
  117. return $this->position;
  118. }
  119. /**
  120. * Report whether the stream is at the end of the file
  121. *
  122. * @return boolean true if the file pointer is at or beyond the end of the file
  123. */
  124. function stream_eof()
  125. {
  126. return $this->position >= strlen( $this->file );
  127. }
  128. /**
  129. * Seek to a specific position within the stream
  130. *
  131. * @param integer $offset The offset from the specified position
  132. * @param integer $whence The position to seek from
  133. * @return boolean true if seek was successful
  134. */
  135. function stream_seek( $offset, $whence )
  136. {
  137. switch ( $whence ) {
  138. case SEEK_SET:
  139. if ( $offset < strlen( $this->file ) && $offset >= 0 ) {
  140. $this->position = $offset;
  141. return true;
  142. }
  143. else {
  144. return false;
  145. }
  146. break;
  147. case SEEK_CUR:
  148. if ( $offset >= 0 ) {
  149. $this->position += $offset;
  150. return true;
  151. }
  152. else {
  153. return false;
  154. }
  155. break;
  156. case SEEK_END:
  157. if ( strlen( $this->file ) + $offset >= 0 ) {
  158. $this->position = strlen( $this->file ) + $offset;
  159. return true;
  160. }
  161. else {
  162. return false;
  163. }
  164. break;
  165. default:
  166. return false;
  167. }
  168. }
  169. /**
  170. * Return fstat() info as required when calling stats on the stream
  171. * @return array An array of stat info
  172. */
  173. function stream_stat()
  174. {
  175. return array();
  176. }
  177. /**
  178. * Process the template file for template tags
  179. *
  180. * @param string $template The template file contents
  181. * @return string The processed template
  182. */
  183. function process( $template )
  184. {
  185. $template = preg_replace_callback( '/\{hi:(".+?")((?:\s*[\w\.]+){0,2})\s*\}/smu', array( $this, 'hi_quote' ), $template );
  186. $template = preg_replace_callback( '%\{hi:([^\?]+?)\}(.+?)\{/hi:\1\}%ism', array( $this, 'hi_loop' ), $template );
  187. $template = preg_replace_callback( '%\{hi:\?\s*(.+?)\}(.+?)\{/hi:\?\}%ismu', array( $this, 'hi_if' ), $template );
  188. $template = preg_replace_callback( '%\{hi:([^:}]+?:.+?)\}%i', array( $this, 'hi_command' ), $template );
  189. $template = preg_replace_callback( '%\{hi:(.+?)\}%i', array( $this, 'hi_var' ), $template );
  190. return $template;
  191. }
  192. /**
  193. * Replace a single function template tag with its PHP counterpart
  194. *
  195. * @param array $matches The match array found in HiEngineParser::process()
  196. * @return string The PHP replacement for the function template tag
  197. */
  198. function hi_command( $matches )
  199. {
  200. $cmd = trim( $matches[1] );
  201. // Catch tags in the format {hi:command:parameter}
  202. if ( preg_match( '/^(\w+):(.+)$/u', $cmd, $cmd_matches ) ) {
  203. switch ( strtolower( $cmd_matches[1] ) ) {
  204. case 'area':
  205. return '<?php $theme->area(\'' . $cmd_matches[2] . '\'); ?>';
  206. case 'display':
  207. return '<?php $theme->display(\'' . $cmd_matches[2] . '\'); ?>';
  208. case 'option':
  209. case 'options':
  210. return '<?php Options::out(\'' . $cmd_matches[2] . '\'); ?>';
  211. case 'siteurl':
  212. return '<?php Site::out_url( \'' . $cmd_matches[2] . '\' ); ?>';
  213. case 'url':
  214. return '<?php URL::out( \'' . $cmd_matches[2] . '\' ); ?>';
  215. case 'session':
  216. switch ( $cmd_matches[2] ) {
  217. case 'messages':
  218. return '<?php if (Session::has_messages()){Session::messages_out();} ?>';
  219. case 'errors':
  220. return '<?php if (Session::has_errors()){Session::messages_out();} ?>';
  221. }
  222. // this is an internal match
  223. case 'context':
  224. return $this->hi_to_var( $cmd_matches[2] );
  225. case 'escape':
  226. return '<?php echo Utils::htmlspecialchars( ' . $this->hi_to_var( $cmd_matches[2] ) . ' ); ?>';
  227. }
  228. }
  229. return $matches[0];
  230. }
  231. /**
  232. * Replace a single template tag with its PHP counterpart
  233. *
  234. * @param array $matches The match array found in HiEngineParser::process()
  235. * @return string The PHP replacement for the template tag
  236. */
  237. function hi_var( $matches )
  238. {
  239. $cmd = trim( $matches[1] );
  240. $params = array();
  241. $returnval = false;
  242. if ( preg_match_all( '/(?<=\s)(?P<name>[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)\s*=\s*(?P<value>(?P<quot>["\']).+?\3|[^"\'\s]+)/i', $cmd, $foundparams, PREG_SET_ORDER ) ) {
  243. foreach ( $foundparams as $p ) {
  244. $params[$p['name']] = trim( $p['value'], $p['quot'] );
  245. }
  246. }
  247. // Straight variable or property output, ala {hi:variable_name} or {hi:post.title}
  248. if ( preg_match( '%(^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)%i', $cmd, $cmdmatch ) ) {
  249. $cmdmatch = str_replace( '.', '->', $cmdmatch[1] );
  250. if ( count( $this->contexts ) ) {
  251. // Build a conditional that checks for the most specific, then the least
  252. // eg.- $a->b->c->d->x, then $a->b->c->x, down to just $x
  253. $ctx = $this->contexts;
  254. $prefixes = array();
  255. foreach ( $ctx as $void ) {
  256. $prefixes[] = implode( '->', $this->contexts );
  257. array_pop( $ctx );
  258. }
  259. $output = '';
  260. foreach ( $prefixes as $prefix ) {
  261. $output .= '(is_object($' . $prefix . ') && !'.'is_null($' . $prefix . '->' . $cmdmatch . ')) ? $' . $prefix . '->' . $cmdmatch . ' : ';
  262. }
  263. $output .= '$' . $cmdmatch;
  264. $returnval = $output;
  265. }
  266. else {
  267. $returnval = '$'. $cmdmatch;
  268. }
  269. }
  270. if ( $returnval !== false ) {
  271. $returnval = $this->apply_parameters( $returnval, $params );
  272. return '<?php echo '. $returnval . '; ?>';
  273. }
  274. // Use tags in the format {hi:@foo} as theme functions, ala $theme->foo();
  275. if ( $cmd[0] == '@' ) {
  276. return '<?php $theme->' . substr( $cmd, 1 ) . '(); ?>';
  277. }
  278. // Didn't match anything we support so far
  279. return $matches[0];
  280. }
  281. /**
  282. * Take the found paramters on a variable tag and apply them to the output
  283. *
  284. * @param array $returnval The expression to be output
  285. * @param array $params An associative array of parameters
  286. * @return string The PHP expression with the paramters applied
  287. */
  288. function apply_parameters( $returnval, $params )
  289. {
  290. foreach ( $params as $k => $v ) {
  291. if ( $k[0] == '@' ) {
  292. $returnval = '$theme->' . substr( $cmd, 1 ) . '(' . $returnval . ", '" . $v . "')";
  293. }
  294. switch ( $k ) {
  295. case 'dateformat':
  296. $returnval = "call_user_func(array(" . $returnval . ", 'format'), '" . addslashes( $v ) . "')";
  297. break;
  298. }
  299. }
  300. return $returnval;
  301. }
  302. /**
  303. * Replace a loop tag section with its PHP counterpart, and add the context to the stack
  304. *
  305. * @param array $matches The match array found in HiEngineParser::process()
  306. * @return string The PHP replacement for the template tag
  307. */
  308. function hi_loop( $matches )
  309. {
  310. $hivar = $matches[1];
  311. $phpvar = $this->hi_to_var( $hivar );
  312. $iterator = strpos( $hivar, '.' ) ? substr( $hivar, strrpos( $hivar, '.' ) + 1 ) : $hivar;
  313. $output = '<?php foreach(' . $phpvar . ' as $' . $iterator . '_index => $' . $iterator . '_1): ?>';
  314. $this->contexts[] = "{$iterator}_1";
  315. $output .= $this->process( $matches[2] );
  316. $output .= '<?php endforeach; ?>';
  317. array_pop( $this->contexts );
  318. return $output;
  319. }
  320. /**
  321. * Replace variables in the hiengine syntax with PHP varaibles
  322. * @param array $matches The match array found in hi_if ()
  323. * @returns string A PHP variable string to use as the replacement
  324. */
  325. function var_replace( $matches )
  326. {
  327. $var = $matches[1];
  328. if ( is_callable( $var ) ) {
  329. return $var;
  330. }
  331. if ( preg_match( '/true|false|null|isset|empty/i', $var ) ) {
  332. return $var;
  333. }
  334. $var = $this->hi_to_var( $var );
  335. return $var;
  336. }
  337. function hi_to_var( $hisyntax )
  338. {
  339. $var = str_replace( '.', '->', $hisyntax );
  340. if ( count( $this->contexts ) ) {
  341. // Build a conditional that checks for the most specific, then the least
  342. // eg.- $a->b->c->d->x, then $a->b->c->x, down to just $x
  343. $ctx = $this->contexts;
  344. $prefixes = array();
  345. foreach ( $ctx as $void ) {
  346. $prefixes[] = implode( '->', $this->contexts );
  347. array_pop( $ctx );
  348. }
  349. $output = '';
  350. foreach ( $prefixes as $prefix ) {
  351. $output .= '(!is_null($' . $prefix . '->' . $var . ') ? $' . $prefix . '->' . $var . ' : ';
  352. }
  353. $output .= '$' . $var . ')';
  354. return $output;
  355. }
  356. else {
  357. return '$'. $var;
  358. }
  359. }
  360. /**
  361. * Creates a table of static strings in hiengine expressions to be replaced in later
  362. * @param array $matches The match found in hi_if ()
  363. * @returns string An uncommon string index for the stored static string.
  364. */
  365. function string_stack( $matches )
  366. {
  367. $key = chr( 0 ) . count( $this->strings ) . chr( 1 );
  368. $this->strings[$key] = $matches[0];
  369. return $key;
  370. }
  371. /**
  372. * Replace an if tag section with its PHP counterpart
  373. *
  374. * @param array $matches The match array found in HiEngineParser::process()
  375. * @return string The PHP replacement for the template tag
  376. */
  377. function hi_if ( $matches )
  378. {
  379. list( $void, $eval, $context ) = $matches;
  380. $eval = preg_replace_callback( '/([\'"]).*?(?<!\\\\)\1/i', array( $this, 'string_stack' ), $eval );
  381. $eval = preg_replace_callback( '/\b((?<!::)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*(?!::))\b/i', array( $this, 'var_replace' ), $eval );
  382. $eval = preg_replace( '/(?<!=)=(?!=)/i', '==', $eval );
  383. $eval = str_replace( array_keys( $this->strings ), $this->strings, $eval );
  384. $context = preg_replace( '/\{hi:\?else\?\}/i', '<?php else: ?>', $context );
  385. $output = '<?php if (' . $eval . '): ?>';
  386. $output .= $this->process( $context );
  387. $output .= '<?php endif; ?>';
  388. return $output;
  389. }
  390. /**
  391. * Prepare strings for translation
  392. * @param array $matches Matches in HiEngineParser::process()
  393. * @param string The PHP replacement for the template tag
  394. */
  395. function hi_quote( $matches )
  396. {
  397. $args = preg_split( '/\s+/u', trim( $matches[2] ) );
  398. preg_match_all( '/"(.+?)(?<!\\\\)"/', $matches[1], $quotes );
  399. $count = 0;
  400. $all_vars = array();
  401. foreach ( $quotes[1] as $index => $quote ) {
  402. preg_match_all( '/{hi:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)}/', $quote, $vars, PREG_SET_ORDER );
  403. foreach ( $vars as $var ) {
  404. $count++;
  405. $quote = str_replace( $var[0], '%'.$count.'\$s', $quote );
  406. $all_vars[] = '{hi:context:' . $var[1] . '}'; //$this->hi_to_var($var[1]);
  407. }
  408. $quotes[1][$index] = $quote;
  409. }
  410. if ( count( $quotes[1] ) > 1 ) {
  411. $output = '<?php printf(_n("'.$quotes[1][0].'", "'.$quotes[1][1].'", {hi:context:'.$args[0].'})';
  412. // Add vars
  413. if ( count( $all_vars ) > 0 ) {
  414. $output .= ', ' . implode( ', ', $all_vars );
  415. }
  416. array_shift( $args );
  417. }
  418. else {
  419. $output = '<?php _e("'.$quotes[1][0].'"';
  420. // Add vars
  421. if ( count( $all_vars ) > 0 ) {
  422. $output .= ', array(' . implode( ', ', $all_vars ) . ')';
  423. }
  424. }
  425. // Add the domain, if any
  426. if ( isset( $args[0] ) ) {
  427. $output .= ', "'.$args[0].'"';
  428. }
  429. // Close the tag
  430. $output .= '); ?>';
  431. return $output;
  432. }
  433. }
  434. ?>