RainTPL.php 36 KB

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