]> jfr.im git - uguu.git/blob - rain/rain.tpl.class.php
Update rain.tpl.class.php
[uguu.git] / rain / rain.tpl.class.php
1 <?php
2
3 /**
4 * RainTPL
5 * -------
6 * Realized by Federico Ulfo & maintained by the Rain Team
7 * Distributed under the MIT license http://www.opensource.org/licenses/mit-license.php
8 *
9 * @version 2.7.2
10 */
11
12
13 class RainTPL{
14
15 // -------------------------
16 // CONFIGURATION
17 // -------------------------
18
19 /**
20 * Template directory
21 *
22 * @var string
23 */
24 static $tpl_dir = "tpl/";
25
26
27 /**
28 * Cache directory. Is the directory where RainTPL will compile the template and save the cache
29 *
30 * @var string
31 */
32 static $cache_dir = "tmp/";
33
34
35 /**
36 * Template base URL. RainTPL will add this URL to the relative paths of element selected in $path_replace_list.
37 *
38 * @var string
39 */
40 static $base_url = null;
41
42
43 /**
44 * Template extension.
45 *
46 * @var string
47 */
48 static $tpl_ext = "html";
49
50
51 /**
52 * 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="...">)
53 * Set true to enable the path replace.
54 *
55 * @var unknown_type
56 */
57 static $path_replace = true;
58
59
60 /**
61 * You can set what the path_replace method will replace.
62 * Avaible options: a, img, link, script, input
63 *
64 * @var array
65 */
66 static $path_replace_list = array( 'a', 'img', 'link', 'script', 'input' );
67
68
69 /**
70 * You can define in the black list what string are disabled into the template tags
71 *
72 * @var unknown_type
73 */
74 static $black_list = array( '\$this', 'raintpl::', 'self::', '_SESSION', '_SERVER', '_ENV', 'eval', 'exec', 'unlink', 'rmdir' );
75
76
77 /**
78 * Check template.
79 * true: checks template update time, if changed it compile them
80 * false: loads the compiled template. Set false if server doesn't have write permission for cache_directory.
81 *
82 */
83 static $check_template_update = true;
84
85
86 /**
87 * PHP tags <? ?>
88 * True: php tags are enabled into the template
89 * False: php tags are disabled into the template and rendered as html
90 *
91 * @var bool
92 */
93 static $php_enabled = true;
94
95
96 /**
97 * Debug mode flag.
98 * True: debug mode is used, syntax errors are displayed directly in template. Execution of script is not terminated.
99 * False: exception is thrown on found error.
100 *
101 * @var bool
102 */
103 static $debug = false;
104
105 // -------------------------
106
107
108 // -------------------------
109 // RAINTPL VARIABLES
110 // -------------------------
111
112 /**
113 * Is the array where RainTPL keep the variables assigned
114 *
115 * @var array
116 */
117 public $var = array();
118
119 protected $tpl = array(), // variables to keep the template directories and info
120 $cache = false, // static cache enabled / disabled
121 $cache_id = null; // identify only one cache
122
123 protected static $config_name_sum = array(); // takes all the config to create the md5 of the file
124
125 // -------------------------
126
127
128
129 const CACHE_EXPIRE_TIME = 3600; // default cache expire time = hour
130
131
132
133 /**
134 * Assign variable
135 * eg. $t->assign('name','mickey');
136 *
137 * @param mixed $variable_name Name of template variable or associative array name/value
138 * @param mixed $value value assigned to this variable. Not set if variable_name is an associative array
139 */
140
141 function assign( $variable, $value = null ){
142 if( is_array( $variable ) )
143 $this->var = $variable + $this->var;
144 else
145 $this->var[ $variable ] = $value;
146 }
147
148
149
150 /**
151 * Draw the template
152 * eg. $html = $tpl->draw( 'demo', TRUE ); // return template in string
153 * or $tpl->draw( $tpl_name ); // echo the template
154 *
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
160 function draw( $tpl_name, $return_string = false ){
161
162 try {
163 // compile the template if necessary and set the template filepath
164 $this->check_template( $tpl_name );
165 } catch (RainTpl_Exception $e) {
166 $output = $this->printDebug($e);
167 die($output);
168 }
169
170 // Cache is off and, return_string is false
171 // Rain just echo the template
172
173 if( !$this->cache && !$return_string ){
174 extract( $this->var );
175 include $this->tpl['compiled_filename'];
176 unset( $this->tpl );
177 }
178
179
180 // cache or return_string are enabled
181 // rain get the output buffer to save the output in the cache or to return it as string
182
183 else{
184
185 //----------------------
186 // get the output buffer
187 //----------------------
188 ob_start();
189 extract( $this->var );
190 include $this->tpl['compiled_filename'];
191 $raintpl_contents = ob_get_clean();
192 //----------------------
193
194
195 // save the output in the cache
196 if( $this->cache )
197 file_put_contents( $this->tpl['cache_filename'], "<?php if(!class_exists('raintpl')){exit;}?>" . $raintpl_contents );
198
199 // free memory
200 unset( $this->tpl );
201
202 // return or print the template
203 if( $return_string ) return $raintpl_contents; else echo $raintpl_contents;
204
205 }
206
207 }
208
209
210
211 /**
212 * If exists a valid cache for this template it returns the cache
213 *
214 * @param string $tpl_name Name of template (set the same of draw)
215 * @param int $expiration_time Set after how many seconds the cache expire and must be regenerated
216 * @return string it return the HTML or null if the cache must be recreated
217 */
218
219 function cache( $tpl_name, $expire_time = self::CACHE_EXPIRE_TIME, $cache_id = null ){
220
221 // set the cache_id
222 $this->cache_id = $cache_id;
223
224 if( !$this->check_template( $tpl_name ) && file_exists( $this->tpl['cache_filename'] ) && ( time() - filemtime( $this->tpl['cache_filename'] ) < $expire_time ) ){
225 // 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;}? >
226 return substr( file_get_contents( $this->tpl['cache_filename'] ), 43 );
227 }
228 else{
229 //delete the cache of the selected template
230 if (file_exists($this->tpl['cache_filename']))
231 unlink($this->tpl['cache_filename'] );
232 $this->cache = true;
233 }
234 }
235
236
237
238 /**
239 * Configure the settings of RainTPL
240 *
241 */
242 static function configure( $setting, $value = null ){
243 if( is_array( $setting ) )
244 foreach( $setting as $key => $value )
245 self::configure( $key, $value );
246 else if( property_exists( __CLASS__, $setting ) ){
247 self::$$setting = $value;
248 self::$config_name_sum[ $setting ] = $value; // take trace of all config
249 }
250 }
251
252
253
254 // check if has to compile the template
255 // return true if the template has changed
256 protected function check_template( $tpl_name ){
257
258 if( !isset($this->tpl['checked']) ){
259
260 $tpl_basename = basename( $tpl_name ); // template basename
261 $tpl_basedir = strpos($tpl_name,"/") ? dirname($tpl_name) . '/' : null; // template basedirectory
262 $this->tpl['template_directory'] = self::$tpl_dir . $tpl_basedir; // template directory
263 $this->tpl['tpl_filename'] = $this->tpl['template_directory'] . $tpl_basename . '.' . self::$tpl_ext; // template filename
264 $temp_compiled_filename = self::$cache_dir . $tpl_basename . "." . md5( $this->tpl['template_directory'] . serialize(self::$config_name_sum));
265 $this->tpl['compiled_filename'] = $temp_compiled_filename . '.rtpl.php'; // cache filename
266 $this->tpl['cache_filename'] = $temp_compiled_filename . '.s_' . $this->cache_id . '.rtpl.php'; // static cache filename
267 $this->tpl['checked'] = true;
268
269 // if the template doesn't exist and is not an external source throw an error
270 if( self::$check_template_update && !file_exists( $this->tpl['tpl_filename'] ) && !preg_match('/http/', $tpl_name) ){
271 $e = new RainTpl_NotFoundException( 'Template '. $tpl_basename .' not found!' );
272 throw $e->setTemplateFile($this->tpl['tpl_filename']);
273 }
274
275 // We check if the template is not an external source
276 if(preg_match('/http/', $tpl_name)){
277 $this->compileFile('', '', $tpl_name, self::$cache_dir, $this->tpl['compiled_filename'] );
278 return true;
279 }
280 // file doesn't exist, or the template was updated, Rain will compile the template
281 elseif( !file_exists( $this->tpl['compiled_filename'] ) || ( self::$check_template_update && filemtime($this->tpl['compiled_filename']) < filemtime( $this->tpl['tpl_filename'] ) ) ){
282 $this->compileFile( $tpl_basename, $tpl_basedir, $this->tpl['tpl_filename'], self::$cache_dir, $this->tpl['compiled_filename'] );
283 return true;
284 }
285
286 }
287 }
288
289
290 /**
291 * execute stripslaches() on the xml block. Invoqued by preg_replace_callback function below
292 * @access protected
293 */
294 protected function xml_reSubstitution($capture) {
295 return "<?php echo '<?xml ".stripslashes($capture[1])." ?>'; ?>";
296 }
297
298 /**
299 * Compile and write the compiled template file
300 * @access protected
301 */
302 protected function compileFile( $tpl_basename, $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){
303
304 //read template file
305 $this->tpl['source'] = $template_code = file_get_contents( $tpl_filename );
306
307 //xml substitution
308 $template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code );
309
310 //disable php tag
311 if( !self::$php_enabled )
312 $template_code = str_replace( array("<?","?>"), array("&lt;?","?&gt;"), $template_code );
313
314 //xml re-substitution
315 $template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code );
316
317 //compile template
318 $template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir );
319
320
321 // fix the php-eating-newline-after-closing-tag-problem
322 $template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled );
323
324 // create directories
325 if( !is_dir( $cache_dir ) )
326 mkdir( $cache_dir, 0755, true );
327
328 if( !is_writable( $cache_dir ) )
329 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/');
330
331 //write compiled file
332 file_put_contents( $compiled_filename, $template_compiled );
333 }
334
335
336
337 /**
338 * Compile template
339 * @access protected
340 */
341 protected function compileTemplate( $template_code, $tpl_basedir ){
342
343 //tag list
344 $tag_regexp = array( 'loop' => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})',
345 'break' => '(\{break\})',
346 'loop_close' => '(\{\/loop\})',
347 'if' => '(\{if(?: condition){0,1}="[^"]*"\})',
348 'elseif' => '(\{elseif(?: condition){0,1}="[^"]*"\})',
349 'else' => '(\{else\})',
350 'if_close' => '(\{\/if\})',
351 'function' => '(\{function="[^"]*"\})',
352 'noparse' => '(\{noparse\})',
353 'noparse_close'=> '(\{\/noparse\})',
354 'ignore' => '(\{ignore\}|\{\*)',
355 'ignore_close' => '(\{\/ignore\}|\*\})',
356 'include' => '(\{include="[^"]*"(?: cache="[^"]*")?\})',
357 'template_info'=> '(\{\$template_info\})',
358 'function' => '(\{function="(\w*?)(?:.*?)"\})'
359 );
360
361 $tag_regexp = "/" . join( "|", $tag_regexp ) . "/";
362
363 //path replace (src of img, background and href of link)
364 $template_code = $this->path_replace( $template_code, $tpl_basedir );
365
366 //split the code with the tags regexp
367 $template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
368
369 //compile the code
370 $compiled_code = $this->compileCode( $template_code );
371
372 //return the compiled code
373 return $compiled_code;
374
375 }
376
377
378
379 /**
380 * Compile the code
381 * @access protected
382 */
383 protected function compileCode( $parsed_code ){
384
385 // if parsed code is empty return null string
386 if( !$parsed_code )
387 return "";
388
389 //variables initialization
390 $compiled_code = $open_if = $comment_is_open = $ignore_is_open = null;
391 $loop_level = 0;
392
393
394 //read all parsed code
395 foreach( $parsed_code as $html ){
396
397 //close ignore tag
398 if( !$comment_is_open && ( strpos( $html, '{/ignore}' ) !== FALSE || strpos( $html, '*}' ) !== FALSE ) )
399 $ignore_is_open = false;
400
401 //code between tag ignore id deleted
402 elseif( $ignore_is_open ){
403 //ignore the code
404 }
405
406 //close no parse tag
407 elseif( strpos( $html, '{/noparse}' ) !== FALSE )
408 $comment_is_open = false;
409
410 //code between tag noparse is not compiled
411 elseif( $comment_is_open )
412 $compiled_code .= $html;
413
414 //ignore
415 elseif( strpos( $html, '{ignore}' ) !== FALSE || strpos( $html, '{*' ) !== FALSE )
416 $ignore_is_open = true;
417
418 //noparse
419 elseif( strpos( $html, '{noparse}' ) !== FALSE )
420 $comment_is_open = true;
421
422 //include tag
423 elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){
424 if (preg_match("/http/", $code[1])) {
425 $content = file_get_contents($code[1]);
426 $compiled_code .= $content;
427 } else {
428 //variables substitution
429 $include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level );
430
431 //get the folder of the actual template
432 $actual_folder = substr( $this->tpl['template_directory'], strlen(self::$tpl_dir) );
433
434 //get the included template
435 $include_template = $actual_folder . $include_var;
436
437 // reduce the path
438 $include_template = $this->reduce_path( $include_template );
439
440 // if the cache is active
441 if( isset($code[ 2 ]) ){
442
443 //include
444 $compiled_code .= '<?php $tpl = new '.get_called_class().';' .
445 'if( $cache = $tpl->cache( "'.$include_template.'" ) )' .
446 ' echo $cache;' .
447 'else{' .
448 '$tpl->assign( $this->var );' .
449 ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
450 '$tpl->draw( "'.$include_template.'" );'.
451 '}' .
452 '?>';
453
454 }
455 else{
456 //include
457 $compiled_code .= '<?php $tpl = new '.get_called_class().';' .
458 '$tpl->assign( $this->var );' .
459 ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
460 '$tpl->draw( "'.$include_template.'" );'.
461 '?>';
462
463 }
464 }
465 }
466
467 //loop
468 elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){
469
470 //increase the loop counter
471 $loop_level++;
472
473 //replace the variable in the loop
474 $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 );
475
476 //loop variables
477 $counter = "\$counter$loop_level"; // count iteration
478 $key = "\$key$loop_level"; // key
479 $value = "\$value$loop_level"; // value
480
481 //loop code
482 $compiled_code .= "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>";
483
484 }
485
486 // loop break
487 elseif( strpos( $html, '{break}' ) !== FALSE ) {
488
489 //else code
490 $compiled_code .= '<?php break; ?>';
491
492 }
493
494 //close loop tag
495 elseif( strpos( $html, '{/loop}' ) !== FALSE ) {
496
497 //iterator
498 $counter = "\$counter$loop_level";
499
500 //decrease the loop counter
501 $loop_level--;
502
503 //close loop code
504 $compiled_code .= "<?php } ?>";
505
506 }
507
508 //if
509 elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
510
511 //increase open if counter (for intendation)
512 $open_if++;
513
514 //tag
515 $tag = $code[ 0 ];
516
517 //condition attribute
518 $condition = $code[ 1 ];
519
520 // check if there's any function disabled by black_list
521 $this->function_check( $tag );
522
523 //variable substitution into condition (no delimiter into the condition)
524 $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
525
526 //if code
527 $compiled_code .= "<?php if( $parsed_condition ){ ?>";
528
529 }
530
531 //elseif
532 elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
533
534 //tag
535 $tag = $code[ 0 ];
536
537 //condition attribute
538 $condition = $code[ 1 ];
539
540 //variable substitution into condition (no delimiter into the condition)
541 $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
542
543 //elseif code
544 $compiled_code .= "<?php }elseif( $parsed_condition ){ ?>";
545 }
546
547 //else
548 elseif( strpos( $html, '{else}' ) !== FALSE ) {
549
550 //else code
551 $compiled_code .= '<?php }else{ ?>';
552
553 }
554
555 //close if tag
556 elseif( strpos( $html, '{/if}' ) !== FALSE ) {
557
558 //decrease if counter
559 $open_if--;
560
561 // close if code
562 $compiled_code .= '<?php } ?>';
563
564 }
565
566 //function
567 elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){
568
569 //tag
570 $tag = $code[ 0 ];
571
572 //function
573 $function = $code[ 1 ];
574
575 // check if there's any function disabled by black_list
576 $this->function_check( $tag );
577
578 if( empty( $code[ 2 ] ) )
579 $parsed_function = $function . "()";
580 else
581 // parse the function
582 $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 );
583
584 //if code
585 $compiled_code .= "<?php echo $parsed_function; ?>";
586 }
587
588 // show all vars
589 elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) {
590
591 //tag
592 $tag = '{$template_info}';
593
594 //if code
595 $compiled_code .= '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>';
596 }
597
598
599 //all html code
600 else{
601
602 //variables substitution (es. {$title})
603 $html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
604 //const substitution (es. {#CONST#})
605 $html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
606 //functions substitution (es. {"string"|functions})
607 $compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
608 }
609 }
610
611 if( $open_if > 0 ) {
612 $e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template');
613 throw $e->setTemplateFile($this->tpl['tpl_filename']);
614 }
615 return $compiled_code;
616 }
617
618
619
620 /**
621 * Reduce a path, eg. www/library/../filepath//file => www/filepath/file
622 * @param type $path
623 * @return type
624 */
625 protected function reduce_path( $path ){
626 $path = str_replace( "://", "@not_replace@", $path );
627 $path = preg_replace( "#(/+)#", "/", $path );
628 $path = preg_replace( "#(/\./+)#", "/", $path );
629 $path = str_replace( "@not_replace@", "://", $path );
630
631 while( preg_match( '#\.\./#', $path ) ){
632 $path = preg_replace('#\w+/\.\./#', '', $path );
633 }
634 return $path;
635 }
636
637
638
639 /**
640 * Replace URL according to the following rules:
641 * http://url => http://url
642 * url# => url
643 * /url => base_dir/url
644 * url => path/url (where path generally is base_url/template_dir)
645 * (The last one is => base_dir/url for <a> href)
646 *
647 * @param string $url Url to rewrite.
648 * @param string $tag Tag in which the url has been found.
649 * @param string $path Path to prepend to relative URLs.
650 * @return string rewritten url
651 */
652 protected function rewrite_url( $url, $tag, $path ) {
653 // If we don't have to rewrite for this tag, do nothing.
654 if( !in_array( $tag, self::$path_replace_list ) ) {
655 return $url;
656 }
657
658 // Make protocol list. It is a little bit different for <a>.
659 $protocol = 'http|https|ftp|file|apt|magnet';
660 if ( $tag == 'a' ) {
661 $protocol .= '|mailto|javascript';
662 }
663
664 // Regex for URLs that should not change (except the leading #)
665 $no_change = "/(^($protocol)\:)|(#$)/i";
666 if ( preg_match( $no_change, $url ) ) {
667 return rtrim( $url, '#' );
668 }
669
670 // Regex for URLs that need only base url (and not template dir)
671 $base_only = '/^\//';
672 if ( $tag == 'a' or $tag == 'form' ) {
673 $base_only = '//';
674 }
675 if ( preg_match( $base_only, $url ) ) {
676 return rtrim( self::$base_url, '/' ) . '/' . ltrim( $url, '/' );
677 }
678
679 // Other URLs
680 return $path . $url;
681 }
682
683
684
685 /**
686 * replace one single path corresponding to a given match in the `path_replace` regex.
687 * This function has no reason to be used anywhere but in `path_replace`.
688 * @see path_replace
689 *
690 * @param array $matches
691 * @return replacement string
692 */
693 protected function single_path_replace ( $matches ){
694 $tag = $matches[1];
695 $_ = $matches[2];
696 $attr = $matches[3];
697 $url = $matches[4];
698 $new_url = $this->rewrite_url( $url, $tag, $this->path );
699
700 return "<$tag$_$attr=\"$new_url\"";
701 }
702
703
704
705 /**
706 * replace the path of image src, link href and a href.
707 * @see rewrite_url for more information about how paths are replaced.
708 *
709 * @param string $html
710 * @return string html sostituito
711 */
712 protected function path_replace( $html, $tpl_basedir ){
713
714 if( self::$path_replace ){
715
716 $tpl_dir = self::$base_url . self::$tpl_dir . $tpl_basedir;
717
718 // Prepare reduced path not to compute it for each link
719 $this->path = $this->reduce_path( $tpl_dir );
720
721 $url = '(?:(?:\\{.*?\\})?[^{}]*?)*?'; // allow " inside {} for cases in which url contains {function="foo()"}
722
723 $exp = array();
724
725 $tags = array_intersect( array( "link", "a" ), self::$path_replace_list );
726 $exp[] = '/<(' . join( '|', $tags ) . ')(.*?)(href)="(' . $url . ')"/i';
727
728 $tags = array_intersect( array( "img", "script", "input" ), self::$path_replace_list );
729 $exp[] = '/<(' . join( '|', $tags ) . ')(.*?)(src)="(' . $url . ')"/i';
730
731 $tags = array_intersect( array( "form" ), self::$path_replace_list );
732 $exp[] = '/<(' . join( '|', $tags ) . ')(.*?)(action)="(' . $url . ')"/i';
733
734 return preg_replace_callback( $exp, 'self::single_path_replace', $html );
735
736 }
737 else
738 return $html;
739
740 }
741
742
743
744 // replace const
745 function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
746 // const
747 return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html );
748 }
749
750
751
752 // replace functions/modifiers on constants and strings
753 function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
754
755 preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches );
756
757 for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){
758
759 //complete tag ex: {$news.title|substr:0,100}
760 $tag = $matches[ 0 ][ $i ];
761
762 //variable name ex: news.title
763 $var = $matches[ 1 ][ $i ];
764
765 //function and parameters associate to the variable ex: substr:0,100
766 $extra_var = $matches[ 2 ][ $i ];
767
768 // check if there's any function disabled by black_list
769 $this->function_check( $tag );
770
771 $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
772
773
774 // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
775 $is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var );
776
777 //function associate to variable
778 $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
779
780 //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
781 $temp = preg_split( "/\.|\[|\-\>/", $var );
782
783 //variable name
784 $var_name = $temp[ 0 ];
785
786 //variable path
787 $variable_path = substr( $var, strlen( $var_name ) );
788
789 //parentesis transform [ e ] in [" e in "]
790 $variable_path = str_replace( '[', '["', $variable_path );
791 $variable_path = str_replace( ']', '"]', $variable_path );
792
793 //transform .$variable in ["$variable"]
794 $variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path );
795
796 //transform [variable] in ["variable"]
797 $variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path );
798
799 //if there's a function
800 if( $function_var ){
801
802 // check if there's a function or a static method and separate, function by parameters
803 $function_var = str_replace("::", "@double_dot@", $function_var );
804
805 // get the position of the first :
806 if( $dot_position = strpos( $function_var, ":" ) ){
807
808 // get the function and the parameters
809 $function = substr( $function_var, 0, $dot_position );
810 $params = substr( $function_var, $dot_position+1 );
811
812 }
813 else{
814
815 //get the function
816 $function = str_replace( "@double_dot@", "::", $function_var );
817 $params = null;
818
819 }
820
821 // replace back the @double_dot@ with ::
822 $function = str_replace( "@double_dot@", "::", $function );
823 $params = str_replace( "@double_dot@", "::", $params );
824
825
826 }
827 else
828 $function = $params = null;
829
830 $php_var = $var_name . $variable_path;
831
832 // compile the variable for php
833 if( isset( $function ) ){
834 if( $php_var )
835 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
836 else
837 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter;
838 }
839 else
840 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
841
842 $html = str_replace( $tag, $php_var, $html );
843
844 }
845
846 return $html;
847
848 }
849
850
851
852 function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
853
854 //all variables
855 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 ) ){
856
857 for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ )
858 $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]);
859
860 foreach( $parsed as $tag => $array ){
861
862 //variable name ex: news.title
863 $var = $array['var'];
864
865 //function and parameters associate to the variable ex: substr:0,100
866 $extra_var = $array['extra_var'];
867
868 // check if there's any function disabled by black_list
869 $this->function_check( $tag );
870
871 $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
872
873 // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
874 $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var );
875
876 //function associate to variable
877 $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
878
879 //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
880 $temp = preg_split( "/\.|\[|\-\>/", $var );
881
882 //variable name
883 $var_name = $temp[ 0 ];
884
885 //variable path
886 $variable_path = substr( $var, strlen( $var_name ) );
887
888 //parentesis transform [ e ] in [" e in "]
889 $variable_path = str_replace( '[', '["', $variable_path );
890 $variable_path = str_replace( ']', '"]', $variable_path );
891
892 //transform .$variable in ["$variable"] and .variable in ["variable"]
893 $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path );
894
895 // if is an assignment also assign the variable to $this->var['value']
896 if( $is_init_variable )
897 $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var;
898
899
900
901 //if there's a function
902 if( $function_var ){
903
904 // check if there's a function or a static method and separate, function by parameters
905 $function_var = str_replace("::", "@double_dot@", $function_var );
906
907
908 // get the position of the first :
909 if( $dot_position = strpos( $function_var, ":" ) ){
910
911 // get the function and the parameters
912 $function = substr( $function_var, 0, $dot_position );
913 $params = substr( $function_var, $dot_position+1 );
914
915 }
916 else{
917
918 //get the function
919 $function = str_replace( "@double_dot@", "::", $function_var );
920 $params = null;
921
922 }
923
924 // replace back the @double_dot@ with ::
925 $function = str_replace( "@double_dot@", "::", $function );
926 $params = str_replace( "@double_dot@", "::", $params );
927 }
928 else
929 $function = $params = null;
930
931 //if it is inside a loop
932 if( $loop_level ){
933 //verify the variable name
934 if( $var_name == 'key' )
935 $php_var = '$key' . $loop_level;
936 elseif( $var_name == 'value' )
937 $php_var = '$value' . $loop_level . $variable_path;
938 elseif( $var_name == 'counter' )
939 $php_var = '$counter' . $loop_level;
940 else
941 $php_var = '$' . $var_name . $variable_path;
942 }else
943 $php_var = '$' . $var_name . $variable_path;
944
945 // compile the variable for php
946 if( isset( $function ) )
947 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
948 else
949 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
950
951 $html = str_replace( $tag, $php_var, $html );
952
953
954 }
955 }
956
957 return $html;
958 }
959
960
961
962 /**
963 * Check if function is in black list (sandbox)
964 *
965 * @param string $code
966 * @param string $tag
967 */
968 protected function function_check( $code ){
969
970 $preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#';
971
972 // check if the function is in the black list (or not in white list)
973 if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){
974
975 // find the line of the error
976 $line = 0;
977 $rows=explode("\n",$this->tpl['source']);
978 while( !strpos($rows[$line],$code) )
979 $line++;
980
981 // stop the execution of the script
982 $e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template');
983 throw $e->setTemplateFile($this->tpl['tpl_filename'])
984 ->setTag($code)
985 ->setTemplateLine($line);
986 }
987
988 }
989
990 /**
991 * Prints debug info about exception or passes it further if debug is disabled.
992 *
993 * @param RainTpl_Exception $e
994 * @return string
995 */
996 protected function printDebug(RainTpl_Exception $e){
997 if (!self::$debug) {
998 throw $e;
999 }
1000 $output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>',
1001 get_class($e),
1002 $e->getMessage(),
1003 $e->getTemplateFile()
1004 );
1005 if ($e instanceof RainTpl_SyntaxException) {
1006 if (null != $e->getTemplateLine()) {
1007 $output .= '<p>line: ' . $e->getTemplateLine() . '</p>';
1008 }
1009 if (null != $e->getTag()) {
1010 $output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>';
1011 }
1012 if (null != $e->getTemplateLine() && null != $e->getTag()) {
1013 $rows=explode("\n", htmlspecialchars($this->tpl['source']));
1014 $rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>';
1015 $output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>';
1016 }
1017 }
1018 $output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>',
1019 $e->getFile(), $e->getLine(),
1020 nl2br(htmlspecialchars($e->getTraceAsString()))
1021 );
1022 return $output;
1023 }
1024 }
1025
1026
1027 /**
1028 * Basic Rain tpl exception.
1029 */
1030 class RainTpl_Exception extends Exception{
1031 /**
1032 * Path of template file with error.
1033 */
1034 protected $templateFile = '';
1035
1036 /**
1037 * Returns path of template file with error.
1038 *
1039 * @return string
1040 */
1041 public function getTemplateFile()
1042 {
1043 return $this->templateFile;
1044 }
1045
1046 /**
1047 * Sets path of template file with error.
1048 *
1049 * @param string $templateFile
1050 * @return RainTpl_Exception
1051 */
1052 public function setTemplateFile($templateFile)
1053 {
1054 $this->templateFile = (string) $templateFile;
1055 return $this;
1056 }
1057 }
1058
1059 /**
1060 * Exception thrown when template file does not exists.
1061 */
1062 class RainTpl_NotFoundException extends RainTpl_Exception{
1063 }
1064
1065 /**
1066 * Exception thrown when syntax error occurs.
1067 */
1068 class RainTpl_SyntaxException extends RainTpl_Exception{
1069 /**
1070 * Line in template file where error has occured.
1071 *
1072 * @var int | null
1073 */
1074 protected $templateLine = null;
1075
1076 /**
1077 * Tag which caused an error.
1078 *
1079 * @var string | null
1080 */
1081 protected $tag = null;
1082
1083 /**
1084 * Returns line in template file where error has occured
1085 * or null if line is not defined.
1086 *
1087 * @return int | null
1088 */
1089 public function getTemplateLine()
1090 {
1091 return $this->templateLine;
1092 }
1093
1094 /**
1095 * Sets line in template file where error has occured.
1096 *
1097 * @param int $templateLine
1098 * @return RainTpl_SyntaxException
1099 */
1100 public function setTemplateLine($templateLine)
1101 {
1102 $this->templateLine = (int) $templateLine;
1103 return $this;
1104 }
1105
1106 /**
1107 * Returns tag which caused an error.
1108 *
1109 * @return string
1110 */
1111 public function getTag()
1112 {
1113 return $this->tag;
1114 }
1115
1116 /**
1117 * Sets tag which caused an error.
1118 *
1119 * @param string $tag
1120 * @return RainTpl_SyntaxException
1121 */
1122 public function setTag($tag)
1123 {
1124 $this->tag = (string) $tag;
1125 return $this;
1126 }
1127 }
1128
1129 // -- end