]>
Commit | Line | Data |
---|---|---|
284e095d A |
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 | */ | |
21e65e79 | 93 | static $php_enabled = true; |
284e095d A |
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("<?","?>"), $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 |