RainTPL.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. <?php
  2. /**
  3. * RainTPL
  4. *
  5. * Realized by Federico Ulfo & maintained by the Rain Team
  6. * Distributed under GNU/LGPL 3 License
  7. *
  8. * @version 2.7.2
  9. */
  10. /**
  11. * RainTPL
  12. */
  13. class RainTPL{
  14. // -------------------------
  15. // CONFIGURATION
  16. // -------------------------
  17. /**
  18. * Template directory
  19. *
  20. * @var string
  21. */
  22. static $tpl_dir = 'tpl/';
  23. /**
  24. * Cache directory
  25. *
  26. * Is the directory where RainTPL will compile the template and save the cache
  27. *
  28. * @var string
  29. */
  30. static $cache_dir = 'tmp/';
  31. /**
  32. * Template base URL
  33. *
  34. * RainTPL will add this URL to the relative paths of element selected in $path_replace_list.
  35. *
  36. * @var string
  37. */
  38. static $base_url = null;
  39. /**
  40. * Template extension
  41. *
  42. * @var string
  43. */
  44. static $tpl_ext = "html";
  45. /**
  46. * Should the path be replaced
  47. *
  48. * Path replace is a cool features that replace all relative paths of images (&lt;img src="..."&gt;), stylesheet (&lt;link href="..."&gt;), script (&lt;script src="..."&gt;) and link (&lt;a href="..."&gt;)
  49. * Set true to enable the path replace.
  50. *
  51. * @var boolean
  52. */
  53. static $path_replace = true;
  54. /**
  55. * You can set what the path_replace method will replace.
  56. * Avaible options: a, img, link, script, input
  57. *
  58. * @var array
  59. */
  60. static $path_replace_list = array( 'a', 'img', 'link', 'script', 'input' );
  61. /**
  62. * You can define in the black list what string are disabled into the template tags
  63. *
  64. * @var array
  65. */
  66. static $black_list = array( '\$this', 'raintpl::', 'self::', '_SESSION', '_SERVER', '_ENV', 'eval', 'exec', 'unlink', 'rmdir' );
  67. /**
  68. * Check template
  69. *
  70. * true: checks template update time, if changed it compile them
  71. * false: loads the compiled template. Set false if server doesn't have write permission for cache_directory.
  72. *
  73. * @var bool
  74. */
  75. static $check_template_update = true;
  76. /**
  77. * PHP tags <? ?>
  78. *
  79. * True: php tags are enabled into the template
  80. * False: php tags are disabled into the template and rendered as html
  81. *
  82. * @var bool
  83. */
  84. static $php_enabled = false;
  85. /**
  86. * Debug mode flag
  87. *
  88. * True: debug mode is used, syntax errors are displayed directly in template. Execution of script is not terminated.
  89. * False: exception is thrown on found error.
  90. *
  91. * @var bool
  92. */
  93. static $debug = false;
  94. // -------------------------
  95. // -------------------------
  96. // RAINTPL VARIABLES
  97. // -------------------------
  98. /**
  99. * Is the array where RainTPL keep the variables assigned
  100. *
  101. * @var array
  102. */
  103. public $var = array();
  104. /**
  105. * variables to keep the template directories and info
  106. *
  107. * @var array
  108. */
  109. protected $tpl = array(); //
  110. /**
  111. * static cache enabled / disabled
  112. *
  113. * @var bool
  114. */
  115. protected $cache = false;
  116. /**
  117. * identify only one cache
  118. *
  119. * @var string
  120. */
  121. protected $cache_id = '';
  122. /**
  123. * takes all the config to create the md5 of the file
  124. *
  125. * @var array the file
  126. */
  127. protected static $config_name_sum = array();
  128. // -------------------------
  129. /**
  130. * default cache expire time = hour
  131. *
  132. * @const int
  133. */
  134. const CACHE_EXPIRE_TIME = 3600;
  135. /**
  136. * Assign variable
  137. * eg. $t->assign('name','mickey');
  138. *
  139. * @access public
  140. * @param mixed $variable_name Name of template variable or associative array name/value
  141. * @param mixed $value value assigned to this variable. Not set if variable_name is an associative array
  142. */
  143. public function assign( $variable, $value = null ){
  144. if( is_array( $variable ) )
  145. $this->var += $variable;
  146. else
  147. $this->var[ $variable ] = $value;
  148. }
  149. /**
  150. * Draw the template
  151. * eg. $html = $tpl->draw( 'demo', TRUE ); // return template in string
  152. * or $tpl->draw( $tpl_name ); // echo the template
  153. *
  154. * @access public
  155. * @param string $tpl_name template to load
  156. * @param boolean $return_string true=return a string, false=echo the template
  157. * @return string
  158. */
  159. public function draw( $tpl_name, $return_string = false ){
  160. try {
  161. // compile the template if necessary and set the template filepath
  162. $this->check_template( $tpl_name );
  163. } catch (RainTpl_Exception $e) {
  164. $output = $this->printDebug($e);
  165. die($output);
  166. }
  167. // Cache is off and, return_string is false
  168. // Rain just echo the template
  169. if( !$this->cache && !$return_string ){
  170. extract( $this->var );
  171. include $this->tpl['compiled_filename'];
  172. unset( $this->tpl );
  173. }
  174. // cache or return_string are enabled
  175. // rain get the output buffer to save the output in the cache or to return it as string
  176. else{
  177. //----------------------
  178. // get the output buffer
  179. //----------------------
  180. ob_start();
  181. extract( $this->var );
  182. include $this->tpl['compiled_filename'];
  183. $raintpl_contents = ob_get_clean();
  184. //----------------------
  185. // save the output in the cache
  186. if( $this->cache )
  187. file_put_contents( $this->tpl['cache_filename'], "<?php if(!class_exists('raintpl')){exit;}?>" . $raintpl_contents );
  188. // free memory
  189. unset( $this->tpl );
  190. // return or print the template
  191. if( $return_string ) return $raintpl_contents; else echo $raintpl_contents;
  192. }
  193. }
  194. /**
  195. * If exists a valid cache for this template it returns the cache
  196. *
  197. * @access public
  198. * @param string $tpl_name Name of template (set the same of draw)
  199. * @param int $expiration_time Set after how many seconds the cache expire and must be regenerated
  200. * @param string $cache_id Suffix to be used when writing file to cache (optional)
  201. * @return string it return the HTML or null if the cache must be recreated
  202. */
  203. public function cache( $tpl_name, $expire_time = self::CACHE_EXPIRE_TIME, $cache_id = '' ){
  204. // set the cache_id
  205. $this->cache_id = $cache_id;
  206. if( !$this->check_template( $tpl_name ) && file_exists( $this->tpl['cache_filename'] ) && ( time() - filemtime( $this->tpl['cache_filename'] ) < $expire_time ) )
  207. return substr( file_get_contents( $this->tpl['cache_filename'] ), 43 );
  208. else{
  209. //delete the cache of the selected template
  210. if (file_exists($this->tpl['cache_filename']))
  211. unlink($this->tpl['cache_filename'] );
  212. $this->cache = true;
  213. }
  214. }
  215. /**
  216. * Configure the settings of RainTPL
  217. *
  218. * @access public
  219. * @static
  220. * @param array|string $setting array of settings or setting name
  221. * @param mixed $value content to set in the setting (optional)
  222. */
  223. public static function configure( $setting, $value = null ){
  224. if( is_array( $setting ) )
  225. foreach( $setting as $key => $value )
  226. self::configure( $key, $value );
  227. else if( property_exists( __CLASS__, $setting ) ){
  228. self::$$setting = $value;
  229. self::$config_name_sum[ $setting ] = $value; // take trace of all config
  230. }
  231. }
  232. /**
  233. * Check if has to compile the template
  234. *
  235. * @access protected
  236. * @param string $tpl_name template name to check
  237. * @throws RainTpl_NotFoundException
  238. * @return bool return true if the template has changed
  239. */
  240. protected function check_template( $tpl_name ){
  241. if( !isset($this->tpl['checked']) ){
  242. $tpl_basename = basename( $tpl_name ); // template basename
  243. $tpl_basedir = strpos($tpl_name,"/") ? dirname($tpl_name) . '/' : null; // template basedirectory
  244. $tpl_dir = PATH . self::$tpl_dir . $tpl_basedir; // template directory
  245. $this->tpl['tpl_filename'] = $tpl_dir . $tpl_basename . '.' . self::$tpl_ext; // template filename
  246. $temp_compiled_filename = PATH . self::$cache_dir . $tpl_basename . "." . md5( $tpl_dir . serialize(self::$config_name_sum));
  247. $this->tpl['compiled_filename'] = $temp_compiled_filename . '.rtpl.php'; // cache filename
  248. $this->tpl['cache_filename'] = $temp_compiled_filename . '.s_' . $this->cache_id . '.rtpl.php'; // static cache filename
  249. // if the template doesn't exsist throw an error
  250. if( self::$check_template_update && !file_exists( $this->tpl['tpl_filename'] ) ){
  251. $e = new RainTpl_NotFoundException( 'Template '. $tpl_basename .' not found!' );
  252. throw $e->setTemplateFile($this->tpl['tpl_filename']);
  253. }
  254. // file doesn't exsist, or the template was updated, Rain will compile the template
  255. if( !file_exists( $this->tpl['compiled_filename'] ) || ( self::$check_template_update && filemtime($this->tpl['compiled_filename']) < filemtime( $this->tpl['tpl_filename'] ) ) ){
  256. $this->compileFile( $tpl_basedir, $this->tpl['tpl_filename'], PATH . self::$cache_dir, $this->tpl['compiled_filename'] );
  257. return true;
  258. }
  259. $this->tpl['checked'] = true;
  260. }
  261. }
  262. /**
  263. * execute stripslaches() on the xml block. Invoqued by preg_replace_callback function below
  264. *
  265. * @access protected
  266. * @param string $capture
  267. * @return string
  268. */
  269. protected function xml_reSubstitution($capture) {
  270. return "<?php echo '<?xml ".stripslashes($capture[1])." ?>'; ?>";
  271. }
  272. /**
  273. * Compile and write the compiled template file
  274. *
  275. * @access protected
  276. * @param string $tpl_basedir
  277. * @param string $tpl_filename
  278. * @param string $cache_dir
  279. * @param string $compiled_filename
  280. * @throws RainTpl_Exception
  281. * @return void
  282. */
  283. protected function compileFile( $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){
  284. //read template file
  285. $this->tpl['source'] = $template_code = file_get_contents( $tpl_filename );
  286. //xml substitution
  287. $template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code );
  288. //disable php tag
  289. if( !self::$php_enabled )
  290. $template_code = str_replace( array("<?","?>"), array("&lt;?","?&gt;"), $template_code );
  291. //xml re-substitution
  292. $template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code );
  293. //compile template
  294. $template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir );
  295. // fix the php-eating-newline-after-closing-tag-problem
  296. $template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled );
  297. // create directories
  298. if( !is_dir( $cache_dir ) )
  299. mkdir( $cache_dir, 0755, true );
  300. if( !is_writable( $cache_dir ) )
  301. throw new RainTpl_Exception ('Cache directory ' . $cache_dir . ' doesn\'t have write permission. Set write permission or set RAINTPL_CHECK_TEMPLATE_UPDATE to false. More details on http://www.raintpl.com/Documentation/Documentation-for-PHP-developers/Configuration/');
  302. //write compiled file
  303. file_put_contents( $compiled_filename, $template_compiled );
  304. }
  305. /**
  306. * Compile template
  307. *
  308. * @access protected
  309. * @param string $template_code
  310. * @param string $tpl_basedir
  311. * @return string
  312. */
  313. protected function compileTemplate( $template_code, $tpl_basedir ){
  314. //tag list
  315. $tag_regexp = array( 'loop' => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})',
  316. 'loop_close' => '(\{\/loop\})',
  317. 'if' => '(\{if(?: condition){0,1}="[^"]*"\})',
  318. 'elseif' => '(\{elseif(?: condition){0,1}="[^"]*"\})',
  319. 'else' => '(\{else\})',
  320. 'if_close' => '(\{\/if\})',
  321. 'function' => '(\{function="[^"]*"\})',
  322. 'noparse' => '(\{noparse\})',
  323. 'noparse_close'=> '(\{\/noparse\})',
  324. 'ignore' => '(\{ignore\}|\{\*)',
  325. 'ignore_close' => '(\{\/ignore\}|\*\})',
  326. 'include' => '(\{include="[^"]*"(?: cache="[^"]*")?\})',
  327. 'template_info'=> '(\{\$template_info\})',
  328. 'function' => '(\{function="(\w*?)(?:.*?)"\})'
  329. );
  330. $tag_regexp = "/" . join( "|", $tag_regexp ) . "/";
  331. //split the code with the tags regexp
  332. $template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
  333. //path replace (src of img, background and href of link)
  334. $template_code = $this->path_replace( $template_code, $tpl_basedir );
  335. //compile the code
  336. $compiled_code = $this->compileCode( $template_code );
  337. //return the compiled code
  338. return $compiled_code;
  339. }
  340. /**
  341. * Compile the code
  342. *
  343. * @access protected
  344. * @param string $parsed_code
  345. * @throws RainTpl_SyntaxException
  346. * @return string
  347. */
  348. protected function compileCode( $parsed_code ){
  349. //variables initialization
  350. $compiled_code = $open_if = $comment_is_open = $ignore_is_open = null;
  351. $loop_level = 0;
  352. //read all parsed code
  353. while( $html = array_shift( $parsed_code ) ){
  354. //close ignore tag
  355. if( !$comment_is_open && ( strpos( $html, '{/ignore}' ) !== FALSE || strpos( $html, '*}' ) !== FALSE ) )
  356. $ignore_is_open = false;
  357. //code between tag ignore id deleted
  358. elseif( $ignore_is_open ){
  359. //ignore the code
  360. }
  361. //close no parse tag
  362. elseif( strpos( $html, '{/noparse}' ) !== FALSE )
  363. $comment_is_open = false;
  364. //code between tag noparse is not compiled
  365. elseif( $comment_is_open )
  366. $compiled_code .= $html;
  367. //ignore
  368. elseif( strpos( $html, '{ignore}' ) !== FALSE || strpos( $html, '{*' ) !== FALSE )
  369. $ignore_is_open = true;
  370. //noparse
  371. elseif( strpos( $html, '{noparse}' ) !== FALSE )
  372. $comment_is_open = true;
  373. //include tag
  374. elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){
  375. //variables substitution
  376. $include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level );
  377. // if the cache is active
  378. if( isset($code[ 2 ]) ){
  379. //dynamic include
  380. $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
  381. 'if( $cache = $tpl->cache( $template = basename("'.$include_var.'") ) )' .
  382. ' echo $cache;' .
  383. 'else{' .
  384. ' $tpl_dir_temp = self::$tpl_dir;' .
  385. ' $tpl->assign( $this->var );' .
  386. ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
  387. ' $tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
  388. '} ?>';
  389. }
  390. else{
  391. //dynamic include
  392. $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
  393. '$tpl_dir_temp = self::$tpl_dir;' .
  394. '$tpl->assign( $this->var );' .
  395. ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
  396. '$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
  397. '?>';
  398. }
  399. }
  400. //loop
  401. elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){
  402. //increase the loop counter
  403. $loop_level++;
  404. //replace the variable in the loop
  405. $var = $this->var_replace( '$' . $code[ 1 ], $tag_left_delimiter=null, $tag_right_delimiter=null, $php_left_delimiter=null, $php_right_delimiter=null, $loop_level-1 );
  406. //loop variables
  407. $counter = "\$counter$loop_level"; // count iteration
  408. $key = "\$key$loop_level"; // key
  409. $value = "\$value$loop_level"; // value
  410. //loop code
  411. $compiled_code .= "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>";
  412. }
  413. //close loop tag
  414. elseif( strpos( $html, '{/loop}' ) !== FALSE ) {
  415. //iterator
  416. $counter = "\$counter$loop_level";
  417. //decrease the loop counter
  418. $loop_level--;
  419. //close loop code
  420. $compiled_code .= "<?php } ?>";
  421. }
  422. //if
  423. elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
  424. //increase open if counter (for intendation)
  425. $open_if++;
  426. //tag
  427. $tag = $code[ 0 ];
  428. //condition attribute
  429. $condition = $code[ 1 ];
  430. // check if there's any function disabled by black_list
  431. $this->function_check( $tag );
  432. //variable substitution into condition (no delimiter into the condition)
  433. $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
  434. //if code
  435. $compiled_code .= "<?php if( $parsed_condition ){ ?>";
  436. }
  437. //elseif
  438. elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
  439. //tag
  440. $tag = $code[ 0 ];
  441. //condition attribute
  442. $condition = $code[ 1 ];
  443. //variable substitution into condition (no delimiter into the condition)
  444. $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
  445. //elseif code
  446. $compiled_code .= "<?php }elseif( $parsed_condition ){ ?>";
  447. }
  448. //else
  449. elseif( strpos( $html, '{else}' ) !== FALSE ) {
  450. //else code
  451. $compiled_code .= '<?php }else{ ?>';
  452. }
  453. //close if tag
  454. elseif( strpos( $html, '{/if}' ) !== FALSE ) {
  455. //decrease if counter
  456. $open_if--;
  457. // close if code
  458. $compiled_code .= '<?php } ?>';
  459. }
  460. //function
  461. elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){
  462. //tag
  463. $tag = $code[ 0 ];
  464. //function
  465. $function = $code[ 1 ];
  466. // check if there's any function disabled by black_list
  467. $this->function_check( $tag );
  468. if( empty( $code[ 2 ] ) )
  469. $parsed_function = $function . "()";
  470. else
  471. // parse the function
  472. $parsed_function = $function . $this->var_replace( $code[ 2 ], $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
  473. //if code
  474. $compiled_code .= "<?php echo $parsed_function; ?>";
  475. }
  476. // show all vars
  477. elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) {
  478. //tag
  479. $tag = '{$template_info}';
  480. //if code
  481. $compiled_code .= '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>';
  482. }
  483. //all html code
  484. else{
  485. //variables substitution (es. {$title})
  486. $html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
  487. //const substitution (es. {#CONST#})
  488. $html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
  489. //functions substitution (es. {"string"|functions})
  490. $compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
  491. }
  492. }
  493. if( $open_if > 0 ) {
  494. $e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template');
  495. throw $e->setTemplateFile($this->tpl['tpl_filename']);
  496. }
  497. return $compiled_code;
  498. }
  499. /**
  500. * Reduce a path
  501. *
  502. * eg. www/library/../filepath//file => www/filepath/file
  503. *
  504. * @param string $path
  505. * @return string
  506. */
  507. protected function reduce_path( $path ){
  508. $path = str_replace( "://", "@not_replace@", $path );
  509. $path = str_replace( "//", "/", $path );
  510. $path = str_replace( "@not_replace@", "://", $path );
  511. return preg_replace('/\w+\/\.\.\//', '', $path );
  512. }
  513. /**
  514. * replace the path of image src, link href and a href
  515. *
  516. * url => template_dir/url
  517. * url# => url
  518. * http://url => http://url
  519. *
  520. * @access protected
  521. * @param string $html
  522. * @param string $tpl_basedir
  523. * @return string html substitution
  524. */
  525. protected function path_replace( $html, $tpl_basedir ){
  526. if( self::$path_replace ){
  527. $tpl_dir = self::$base_url . PATH . self::$tpl_dir . $tpl_basedir;
  528. // reduce the path
  529. $path = $this->reduce_path($tpl_dir);
  530. $exp = $sub = array();
  531. if( in_array( "img", self::$path_replace_list ) ){
  532. $exp = array( '/<img(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<img(.*?)src=(?:")([^"]+?)#(?:")/i', '/<img(.*?)src="(.*?)"/', '/<img(.*?)src=(?:\@)([^"]+?)(?:\@)/i' );
  533. $sub = array( '<img$1src=@$2://$3@', '<img$1src=@$2@', '<img$1src="' . $path . '$2"', '<img$1src="$2"' );
  534. }
  535. if( in_array( "script", self::$path_replace_list ) ){
  536. $exp = array_merge( $exp , array( '/<script(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<script(.*?)src=(?:")([^"]+?)#(?:")/i', '/<script(.*?)src="(.*?)"/', '/<script(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
  537. $sub = array_merge( $sub , array( '<script$1src=@$2://$3@', '<script$1src=@$2@', '<script$1src="' . $path . '$2"', '<script$1src="$2"' ) );
  538. }
  539. if( in_array( "link", self::$path_replace_list ) ){
  540. $exp = array_merge( $exp , array( '/<link(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<link(.*?)href=(?:")([^"]+?)#(?:")/i', '/<link(.*?)href="(.*?)"/', '/<link(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
  541. $sub = array_merge( $sub , array( '<link$1href=@$2://$3@', '<link$1href=@$2@' , '<link$1href="' . $path . '$2"', '<link$1href="$2"' ) );
  542. }
  543. if( in_array( "a", self::$path_replace_list ) ){
  544. $exp = array_merge( $exp , array( '/<a(.*?)href=(?:")(http\:\/\/|https\:\/\/|javascript:)([^"]+?)(?:")/i', '/<a(.*?)href="(.*?)"/', '/<a(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
  545. $sub = array_merge( $sub , array( '<a$1href=@$2$3@', '<a$1href="' . self::$base_url . '$2"', '<a$1href="$2"' ) );
  546. }
  547. if( in_array( "input", self::$path_replace_list ) ){
  548. $exp = array_merge( $exp , array( '/<input(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<input(.*?)src=(?:")([^"]+?)#(?:")/i', '/<input(.*?)src="(.*?)"/', '/<input(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
  549. $sub = array_merge( $sub , array( '<input$1src=@$2://$3@', '<input$1src=@$2@', '<input$1src="' . $path . '$2"', '<input$1src="$2"' ) );
  550. }
  551. return preg_replace( $exp, $sub, $html );
  552. }
  553. else
  554. return $html;
  555. }
  556. /**
  557. * replace constants
  558. *
  559. * @access public
  560. * @param string $html
  561. * @param string $tag_left_delimiter
  562. * @param string $tag_right_delimiter
  563. * @param string $php_left_delimiter (optional)
  564. * @param string $php_right_delimiter (optional)
  565. * @param string $loop_level (optional)
  566. * @param string $echo (optional)
  567. * @return string
  568. */
  569. public function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
  570. // const
  571. return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html );
  572. }
  573. /**
  574. * replace functions/modifiers on constants and strings
  575. *
  576. * @access public
  577. * @param string $html
  578. * @param string $tag_left_delimiter
  579. * @param string $tag_right_delimiter
  580. * @param string $php_left_delimiter (optional)
  581. * @param string $php_right_delimiter (optional)
  582. * @param string $loop_level (optional)
  583. * @param string $echo (optional)
  584. * @return string
  585. */
  586. public function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
  587. preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches );
  588. for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){
  589. //complete tag ex: {$news.title|substr:0,100}
  590. $tag = $matches[ 0 ][ $i ];
  591. //variable name ex: news.title
  592. $var = $matches[ 1 ][ $i ];
  593. //function and parameters associate to the variable ex: substr:0,100
  594. $extra_var = $matches[ 2 ][ $i ];
  595. // check if there's any function disabled by black_list
  596. $this->function_check( $tag );
  597. $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
  598. // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
  599. $is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var );
  600. //function associate to variable
  601. $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
  602. //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
  603. $temp = preg_split( "/\.|\[|\-\>/", $var );
  604. //variable name
  605. $var_name = $temp[ 0 ];
  606. //variable path
  607. $variable_path = substr( $var, strlen( $var_name ) );
  608. //parentesis transform [ e ] in [" e in "]
  609. $variable_path = str_replace( '[', '["', $variable_path );
  610. $variable_path = str_replace( ']', '"]', $variable_path );
  611. //transform .$variable in ["$variable"]
  612. $variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path );
  613. //transform [variable] in ["variable"]
  614. $variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path );
  615. //if there's a function
  616. if( $function_var ){
  617. // check if there's a function or a static method and separate, function by parameters
  618. $function_var = str_replace("::", "@double_dot@", $function_var );
  619. // get the position of the first :
  620. if( $dot_position = strpos( $function_var, ":" ) ){
  621. // get the function and the parameters
  622. $function = substr( $function_var, 0, $dot_position );
  623. $params = substr( $function_var, $dot_position+1 );
  624. }
  625. else{
  626. //get the function
  627. $function = str_replace( "@double_dot@", "::", $function_var );
  628. $params = null;
  629. }
  630. // replace back the @double_dot@ with ::
  631. $function = str_replace( "@double_dot@", "::", $function );
  632. $params = str_replace( "@double_dot@", "::", $params );
  633. }
  634. else
  635. $function = $params = null;
  636. $php_var = $var_name . $variable_path;
  637. // compile the variable for php
  638. if( isset( $function ) ){
  639. if( $php_var )
  640. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
  641. else
  642. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter;
  643. }
  644. else
  645. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
  646. $html = str_replace( $tag, $php_var, $html );
  647. }
  648. return $html;
  649. }
  650. /**
  651. * replace variables
  652. *
  653. * @access public
  654. * @param string $html
  655. * @param string $tag_left_delimiter
  656. * @param string $tag_right_delimiter
  657. * @param string $php_left_delimiter (optional)
  658. * @param string $php_right_delimiter (optional)
  659. * @param string $loop_level (optional)
  660. * @param string $echo (optional)
  661. * @return string
  662. */
  663. public function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
  664. //all variables
  665. if( preg_match_all( '/' . $tag_left_delimiter . '\$(\w+(?:\.\${0,1}[A-Za-z0-9_]+)*(?:(?:\[\${0,1}[A-Za-z0-9_]+\])|(?:\-\>\${0,1}[A-Za-z0-9_]+))*)(.*?)' . $tag_right_delimiter . '/', $html, $matches ) ){
  666. for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ )
  667. $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]);
  668. foreach( $parsed as $tag => $array ){
  669. //variable name ex: news.title
  670. $var = $array['var'];
  671. //function and parameters associate to the variable ex: substr:0,100
  672. $extra_var = $array['extra_var'];
  673. // check if there's any function disabled by black_list
  674. $this->function_check( $tag );
  675. $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
  676. // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
  677. $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var );
  678. //function associate to variable
  679. $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
  680. //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
  681. $temp = preg_split( "/\.|\[|\-\>/", $var );
  682. //variable name
  683. $var_name = $temp[ 0 ];
  684. //variable path
  685. $variable_path = substr( $var, strlen( $var_name ) );
  686. //parentesis transform [ e ] in [" e in "]
  687. $variable_path = str_replace( '[', '["', $variable_path );
  688. $variable_path = str_replace( ']', '"]', $variable_path );
  689. //transform .$variable in ["$variable"] and .variable in ["variable"]
  690. $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path );
  691. // if is an assignment also assign the variable to $this->var['value']
  692. if( $is_init_variable )
  693. $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var;
  694. //if there's a function
  695. if( $function_var ){
  696. // check if there's a function or a static method and separate, function by parameters
  697. $function_var = str_replace("::", "@double_dot@", $function_var );
  698. // get the position of the first :
  699. if( $dot_position = strpos( $function_var, ":" ) ){
  700. // get the function and the parameters
  701. $function = substr( $function_var, 0, $dot_position );
  702. $params = substr( $function_var, $dot_position+1 );
  703. }
  704. else{
  705. //get the function
  706. $function = str_replace( "@double_dot@", "::", $function_var );
  707. $params = null;
  708. }
  709. // replace back the @double_dot@ with ::
  710. $function = str_replace( "@double_dot@", "::", $function );
  711. $params = str_replace( "@double_dot@", "::", $params );
  712. }
  713. else
  714. $function = $params = null;
  715. //if it is inside a loop
  716. if( $loop_level ){
  717. //verify the variable name
  718. if( $var_name == 'key' )
  719. $php_var = '$key' . $loop_level;
  720. elseif( $var_name == 'value' )
  721. $php_var = '$value' . $loop_level . $variable_path;
  722. elseif( $var_name == 'counter' )
  723. $php_var = '$counter' . $loop_level;
  724. else
  725. $php_var = '$' . $var_name . $variable_path;
  726. }else
  727. $php_var = '$' . $var_name . $variable_path;
  728. // compile the variable for php
  729. if( isset( $function ) )
  730. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
  731. else
  732. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
  733. $html = str_replace( $tag, $php_var, $html );
  734. }
  735. }
  736. return $html;
  737. }
  738. /**
  739. * Check if function is in black list (sandbox)
  740. *
  741. * @access protected
  742. * @param string $code
  743. * @throws RainTpl_SyntaxException
  744. * @return void
  745. */
  746. protected function function_check( $code ){
  747. $preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#';
  748. // check if the function is in the black list (or not in white list)
  749. if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){
  750. // find the line of the error
  751. $line = 0;
  752. $rows=explode("\n",$this->tpl['source']);
  753. while( !strpos($rows[$line],$code) )
  754. $line++;
  755. // stop the execution of the script
  756. $e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template');
  757. throw $e->setTemplateFile($this->tpl['tpl_filename'])
  758. ->setTag($code)
  759. ->setTemplateLine($line);
  760. }
  761. }
  762. /**
  763. * Prints debug info about exception or passes it further if debug is disabled.
  764. *
  765. * @access protected
  766. * @param RainTpl_Exception $e
  767. * @return string
  768. */
  769. protected function printDebug(RainTpl_Exception $e){
  770. if (!self::$debug) {
  771. throw $e;
  772. }
  773. $output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>',
  774. get_class($e),
  775. $e->getMessage(),
  776. $e->getTemplateFile()
  777. );
  778. if ($e instanceof RainTpl_SyntaxException) {
  779. if (null !== $e->getTemplateLine()) {
  780. $output .= '<p>line: ' . $e->getTemplateLine() . '</p>';
  781. }
  782. if (null !== $e->getTag()) {
  783. $output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>';
  784. }
  785. if (null !== $e->getTemplateLine() && null !== $e->getTag()) {
  786. $rows=explode("\n", htmlspecialchars($this->tpl['source']));
  787. $rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>';
  788. $output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>';
  789. }
  790. }
  791. $output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>',
  792. $e->getFile(), $e->getLine(),
  793. nl2br(htmlspecialchars($e->getTraceAsString()))
  794. );
  795. return $output;
  796. }
  797. }
  798. /**
  799. * Basic Rain tpl exception.
  800. */
  801. class RainTpl_Exception extends Exception{
  802. /**
  803. * Path of template file with error.
  804. */
  805. protected $templateFile = '';
  806. /**
  807. * Returns path of template file with error.
  808. *
  809. * @return string
  810. */
  811. public function getTemplateFile()
  812. {
  813. return $this->templateFile;
  814. }
  815. /**
  816. * Sets path of template file with error.
  817. *
  818. * @param string $templateFile
  819. * @return RainTpl_Exception
  820. */
  821. public function setTemplateFile($templateFile)
  822. {
  823. $this->templateFile = (string) $templateFile;
  824. return $this;
  825. }
  826. }
  827. /**
  828. * Exception thrown when template file does not exists.
  829. */
  830. class RainTpl_NotFoundException extends RainTpl_Exception{
  831. }
  832. /**
  833. * Exception thrown when syntax error occurs.
  834. */
  835. class RainTpl_SyntaxException extends RainTpl_Exception{
  836. /**
  837. * Line in template file where error has occured.
  838. *
  839. * @var int | null
  840. */
  841. protected $templateLine = null;
  842. /**
  843. * Tag which caused an error.
  844. *
  845. * @var string | null
  846. */
  847. protected $tag = null;
  848. /**
  849. * Returns line in template file where error has occured
  850. * or null if line is not defined.
  851. *
  852. * @return int | null
  853. */
  854. public function getTemplateLine()
  855. {
  856. return $this->templateLine;
  857. }
  858. /**
  859. * Sets line in template file where error has occured.
  860. *
  861. * @param int $templateLine
  862. * @return RainTpl_SyntaxException
  863. */
  864. public function setTemplateLine($templateLine)
  865. {
  866. $this->templateLine = (int) $templateLine;
  867. return $this;
  868. }
  869. /**
  870. * Returns tag which caused an error.
  871. *
  872. * @return string
  873. */
  874. public function getTag()
  875. {
  876. return $this->tag;
  877. }
  878. /**
  879. * Sets tag which caused an error.
  880. *
  881. * @param string $tag
  882. * @return RainTpl_SyntaxException
  883. */
  884. public function setTag($tag)
  885. {
  886. $this->tag = (string) $tag;
  887. return $this;
  888. }
  889. }
  890. // -- end