rain.tpl.class.php 34 KB

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