RainTPL.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  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_basename, $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_basename
  277. * @param string $tpl_basedir
  278. * @param string $tpl_filename
  279. * @param string $cache_dir
  280. * @param string $compiled_filename
  281. * @throws RainTpl_Exception
  282. * @return void
  283. */
  284. protected function compileFile( $tpl_basename, $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){
  285. //read template file
  286. $this->tpl['source'] = $template_code = file_get_contents( $tpl_filename );
  287. //xml substitution
  288. $template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code );
  289. //disable php tag
  290. if( !self::$php_enabled )
  291. $template_code = str_replace( array("<?","?>"), array("&lt;?","?&gt;"), $template_code );
  292. //xml re-substitution
  293. $template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code );
  294. //compile template
  295. $template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir );
  296. // fix the php-eating-newline-after-closing-tag-problem
  297. $template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled );
  298. // create directories
  299. if( !is_dir( $cache_dir ) )
  300. mkdir( $cache_dir, 0755, true );
  301. if( !is_writable( $cache_dir ) )
  302. 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/');
  303. //write compiled file
  304. file_put_contents( $compiled_filename, $template_compiled );
  305. }
  306. /**
  307. * Compile template
  308. *
  309. * @access protected
  310. * @param string $template_code
  311. * @param string $tpl_basedir
  312. * @return string
  313. */
  314. protected function compileTemplate( $template_code, $tpl_basedir ){
  315. //tag list
  316. $tag_regexp = array( 'loop' => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})',
  317. 'loop_close' => '(\{\/loop\})',
  318. 'if' => '(\{if(?: condition){0,1}="[^"]*"\})',
  319. 'elseif' => '(\{elseif(?: condition){0,1}="[^"]*"\})',
  320. 'else' => '(\{else\})',
  321. 'if_close' => '(\{\/if\})',
  322. 'function' => '(\{function="[^"]*"\})',
  323. 'noparse' => '(\{noparse\})',
  324. 'noparse_close'=> '(\{\/noparse\})',
  325. 'ignore' => '(\{ignore\}|\{\*)',
  326. 'ignore_close' => '(\{\/ignore\}|\*\})',
  327. 'include' => '(\{include="[^"]*"(?: cache="[^"]*")?\})',
  328. 'template_info'=> '(\{\$template_info\})',
  329. 'function' => '(\{function="(\w*?)(?:.*?)"\})'
  330. );
  331. $tag_regexp = "/" . join( "|", $tag_regexp ) . "/";
  332. //split the code with the tags regexp
  333. $template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
  334. //path replace (src of img, background and href of link)
  335. $template_code = $this->path_replace( $template_code, $tpl_basedir );
  336. //compile the code
  337. $compiled_code = $this->compileCode( $template_code );
  338. //return the compiled code
  339. return $compiled_code;
  340. }
  341. /**
  342. * Compile the code
  343. *
  344. * @access protected
  345. * @param string $parsed_code
  346. * @throws RainTpl_SyntaxException
  347. * @return string
  348. */
  349. protected function compileCode( $parsed_code ){
  350. //variables initialization
  351. $compiled_code = $open_if = $comment_is_open = $ignore_is_open = null;
  352. $loop_level = 0;
  353. //read all parsed code
  354. while( $html = array_shift( $parsed_code ) ){
  355. //close ignore tag
  356. if( !$comment_is_open && ( strpos( $html, '{/ignore}' ) !== FALSE || strpos( $html, '*}' ) !== FALSE ) )
  357. $ignore_is_open = false;
  358. //code between tag ignore id deleted
  359. elseif( $ignore_is_open ){
  360. //ignore the code
  361. }
  362. //close no parse tag
  363. elseif( strpos( $html, '{/noparse}' ) !== FALSE )
  364. $comment_is_open = false;
  365. //code between tag noparse is not compiled
  366. elseif( $comment_is_open )
  367. $compiled_code .= $html;
  368. //ignore
  369. elseif( strpos( $html, '{ignore}' ) !== FALSE || strpos( $html, '{*' ) !== FALSE )
  370. $ignore_is_open = true;
  371. //noparse
  372. elseif( strpos( $html, '{noparse}' ) !== FALSE )
  373. $comment_is_open = true;
  374. //include tag
  375. elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){
  376. //variables substitution
  377. $include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level );
  378. // if the cache is active
  379. if( isset($code[ 2 ]) ){
  380. //dynamic include
  381. $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
  382. 'if( $cache = $tpl->cache( $template = basename("'.$include_var.'") ) )' .
  383. ' echo $cache;' .
  384. 'else{' .
  385. ' $tpl_dir_temp = self::$tpl_dir;' .
  386. ' $tpl->assign( $this->var );' .
  387. ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
  388. ' $tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
  389. '} ?>';
  390. }
  391. else{
  392. //dynamic include
  393. $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
  394. '$tpl_dir_temp = self::$tpl_dir;' .
  395. '$tpl->assign( $this->var );' .
  396. ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
  397. '$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
  398. '?>';
  399. }
  400. }
  401. //loop
  402. elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){
  403. //increase the loop counter
  404. $loop_level++;
  405. //replace the variable in the loop
  406. $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 );
  407. //loop variables
  408. $counter = "\$counter$loop_level"; // count iteration
  409. $key = "\$key$loop_level"; // key
  410. $value = "\$value$loop_level"; // value
  411. //loop code
  412. $compiled_code .= "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>";
  413. }
  414. //close loop tag
  415. elseif( strpos( $html, '{/loop}' ) !== FALSE ) {
  416. //iterator
  417. $counter = "\$counter$loop_level";
  418. //decrease the loop counter
  419. $loop_level--;
  420. //close loop code
  421. $compiled_code .= "<?php } ?>";
  422. }
  423. //if
  424. elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
  425. //increase open if counter (for intendation)
  426. $open_if++;
  427. //tag
  428. $tag = $code[ 0 ];
  429. //condition attribute
  430. $condition = $code[ 1 ];
  431. // check if there's any function disabled by black_list
  432. $this->function_check( $tag );
  433. //variable substitution into condition (no delimiter into the condition)
  434. $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
  435. //if code
  436. $compiled_code .= "<?php if( $parsed_condition ){ ?>";
  437. }
  438. //elseif
  439. elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
  440. //tag
  441. $tag = $code[ 0 ];
  442. //condition attribute
  443. $condition = $code[ 1 ];
  444. //variable substitution into condition (no delimiter into the condition)
  445. $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
  446. //elseif code
  447. $compiled_code .= "<?php }elseif( $parsed_condition ){ ?>";
  448. }
  449. //else
  450. elseif( strpos( $html, '{else}' ) !== FALSE ) {
  451. //else code
  452. $compiled_code .= '<?php }else{ ?>';
  453. }
  454. //close if tag
  455. elseif( strpos( $html, '{/if}' ) !== FALSE ) {
  456. //decrease if counter
  457. $open_if--;
  458. // close if code
  459. $compiled_code .= '<?php } ?>';
  460. }
  461. //function
  462. elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){
  463. //tag
  464. $tag = $code[ 0 ];
  465. //function
  466. $function = $code[ 1 ];
  467. // check if there's any function disabled by black_list
  468. $this->function_check( $tag );
  469. if( empty( $code[ 2 ] ) )
  470. $parsed_function = $function . "()";
  471. else
  472. // parse the function
  473. $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 );
  474. //if code
  475. $compiled_code .= "<?php echo $parsed_function; ?>";
  476. }
  477. // show all vars
  478. elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) {
  479. //tag
  480. $tag = '{$template_info}';
  481. //if code
  482. $compiled_code .= '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>';
  483. }
  484. //all html code
  485. else{
  486. //variables substitution (es. {$title})
  487. $html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
  488. //const substitution (es. {#CONST#})
  489. $html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
  490. //functions substitution (es. {"string"|functions})
  491. $compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
  492. }
  493. }
  494. if( $open_if > 0 ) {
  495. $e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template');
  496. throw $e->setTemplateFile($this->tpl['tpl_filename']);
  497. }
  498. return $compiled_code;
  499. }
  500. /**
  501. * Reduce a path
  502. *
  503. * eg. www/library/../filepath//file => www/filepath/file
  504. *
  505. * @param string $path
  506. * @return string
  507. */
  508. protected function reduce_path( $path ){
  509. $path = str_replace( "://", "@not_replace@", $path );
  510. $path = str_replace( "//", "/", $path );
  511. $path = str_replace( "@not_replace@", "://", $path );
  512. return preg_replace('/\w+\/\.\.\//', '', $path );
  513. }
  514. /**
  515. * replace the path of image src, link href and a href
  516. *
  517. * url => template_dir/url
  518. * url# => url
  519. * http://url => http://url
  520. *
  521. * @access protected
  522. * @param string $html
  523. * @param string $tpl_basedir
  524. * @return string html substitution
  525. */
  526. protected function path_replace( $html, $tpl_basedir ){
  527. if( self::$path_replace ){
  528. $tpl_dir = self::$base_url . PATH . self::$tpl_dir . $tpl_basedir;
  529. // reduce the path
  530. $path = $this->reduce_path($tpl_dir);
  531. $exp = $sub = array();
  532. if( in_array( "img", self::$path_replace_list ) ){
  533. $exp = array( '/<img(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<img(.*?)src=(?:")([^"]+?)#(?:")/i', '/<img(.*?)src="(.*?)"/', '/<img(.*?)src=(?:\@)([^"]+?)(?:\@)/i' );
  534. $sub = array( '<img$1src=@$2://$3@', '<img$1src=@$2@', '<img$1src="' . $path . '$2"', '<img$1src="$2"' );
  535. }
  536. if( in_array( "script", self::$path_replace_list ) ){
  537. $exp = array_merge( $exp , array( '/<script(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<script(.*?)src=(?:")([^"]+?)#(?:")/i', '/<script(.*?)src="(.*?)"/', '/<script(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
  538. $sub = array_merge( $sub , array( '<script$1src=@$2://$3@', '<script$1src=@$2@', '<script$1src="' . $path . '$2"', '<script$1src="$2"' ) );
  539. }
  540. if( in_array( "link", self::$path_replace_list ) ){
  541. $exp = array_merge( $exp , array( '/<link(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<link(.*?)href=(?:")([^"]+?)#(?:")/i', '/<link(.*?)href="(.*?)"/', '/<link(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
  542. $sub = array_merge( $sub , array( '<link$1href=@$2://$3@', '<link$1href=@$2@' , '<link$1href="' . $path . '$2"', '<link$1href="$2"' ) );
  543. }
  544. if( in_array( "a", self::$path_replace_list ) ){
  545. $exp = array_merge( $exp , array( '/<a(.*?)href=(?:")(http\:\/\/|https\:\/\/|javascript:)([^"]+?)(?:")/i', '/<a(.*?)href="(.*?)"/', '/<a(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
  546. $sub = array_merge( $sub , array( '<a$1href=@$2$3@', '<a$1href="' . self::$base_url . '$2"', '<a$1href="$2"' ) );
  547. }
  548. if( in_array( "input", self::$path_replace_list ) ){
  549. $exp = array_merge( $exp , array( '/<input(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<input(.*?)src=(?:")([^"]+?)#(?:")/i', '/<input(.*?)src="(.*?)"/', '/<input(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
  550. $sub = array_merge( $sub , array( '<input$1src=@$2://$3@', '<input$1src=@$2@', '<input$1src="' . $path . '$2"', '<input$1src="$2"' ) );
  551. }
  552. return preg_replace( $exp, $sub, $html );
  553. }
  554. else
  555. return $html;
  556. }
  557. /**
  558. * replace constants
  559. *
  560. * @access public
  561. * @param string $html
  562. * @param string $tag_left_delimiter
  563. * @param string $tag_right_delimiter
  564. * @param string $php_left_delimiter (optional)
  565. * @param string $php_right_delimiter (optional)
  566. * @param string $loop_level (optional)
  567. * @param string $echo (optional)
  568. * @return string
  569. */
  570. public function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
  571. // const
  572. return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html );
  573. }
  574. /**
  575. * replace functions/modifiers on constants and strings
  576. *
  577. * @access public
  578. * @param string $html
  579. * @param string $tag_left_delimiter
  580. * @param string $tag_right_delimiter
  581. * @param string $php_left_delimiter (optional)
  582. * @param string $php_right_delimiter (optional)
  583. * @param string $loop_level (optional)
  584. * @param string $echo (optional)
  585. * @return string
  586. */
  587. public function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
  588. preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches );
  589. for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){
  590. //complete tag ex: {$news.title|substr:0,100}
  591. $tag = $matches[ 0 ][ $i ];
  592. //variable name ex: news.title
  593. $var = $matches[ 1 ][ $i ];
  594. //function and parameters associate to the variable ex: substr:0,100
  595. $extra_var = $matches[ 2 ][ $i ];
  596. // check if there's any function disabled by black_list
  597. $this->function_check( $tag );
  598. $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
  599. // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
  600. $is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var );
  601. //function associate to variable
  602. $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
  603. //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
  604. $temp = preg_split( "/\.|\[|\-\>/", $var );
  605. //variable name
  606. $var_name = $temp[ 0 ];
  607. //variable path
  608. $variable_path = substr( $var, strlen( $var_name ) );
  609. //parentesis transform [ e ] in [" e in "]
  610. $variable_path = str_replace( '[', '["', $variable_path );
  611. $variable_path = str_replace( ']', '"]', $variable_path );
  612. //transform .$variable in ["$variable"]
  613. $variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path );
  614. //transform [variable] in ["variable"]
  615. $variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path );
  616. //if there's a function
  617. if( $function_var ){
  618. // check if there's a function or a static method and separate, function by parameters
  619. $function_var = str_replace("::", "@double_dot@", $function_var );
  620. // get the position of the first :
  621. if( $dot_position = strpos( $function_var, ":" ) ){
  622. // get the function and the parameters
  623. $function = substr( $function_var, 0, $dot_position );
  624. $params = substr( $function_var, $dot_position+1 );
  625. }
  626. else{
  627. //get the function
  628. $function = str_replace( "@double_dot@", "::", $function_var );
  629. $params = null;
  630. }
  631. // replace back the @double_dot@ with ::
  632. $function = str_replace( "@double_dot@", "::", $function );
  633. $params = str_replace( "@double_dot@", "::", $params );
  634. }
  635. else
  636. $function = $params = null;
  637. $php_var = $var_name . $variable_path;
  638. // compile the variable for php
  639. if( isset( $function ) ){
  640. if( $php_var )
  641. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
  642. else
  643. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter;
  644. }
  645. else
  646. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
  647. $html = str_replace( $tag, $php_var, $html );
  648. }
  649. return $html;
  650. }
  651. /**
  652. * replace variables
  653. *
  654. * @access public
  655. * @param string $html
  656. * @param string $tag_left_delimiter
  657. * @param string $tag_right_delimiter
  658. * @param string $php_left_delimiter (optional)
  659. * @param string $php_right_delimiter (optional)
  660. * @param string $loop_level (optional)
  661. * @param string $echo (optional)
  662. * @return string
  663. */
  664. public function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
  665. //all variables
  666. 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 ) ){
  667. for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ )
  668. $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]);
  669. foreach( $parsed as $tag => $array ){
  670. //variable name ex: news.title
  671. $var = $array['var'];
  672. //function and parameters associate to the variable ex: substr:0,100
  673. $extra_var = $array['extra_var'];
  674. // check if there's any function disabled by black_list
  675. $this->function_check( $tag );
  676. $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
  677. // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
  678. $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var );
  679. //function associate to variable
  680. $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
  681. //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
  682. $temp = preg_split( "/\.|\[|\-\>/", $var );
  683. //variable name
  684. $var_name = $temp[ 0 ];
  685. //variable path
  686. $variable_path = substr( $var, strlen( $var_name ) );
  687. //parentesis transform [ e ] in [" e in "]
  688. $variable_path = str_replace( '[', '["', $variable_path );
  689. $variable_path = str_replace( ']', '"]', $variable_path );
  690. //transform .$variable in ["$variable"] and .variable in ["variable"]
  691. $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path );
  692. // if is an assignment also assign the variable to $this->var['value']
  693. if( $is_init_variable )
  694. $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var;
  695. //if there's a function
  696. if( $function_var ){
  697. // check if there's a function or a static method and separate, function by parameters
  698. $function_var = str_replace("::", "@double_dot@", $function_var );
  699. // get the position of the first :
  700. if( $dot_position = strpos( $function_var, ":" ) ){
  701. // get the function and the parameters
  702. $function = substr( $function_var, 0, $dot_position );
  703. $params = substr( $function_var, $dot_position+1 );
  704. }
  705. else{
  706. //get the function
  707. $function = str_replace( "@double_dot@", "::", $function_var );
  708. $params = null;
  709. }
  710. // replace back the @double_dot@ with ::
  711. $function = str_replace( "@double_dot@", "::", $function );
  712. $params = str_replace( "@double_dot@", "::", $params );
  713. }
  714. else
  715. $function = $params = null;
  716. //if it is inside a loop
  717. if( $loop_level ){
  718. //verify the variable name
  719. if( $var_name == 'key' )
  720. $php_var = '$key' . $loop_level;
  721. elseif( $var_name == 'value' )
  722. $php_var = '$value' . $loop_level . $variable_path;
  723. elseif( $var_name == 'counter' )
  724. $php_var = '$counter' . $loop_level;
  725. else
  726. $php_var = '$' . $var_name . $variable_path;
  727. }else
  728. $php_var = '$' . $var_name . $variable_path;
  729. // compile the variable for php
  730. if( isset( $function ) )
  731. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
  732. else
  733. $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
  734. $html = str_replace( $tag, $php_var, $html );
  735. }
  736. }
  737. return $html;
  738. }
  739. /**
  740. * Check if function is in black list (sandbox)
  741. *
  742. * @access protected
  743. * @param string $code
  744. * @throws RainTpl_SyntaxException
  745. * @return void
  746. */
  747. protected function function_check( $code ){
  748. $preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#';
  749. // check if the function is in the black list (or not in white list)
  750. if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){
  751. // find the line of the error
  752. $line = 0;
  753. $rows=explode("\n",$this->tpl['source']);
  754. while( !strpos($rows[$line],$code) )
  755. $line++;
  756. // stop the execution of the script
  757. $e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template');
  758. throw $e->setTemplateFile($this->tpl['tpl_filename'])
  759. ->setTag($code)
  760. ->setTemplateLine($line);
  761. }
  762. }
  763. /**
  764. * Prints debug info about exception or passes it further if debug is disabled.
  765. *
  766. * @access protected
  767. * @param RainTpl_Exception $e
  768. * @return string
  769. */
  770. protected function printDebug(RainTpl_Exception $e){
  771. if (!self::$debug) {
  772. throw $e;
  773. }
  774. $output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>',
  775. get_class($e),
  776. $e->getMessage(),
  777. $e->getTemplateFile()
  778. );
  779. if ($e instanceof RainTpl_SyntaxException) {
  780. if (null != $e->getTemplateLine()) {
  781. $output .= '<p>line: ' . $e->getTemplateLine() . '</p>';
  782. }
  783. if (null != $e->getTag()) {
  784. $output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>';
  785. }
  786. if (null != $e->getTemplateLine() && null != $e->getTag()) {
  787. $rows=explode("\n", htmlspecialchars($this->tpl['source']));
  788. $rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>';
  789. $output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>';
  790. }
  791. }
  792. $output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>',
  793. $e->getFile(), $e->getLine(),
  794. nl2br(htmlspecialchars($e->getTraceAsString()))
  795. );
  796. return $output;
  797. }
  798. }
  799. /**
  800. * Basic Rain tpl exception.
  801. */
  802. class RainTpl_Exception extends Exception{
  803. /**
  804. * Path of template file with error.
  805. */
  806. protected $templateFile = '';
  807. /**
  808. * Returns path of template file with error.
  809. *
  810. * @return string
  811. */
  812. public function getTemplateFile()
  813. {
  814. return $this->templateFile;
  815. }
  816. /**
  817. * Sets path of template file with error.
  818. *
  819. * @param string $templateFile
  820. * @return RainTpl_Exception
  821. */
  822. public function setTemplateFile($templateFile)
  823. {
  824. $this->templateFile = (string) $templateFile;
  825. return $this;
  826. }
  827. }
  828. /**
  829. * Exception thrown when template file does not exists.
  830. */
  831. class RainTpl_NotFoundException extends RainTpl_Exception{
  832. }
  833. /**
  834. * Exception thrown when syntax error occurs.
  835. */
  836. class RainTpl_SyntaxException extends RainTpl_Exception{
  837. /**
  838. * Line in template file where error has occured.
  839. *
  840. * @var int | null
  841. */
  842. protected $templateLine = null;
  843. /**
  844. * Tag which caused an error.
  845. *
  846. * @var string | null
  847. */
  848. protected $tag = null;
  849. /**
  850. * Returns line in template file where error has occured
  851. * or null if line is not defined.
  852. *
  853. * @return int | null
  854. */
  855. public function getTemplateLine()
  856. {
  857. return $this->templateLine;
  858. }
  859. /**
  860. * Sets line in template file where error has occured.
  861. *
  862. * @param int $templateLine
  863. * @return RainTpl_SyntaxException
  864. */
  865. public function setTemplateLine($templateLine)
  866. {
  867. $this->templateLine = (int) $templateLine;
  868. return $this;
  869. }
  870. /**
  871. * Returns tag which caused an error.
  872. *
  873. * @return string
  874. */
  875. public function getTag()
  876. {
  877. return $this->tag;
  878. }
  879. /**
  880. * Sets tag which caused an error.
  881. *
  882. * @param string $tag
  883. * @return RainTpl_SyntaxException
  884. */
  885. public function setTag($tag)
  886. {
  887. $this->tag = (string) $tag;
  888. return $this;
  889. }
  890. }
  891. /**
  892. * shorthand translate function for use in templates
  893. *
  894. * alias for i18n::translate()
  895. *
  896. * @access public
  897. * @param string $messageId
  898. * @param mixed $args one or multiple parameters injected into placeholders
  899. * @return string
  900. */
  901. function t() {
  902. return call_user_func_array(array('i18n', 'translate'), func_get_args());
  903. }
  904. // -- end