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