aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/rain.tpl.class.php
diff options
context:
space:
mode:
authorSébastien SAUVAGE <sebsauvage@sebsauvage.net>2013-02-26 10:09:41 +0100
committerSébastien SAUVAGE <sebsauvage@sebsauvage.net>2013-02-26 10:09:41 +0100
commit450342737ced8ef2864b4f83a4107a7fafcc4add (patch)
treea6d2fe843c27b5c3d88dab5581b6a06828d544b9 /inc/rain.tpl.class.php
downloadShaarli-450342737ced8ef2864b4f83a4107a7fafcc4add.tar.gz
Shaarli-450342737ced8ef2864b4f83a4107a7fafcc4add.tar.zst
Shaarli-450342737ced8ef2864b4f83a4107a7fafcc4add.zip
Initial commit (version 0.0.40 beta)v0.0.40beta
Diffstat (limited to 'inc/rain.tpl.class.php')
-rw-r--r--inc/rain.tpl.class.php1037
1 files changed, 1037 insertions, 0 deletions
diff --git a/inc/rain.tpl.class.php b/inc/rain.tpl.class.php
new file mode 100644
index 00000000..e9f3bd29
--- /dev/null
+++ b/inc/rain.tpl.class.php
@@ -0,0 +1,1037 @@
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.7
10 */
11
12
13class 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 = false;
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;
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 substr( file_get_contents( $this->tpl['cache_filename'] ), 43 );
226 else{
227 //delete the cache of the selected template
228 if (file_exists($this->tpl['cache_filename']))
229 unlink($this->tpl['cache_filename'] );
230 $this->cache = true;
231 }
232 }
233
234
235
236 /**
237 * Configure the settings of RainTPL
238 *
239 */
240 static function configure( $setting, $value = null ){
241 if( is_array( $setting ) )
242 foreach( $setting as $key => $value )
243 self::configure( $key, $value );
244 else if( property_exists( __CLASS__, $setting ) ){
245 self::$$setting = $value;
246 self::$config_name_sum[$key] = $value; // take trace of all config
247 }
248 }
249
250
251
252 // check if has to compile the template
253 // return true if the template has changed
254 protected function check_template( $tpl_name ){
255
256 if( !isset($this->tpl['checked']) ){
257
258 $tpl_basename = basename( $tpl_name ); // template basename
259 $tpl_basedir = strpos($tpl_name,"/") ? dirname($tpl_name) . '/' : null; // template basedirectory
260 $tpl_dir = self::$tpl_dir . $tpl_basedir; // template directory
261 $this->tpl['tpl_filename'] = $tpl_dir . $tpl_basename . '.' . self::$tpl_ext; // template filename
262 $temp_compiled_filename = self::$cache_dir . $tpl_basename . "." . md5( $tpl_dir . implode('', self::$config_name_sum));
263 $this->tpl['compiled_filename'] = $temp_compiled_filename . '.rtpl.php'; // cache filename
264 $this->tpl['cache_filename'] = $temp_compiled_filename . '.s_' . $this->cache_id . '.rtpl.php'; // static cache filename
265
266 // if the template doesn't exsist throw an error
267 if( self::$check_template_update && !file_exists( $this->tpl['tpl_filename'] ) ){
268 $e = new RainTpl_NotFoundException( 'Template '. $tpl_basename .' not found!' );
269 throw $e->setTemplateFile($this->tpl['tpl_filename']);
270 }
271
272 // file doesn't exsist, or the template was updated, Rain will compile the template
273 if( !file_exists( $this->tpl['compiled_filename'] ) || ( self::$check_template_update && filemtime($this->tpl['compiled_filename']) < filemtime( $this->tpl['tpl_filename'] ) ) ){
274 $this->compileFile( $tpl_basename, $tpl_basedir, $this->tpl['tpl_filename'], self::$cache_dir, $this->tpl['compiled_filename'] );
275 return true;
276 }
277 $this->tpl['checked'] = true;
278 }
279 }
280
281
282 /**
283 * execute stripslaches() on the xml block. Invoqued by preg_replace_callback function below
284 * @access protected
285 */
286 protected function xml_reSubstitution($capture) {
287 return "<?php echo '<?xml ".stripslashes($capture[1])." ?>'; ?>";
288 }
289
290 /**
291 * Compile and write the compiled template file
292 * @access protected
293 */
294 protected function compileFile( $tpl_basename, $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){
295
296 //read template file
297 $this->tpl['source'] = $template_code = file_get_contents( $tpl_filename );
298
299 //xml substitution
300 $template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code );
301
302 //disable php tag
303 if( !self::$php_enabled )
304 $template_code = str_replace( array("<?","?>"), array("&lt;?","?&gt;"), $template_code );
305
306 //xml re-substitution
307 $template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code );
308
309 //compile template
310 $template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir );
311
312
313 // fix the php-eating-newline-after-closing-tag-problem
314 $template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled );
315
316 // create directories
317 if( !is_dir( $cache_dir ) )
318 mkdir( $cache_dir, 0755, true );
319
320 if( !is_writable( $cache_dir ) )
321 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/');
322
323 //write compiled file
324 file_put_contents( $compiled_filename, $template_compiled );
325 }
326
327
328
329 /**
330 * Compile template
331 * @access protected
332 */
333 protected function compileTemplate( $template_code, $tpl_basedir ){
334
335 //tag list
336 $tag_regexp = array( 'loop' => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})',
337 'loop_close' => '(\{\/loop\})',
338 'if' => '(\{if(?: condition){0,1}="[^"]*"\})',
339 'elseif' => '(\{elseif(?: condition){0,1}="[^"]*"\})',
340 'else' => '(\{else\})',
341 'if_close' => '(\{\/if\})',
342 'function' => '(\{function="[^"]*"\})',
343 'noparse' => '(\{noparse\})',
344 'noparse_close'=> '(\{\/noparse\})',
345 'ignore' => '(\{ignore\})',
346 'ignore_close' => '(\{\/ignore\})',
347 'include' => '(\{include="[^"]*"(?: cache="[^"]*")?\})',
348 'template_info'=> '(\{\$template_info\})',
349 'function' => '(\{function="(\w*?)(?:.*?)"\})'
350 );
351
352 $tag_regexp = "/" . join( "|", $tag_regexp ) . "/";
353
354 //split the code with the tags regexp
355 $template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
356
357 //path replace (src of img, background and href of link)
358 $template_code = $this->path_replace( $template_code, $tpl_basedir );
359
360 //compile the code
361 $compiled_code = $this->compileCode( $template_code );
362
363 //return the compiled code
364 return $compiled_code;
365
366 }
367
368
369
370 /**
371 * Compile the code
372 * @access protected
373 */
374 protected function compileCode( $parsed_code ){
375
376 //variables initialization
377 $compiled_code = $open_if = $comment_is_open = $ignore_is_open = null;
378 $loop_level = 0;
379
380 //read all parsed code
381 while( $html = array_shift( $parsed_code ) ){
382
383 //close ignore tag
384 if( !$comment_is_open && strpos( $html, '{/ignore}' ) !== FALSE )
385 $ignore_is_open = false;
386
387 //code between tag ignore id deleted
388 elseif( $ignore_is_open ){
389 //ignore the code
390 }
391
392 //close no parse tag
393 elseif( strpos( $html, '{/noparse}' ) !== FALSE )
394 $comment_is_open = false;
395
396 //code between tag noparse is not compiled
397 elseif( $comment_is_open )
398 $compiled_code .= $html;
399
400 //ignore
401 elseif( strpos( $html, '{ignore}' ) !== FALSE )
402 $ignore_is_open = true;
403
404 //noparse
405 elseif( strpos( $html, '{noparse}' ) !== FALSE )
406 $comment_is_open = true;
407
408 //include tag
409 elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){
410
411 //variables substitution
412 $include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level );
413
414 // if the cache is active
415 if( isset($code[ 2 ]) ){
416
417 //dynamic include
418 $compiled_code .= '<?php $tpl = new RainTpl;' .
419 'if( $cache = $tpl->cache( $template = basename("'.$include_var.'") ) )' .
420 ' echo $cache;' .
421 'else{' .
422 ' $tpl_dir_temp = self::$tpl_dir;' .
423 ' $tpl->assign( $this->var );' .
424 ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
425 ' $tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
426 '} ?>';
427 }
428 else{
429
430 //dynamic include
431 $compiled_code .= '<?php $tpl = new RainTpl;' .
432 '$tpl_dir_temp = self::$tpl_dir;' .
433 '$tpl->assign( $this->var );' .
434 ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
435 '$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
436 '?>';
437
438
439 }
440
441 }
442
443 //loop
444 elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){
445
446 //increase the loop counter
447 $loop_level++;
448
449 //replace the variable in the loop
450 $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 );
451
452 //loop variables
453 $counter = "\$counter$loop_level"; // count iteration
454 $key = "\$key$loop_level"; // key
455 $value = "\$value$loop_level"; // value
456
457 //loop code
458 $compiled_code .= "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>";
459
460 }
461
462 //close loop tag
463 elseif( strpos( $html, '{/loop}' ) !== FALSE ) {
464
465 //iterator
466 $counter = "\$counter$loop_level";
467
468 //decrease the loop counter
469 $loop_level--;
470
471 //close loop code
472 $compiled_code .= "<?php } ?>";
473
474 }
475
476 //if
477 elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
478
479 //increase open if counter (for intendation)
480 $open_if++;
481
482 //tag
483 $tag = $code[ 0 ];
484
485 //condition attribute
486 $condition = $code[ 1 ];
487
488 // check if there's any function disabled by black_list
489 $this->function_check( $tag );
490
491 //variable substitution into condition (no delimiter into the condition)
492 $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
493
494 //if code
495 $compiled_code .= "<?php if( $parsed_condition ){ ?>";
496
497 }
498
499 //elseif
500 elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
501
502 //tag
503 $tag = $code[ 0 ];
504
505 //condition attribute
506 $condition = $code[ 1 ];
507
508 //variable substitution into condition (no delimiter into the condition)
509 $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
510
511 //elseif code
512 $compiled_code .= "<?php }elseif( $parsed_condition ){ ?>";
513 }
514
515 //else
516 elseif( strpos( $html, '{else}' ) !== FALSE ) {
517
518 //else code
519 $compiled_code .= '<?php }else{ ?>';
520
521 }
522
523 //close if tag
524 elseif( strpos( $html, '{/if}' ) !== FALSE ) {
525
526 //decrease if counter
527 $open_if--;
528
529 // close if code
530 $compiled_code .= '<?php } ?>';
531
532 }
533
534 //function
535 elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){
536
537 //tag
538 $tag = $code[ 0 ];
539
540 //function
541 $function = $code[ 1 ];
542
543 // check if there's any function disabled by black_list
544 $this->function_check( $tag );
545
546 if( empty( $code[ 2 ] ) )
547 $parsed_function = $function . "()";
548 else
549 // parse the function
550 $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 );
551
552 //if code
553 $compiled_code .= "<?php echo $parsed_function; ?>";
554 }
555
556 // show all vars
557 elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) {
558
559 //tag
560 $tag = '{$template_info}';
561
562 //if code
563 $compiled_code .= '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>';
564 }
565
566
567 //all html code
568 else{
569
570 //variables substitution (es. {$title})
571 $html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
572 //const substitution (es. {#CONST#})
573 $html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
574 //functions substitution (es. {"string"|functions})
575 $compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
576 }
577 }
578
579 if( $open_if > 0 ) {
580 $e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template');
581 throw $e->setTemplateFile($this->tpl['tpl_filename']);
582 }
583 return $compiled_code;
584 }
585
586
587
588 protected function reduce_path( $path ){
589 $path = str_replace( "//", "/", $path );
590 return preg_replace('/\w+\/\.\.\//', '', $path );
591 }
592
593
594
595 /**
596 * replace the path of image src, link href and a href.
597 * url => template_dir/url
598 * url# => url
599 * http://url => http://url
600 *
601 * @param string $html
602 * @return string html sostituito
603 */
604 protected function path_replace( $html, $tpl_basedir ){
605
606 if( self::$path_replace ){
607
608 $tpl_dir = self::$base_url . self::$tpl_dir . $tpl_basedir;
609
610 // reduce the path
611 $path = $this->reduce_path($tpl_dir);
612
613 $exp = $sub = array();
614
615 if( in_array( "img", self::$path_replace_list ) ){
616 $exp = array( '/<img(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<img(.*?)src=(?:")([^"]+?)#(?:")/i', '/<img(.*?)src="(.*?)"/', '/<img(.*?)src=(?:\@)([^"]+?)(?:\@)/i' );
617 $sub = array( '<img$1src=@$2://$3@', '<img$1src=@$2@', '<img$1src="' . $path . '$2"', '<img$1src="$2"' );
618 }
619
620 if( in_array( "script", self::$path_replace_list ) ){
621 $exp = array_merge( $exp , array( '/<script(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<script(.*?)src=(?:")([^"]+?)#(?:")/i', '/<script(.*?)src="(.*?)"/', '/<script(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
622 $sub = array_merge( $sub , array( '<script$1src=@$2://$3@', '<script$1src=@$2@', '<script$1src="' . $path . '$2"', '<script$1src="$2"' ) );
623 }
624
625 if( in_array( "link", self::$path_replace_list ) ){
626 $exp = array_merge( $exp , array( '/<link(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<link(.*?)href=(?:")([^"]+?)#(?:")/i', '/<link(.*?)href="(.*?)"/', '/<link(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
627 $sub = array_merge( $sub , array( '<link$1href=@$2://$3@', '<link$1href=@$2@' , '<link$1href="' . $path . '$2"', '<link$1href="$2"' ) );
628 }
629
630 if( in_array( "a", self::$path_replace_list ) ){
631 $exp = array_merge( $exp , array( '/<a(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<a(.*?)href="(.*?)"/', '/<a(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
632 $sub = array_merge( $sub , array( '<a$1href=@$2://$3@', '<a$1href="' . self::$base_url . '$2"', '<a$1href="$2"' ) );
633 }
634
635 if( in_array( "input", self::$path_replace_list ) ){
636 $exp = array_merge( $exp , array( '/<input(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<input(.*?)src=(?:")([^"]+?)#(?:")/i', '/<input(.*?)src="(.*?)"/', '/<input(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
637 $sub = array_merge( $sub , array( '<input$1src=@$2://$3@', '<input$1src=@$2@', '<input$1src="' . $path . '$2"', '<input$1src="$2"' ) );
638 }
639
640 return preg_replace( $exp, $sub, $html );
641
642 }
643 else
644 return $html;
645
646 }
647
648
649
650
651
652 // replace const
653 function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
654 // const
655 return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html );
656 }
657
658
659
660 // replace functions/modifiers on constants and strings
661 function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
662
663 preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches );
664
665 for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){
666
667 //complete tag ex: {$news.title|substr:0,100}
668 $tag = $matches[ 0 ][ $i ];
669
670 //variable name ex: news.title
671 $var = $matches[ 1 ][ $i ];
672
673 //function and parameters associate to the variable ex: substr:0,100
674 $extra_var = $matches[ 2 ][ $i ];
675
676 // check if there's any function disabled by black_list
677 $this->function_check( $tag );
678
679 $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
680
681
682 // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
683 $is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var );
684
685 //function associate to variable
686 $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
687
688 //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
689 $temp = preg_split( "/\.|\[|\-\>/", $var );
690
691 //variable name
692 $var_name = $temp[ 0 ];
693
694 //variable path
695 $variable_path = substr( $var, strlen( $var_name ) );
696
697 //parentesis transform [ e ] in [" e in "]
698 $variable_path = str_replace( '[', '["', $variable_path );
699 $variable_path = str_replace( ']', '"]', $variable_path );
700
701 //transform .$variable in ["$variable"]
702 $variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path );
703
704 //transform [variable] in ["variable"]
705 $variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path );
706
707 //if there's a function
708 if( $function_var ){
709
710 // check if there's a function or a static method and separate, function by parameters
711 $function_var = str_replace("::", "@double_dot@", $function_var );
712
713 // get the position of the first :
714 if( $dot_position = strpos( $function_var, ":" ) ){
715
716 // get the function and the parameters
717 $function = substr( $function_var, 0, $dot_position );
718 $params = substr( $function_var, $dot_position+1 );
719
720 }
721 else{
722
723 //get the function
724 $function = str_replace( "@double_dot@", "::", $function_var );
725 $params = null;
726
727 }
728
729 // replace back the @double_dot@ with ::
730 $function = str_replace( "@double_dot@", "::", $function );
731 $params = str_replace( "@double_dot@", "::", $params );
732
733
734 }
735 else
736 $function = $params = null;
737
738 $php_var = $var_name . $variable_path;
739
740 // compile the variable for php
741 if( isset( $function ) ){
742 if( $php_var )
743 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
744 else
745 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter;
746 }
747 else
748 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
749
750 $html = str_replace( $tag, $php_var, $html );
751
752 }
753
754 return $html;
755
756 }
757
758
759
760 function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
761
762 //all variables
763 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 ) ){
764
765 for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ )
766 $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]);
767
768 foreach( $parsed as $tag => $array ){
769
770 //variable name ex: news.title
771 $var = $array['var'];
772
773 //function and parameters associate to the variable ex: substr:0,100
774 $extra_var = $array['extra_var'];
775
776 // check if there's any function disabled by black_list
777 $this->function_check( $tag );
778
779 $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
780
781 // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
782 $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var );
783
784 //function associate to variable
785 $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
786
787 //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
788 $temp = preg_split( "/\.|\[|\-\>/", $var );
789
790 //variable name
791 $var_name = $temp[ 0 ];
792
793 //variable path
794 $variable_path = substr( $var, strlen( $var_name ) );
795
796 //parentesis transform [ e ] in [" e in "]
797 $variable_path = str_replace( '[', '["', $variable_path );
798 $variable_path = str_replace( ']', '"]', $variable_path );
799
800 //transform .$variable in ["$variable"] and .variable in ["variable"]
801 $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path );
802
803 // if is an assignment also assign the variable to $this->var['value']
804 if( $is_init_variable )
805 $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var;
806
807
808
809 //if there's a function
810 if( $function_var ){
811
812 // check if there's a function or a static method and separate, function by parameters
813 $function_var = str_replace("::", "@double_dot@", $function_var );
814
815
816 // get the position of the first :
817 if( $dot_position = strpos( $function_var, ":" ) ){
818
819 // get the function and the parameters
820 $function = substr( $function_var, 0, $dot_position );
821 $params = substr( $function_var, $dot_position+1 );
822
823 }
824 else{
825
826 //get the function
827 $function = str_replace( "@double_dot@", "::", $function_var );
828 $params = null;
829
830 }
831
832 // replace back the @double_dot@ with ::
833 $function = str_replace( "@double_dot@", "::", $function );
834 $params = str_replace( "@double_dot@", "::", $params );
835 }
836 else
837 $function = $params = null;
838
839 //if it is inside a loop
840 if( $loop_level ){
841 //verify the variable name
842 if( $var_name == 'key' )
843 $php_var = '$key' . $loop_level;
844 elseif( $var_name == 'value' )
845 $php_var = '$value' . $loop_level . $variable_path;
846 elseif( $var_name == 'counter' )
847 $php_var = '$counter' . $loop_level;
848 else
849 $php_var = '$' . $var_name . $variable_path;
850 }else
851 $php_var = '$' . $var_name . $variable_path;
852
853 // compile the variable for php
854 if( isset( $function ) )
855 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
856 else
857 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
858
859 $html = str_replace( $tag, $php_var, $html );
860
861
862 }
863 }
864
865 return $html;
866 }
867
868
869
870 /**
871 * Check if function is in black list (sandbox)
872 *
873 * @param string $code
874 * @param string $tag
875 */
876 protected function function_check( $code ){
877
878 $preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#';
879
880 // check if the function is in the black list (or not in white list)
881 if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){
882
883 // find the line of the error
884 $line = 0;
885 $rows=explode("\n",$this->tpl['source']);
886 while( !strpos($rows[$line],$code) )
887 $line++;
888
889 // stop the execution of the script
890 $e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template');
891 throw $e->setTemplateFile($this->tpl['tpl_filename'])
892 ->setTag($code)
893 ->setTemplateLine($line);
894 }
895
896 }
897
898 /**
899 * Prints debug info about exception or passes it further if debug is disabled.
900 *
901 * @param RainTpl_Exception $e
902 * @return string
903 */
904 protected function printDebug(RainTpl_Exception $e){
905 if (!self::$debug) {
906 throw $e;
907 }
908 $output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>',
909 get_class($e),
910 $e->getMessage(),
911 $e->getTemplateFile()
912 );
913 if ($e instanceof RainTpl_SyntaxException) {
914 if (null != $e->getTemplateLine()) {
915 $output .= '<p>line: ' . $e->getTemplateLine() . '</p>';
916 }
917 if (null != $e->getTag()) {
918 $output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>';
919 }
920 if (null != $e->getTemplateLine() && null != $e->getTag()) {
921 $rows=explode("\n", htmlspecialchars($this->tpl['source']));
922 $rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>';
923 $output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>';
924 }
925 }
926 $output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>',
927 $e->getFile(), $e->getLine(),
928 nl2br(htmlspecialchars($e->getTraceAsString()))
929 );
930 return $output;
931 }
932}
933
934
935/**
936 * Basic Rain tpl exception.
937 */
938class RainTpl_Exception extends Exception{
939 /**
940 * Path of template file with error.
941 */
942 protected $templateFile = '';
943
944 /**
945 * Returns path of template file with error.
946 *
947 * @return string
948 */
949 public function getTemplateFile()
950 {
951 return $this->templateFile;
952 }
953
954 /**
955 * Sets path of template file with error.
956 *
957 * @param string $templateFile
958 * @return RainTpl_Exception
959 */
960 public function setTemplateFile($templateFile)
961 {
962 $this->templateFile = (string) $templateFile;
963 return $this;
964 }
965}
966
967/**
968 * Exception thrown when template file does not exists.
969 */
970class RainTpl_NotFoundException extends RainTpl_Exception{
971}
972
973/**
974 * Exception thrown when syntax error occurs.
975 */
976class RainTpl_SyntaxException extends RainTpl_Exception{
977 /**
978 * Line in template file where error has occured.
979 *
980 * @var int | null
981 */
982 protected $templateLine = null;
983
984 /**
985 * Tag which caused an error.
986 *
987 * @var string | null
988 */
989 protected $tag = null;
990
991 /**
992 * Returns line in template file where error has occured
993 * or null if line is not defined.
994 *
995 * @return int | null
996 */
997 public function getTemplateLine()
998 {
999 return $this->templateLine;
1000 }
1001
1002 /**
1003 * Sets line in template file where error has occured.
1004 *
1005 * @param int $templateLine
1006 * @return RainTpl_SyntaxException
1007 */
1008 public function setTemplateLine($templateLine)
1009 {
1010 $this->templateLine = (int) $templateLine;
1011 return $this;
1012 }
1013
1014 /**
1015 * Returns tag which caused an error.
1016 *
1017 * @return string
1018 */
1019 public function getTag()
1020 {
1021 return $this->tag;
1022 }
1023
1024 /**
1025 * Sets tag which caused an error.
1026 *
1027 * @param string $tag
1028 * @return RainTpl_SyntaxException
1029 */
1030 public function setTag($tag)
1031 {
1032 $this->tag = (string) $tag;
1033 return $this;
1034 }
1035}
1036
1037// -- end