blob: 84713c6186447673f290b60ad07ded52e3b92f1e [file] [log] [blame]
Nico Huberee52fbc2023-06-24 11:52:57 +00001<?php
2
3// When developing uncomment the line below, re-comment before making public
4//error_reporting(E_ALL);
5
6/**
7 * XTemplate PHP templating engine
8 *
9 * @package XTemplate
10 * @author Barnabas Debreceni [cranx@users.sourceforge.net]
11 * @copyright Barnabas Debreceni 2000-2001
12 * @author Jeremy Coates [cocomp@users.sourceforge.net]
13 * @copyright Jeremy Coates 2002-2007
14 * @see license.txt LGPL / BSD license
15 * @since PHP 5
16 * @link $HeadURL: https://xtpl.svn.sourceforge.net/svnroot/xtpl/trunk/xtemplate.class.php $
17 * @version $Id$
18 *
19 *
20 * XTemplate class - http://www.phpxtemplate.org/ (x)html / xml generation with templates - fast & easy
21 * Latest stable & Subversion versions available @ http://sourceforge.net/projects/xtpl/
22 * License: LGPL / BSD - see license.txt
23 * Changelog: see changelog.txt
24 */
25class XTemplate {
26
27 /**
28 * Properties
29 */
30
31 /**
32 * Raw contents of the template file
33 *
34 * @access public
35 * @var string
36 */
37 public $filecontents = '';
38
39 /**
40 * Unparsed blocks
41 *
42 * @access public
43 * @var array
44 */
45 public $blocks = array();
46
47 /**
48 * Parsed blocks
49 *
50 * @var unknown_type
51 */
52 public $parsed_blocks = array();
53
54 /**
55 * Preparsed blocks (for file includes)
56 *
57 * @access public
58 * @var array
59 */
60 public $preparsed_blocks = array();
61
62 /**
63 * Block parsing order for recursive parsing
64 * (Sometimes reverse :)
65 *
66 * @access public
67 * @var array
68 */
69 public $block_parse_order = array();
70
71 /**
72 * Store sub-block names
73 * (For fast resetting)
74 *
75 * @access public
76 * @var array
77 */
78 public $sub_blocks = array();
79
80 /**
81 * Variables array
82 *
83 * @access public
84 * @var array
85 */
86 public $vars = array();
87
88 /**
89 * File variables array
90 *
91 * @access public
92 * @var array
93 */
94 public $filevars = array();
95
96 /**
97 * Filevars' parent block
98 *
99 * @access public
100 * @var array
101 */
102 public $filevar_parent = array();
103
104 /**
105 * File caching during duration of script
106 * e.g. files only cached to speed {FILE "filename"} repeats
107 *
108 * @access public
109 * @var array
110 */
111 public $filecache = array();
112
113 /**
114 * Location of template files
115 *
116 * @access public
117 * @var string
118 */
119 public $tpldir = '';
120
121 /**
122 * Filenames lookup table
123 *
124 * @access public
125 * @var null
126 */
127 public $files = null;
128
129 /**
130 * Template filename
131 *
132 * @access public
133 * @var string
134 */
135 public $filename = '';
136
137 // moved to setup method so uses the tag_start & end_delims
138 /**
139 * RegEx for file includes
140 *
141 * "/\{FILE\s*\"([^\"]+)\"\s*\}/m";
142 *
143 * @access public
144 * @var string
145 */
146 public $file_delim = '';
147
148 /**
149 * RegEx for file include variable
150 *
151 * "/\{FILE\s*\{([A-Za-z0-9\._]+?)\}\s*\}/m";
152 *
153 * @access public
154 * @var string
155 */
156 public $filevar_delim = '';
157
158 /**
159 * RegEx for file includes with newlines
160 *
161 * "/^\s*\{FILE\s*\{([A-Za-z0-9\._]+?)\}\s*\}\s*\n/m";
162 *
163 * @access public
164 * @var string
165 */
166 public $filevar_delim_nl = '';
167
168 /**
169 * Template block start delimiter
170 *
171 * @access public
172 * @var string
173 */
174 public $block_start_delim = '<!-- ';
175
176 /**
177 * Template block end delimiter
178 *
179 * @access public
180 * @var string
181 */
182 public $block_end_delim = '-->';
183
184 /**
185 * Template block start word
186 *
187 * @access public
188 * @var string
189 */
190 public $block_start_word = 'BEGIN:';
191
192 /**
193 * Template block end word
194 *
195 * The last 3 properties and this make the delimiters look like:
196 * @example <!-- BEGIN: block_name -->
197 * if you use the default syntax.
198 *
199 * @access public
200 * @var string
201 */
202 public $block_end_word = 'END:';
203
204 /**
205 * Template tag start delimiter
206 *
207 * This makes the delimiters look like:
208 * @example {tagname}
209 * if you use the default syntax.
210 *
211 * @access public
212 * @var string
213 */
214 public $tag_start_delim = '{';
215
216 /**
217 * Template tag end delimiter
218 *
219 * This makes the delimiters look like:
220 * @example {tagname}
221 * if you use the default syntax.
222 *
223 * @access public
224 * @var string
225 */
226 public $tag_end_delim = '}';
227 /* this makes the delimiters look like: {tagname} if you use my syntax. */
228
229 /**
230 * Regular expression element for comments within tags and blocks
231 *
232 * @example {tagname#My Comment}
233 * @example {tagname #My Comment}
234 * @example <!-- BEGIN: blockname#My Comment -->
235 * @example <!-- BEGIN: blockname #My Comment -->
236 *
237 * @access public
238 * @var string
239 */
240 public $comment_preg = '( ?#.*?)?';
241
242 /**
243 * Default main template block name
244 *
245 * @access public
246 * @var string
247 */
248 public $mainblock = 'main';
249
250 /**
251 * Script output type
252 *
253 * @access public
254 * @var string
255 */
256 public $output_type = 'HTML';
257
258 /**
259 * Debug mode
260 *
261 * @access public
262 * @var boolean
263 */
264 public $debug = false;
265
266 /**
267 * Null string for unassigned vars
268 *
269 * @access protected
270 * @var array
271 */
272 protected $_null_string = array('' => '');
273
274 /**
275 * Null string for unassigned blocks
276 *
277 * @access protected
278 * @var array
279 */
280 protected $_null_block = array('' => '');
281
282 /**
283 * Errors
284 *
285 * @access protected
286 * @var string
287 */
288 protected $_error = '';
289
290 /**
291 * Auto-reset sub blocks
292 *
293 * @access protected
294 * @var boolean
295 */
296 protected $_autoreset = true;
297
298 /**
299 * Set to FALSE to generate errors if a non-existant blocks is referenced
300 *
301 * @author NW
302 * @since 2002/10/17
303 * @access protected
304 * @var boolean
305 */
306 protected $_ignore_missing_blocks = true;
307
308 /**
309 * PHP 5 Constructor - Instantiate the object
310 *
311 * @param string $file Template file to work on
312 * @param string/array $tpldir Location of template files (useful for keeping files outside web server root)
313 * @param array $files Filenames lookup
314 * @param string $mainblock Name of main block in the template
315 * @param boolean $autosetup If true, run setup() as part of constuctor
316 * @return XTemplate
317 */
318 public function __construct($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) {
319
320 $this->restart($file, $tpldir, $files, $mainblock, $autosetup, $this->tag_start_delim, $this->tag_end_delim);
321 }
322
323 /*
324 * PHP 4 Constructor - Instantiate the object
325 *
326 * @deprecated Use PHP 5 constructor instead
327 * @param string $file Template file to work on
328 * @param string/array $tpldir Location of template files (useful for keeping files outside web server root)
329 * @param array $files Filenames lookup
330 * @param string $mainblock Name of main block in the template
331 * @param boolean $autosetup If true, run setup() as part of constuctor
332 * @return XTemplate
333 *
334 public function XTemplate ($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) {
335
336 assert('Deprecated - use PHP 5 constructor');
337 }*/
338
339
340 /***************************************************************************/
341 /***[ public stuff ]********************************************************/
342 /***************************************************************************/
343
344 /**
345 * Restart the class - allows one instantiation with several files processed by restarting
346 * e.g. $xtpl = new XTemplate('file1.xtpl');
347 * $xtpl->parse('main');
348 * $xtpl->out('main');
349 * $xtpl->restart('file2.xtpl');
350 * $xtpl->parse('main');
351 * $xtpl->out('main');
352 * (Added in response to sf:641407 feature request)
353 *
354 * @param string $file Template file to work on
355 * @param string/array $tpldir Location of template files
356 * @param array $files Filenames lookup
357 * @param string $mainblock Name of main block in the template
358 * @param boolean $autosetup If true, run setup() as part of restarting
359 * @param string $tag_start {
360 * @param string $tag_end }
361 */
362 public function restart ($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true, $tag_start = '{', $tag_end = '}') {
363
364 $this->filename = $file;
365
366 // From SF Feature request 1202027
367 // Kenneth Kalmer
368 $this->tpldir = $tpldir;
369 if (defined('XTPL_DIR') && empty($this->tpldir)) {
370 $this->tpldir = XTPL_DIR;
371 }
372
373 if (is_array($files)) {
374 $this->files = $files;
375 }
376
377 $this->mainblock = $mainblock;
378
379 $this->tag_start_delim = $tag_start;
380 $this->tag_end_delim = $tag_end;
381
382 // Start with fresh file contents
383 $this->filecontents = '';
384
385 // Reset the template arrays
386 $this->blocks = array();
387 $this->parsed_blocks = array();
388 $this->preparsed_blocks = array();
389 $this->block_parse_order = array();
390 $this->sub_blocks = array();
391 $this->vars = array();
392 $this->filevars = array();
393 $this->filevar_parent = array();
394 $this->filecache = array();
395
396 if ($autosetup) {
397 $this->setup();
398 }
399 }
400
401 /**
402 * setup - the elements that were previously in the constructor
403 *
404 * @access public
405 * @param boolean $add_outer If true is passed when called, it adds an outer main block to the file
406 */
407 public function setup ($add_outer = false) {
408
409 $this->tag_start_delim = preg_quote($this->tag_start_delim);
410 $this->tag_end_delim = preg_quote($this->tag_end_delim);
411
412 // Setup the file delimiters
413
414 // regexp for file includes
415 $this->file_delim = "/" . $this->tag_start_delim . "FILE\s*\"([^\"]+)\"" . $this->comment_preg . $this->tag_end_delim . "/m";
416
417 // regexp for file includes
418 $this->filevar_delim = "/" . $this->tag_start_delim . "FILE\s*" . $this->tag_start_delim . "([A-Za-z0-9\._]+?)" . $this->comment_preg . $this->tag_end_delim . $this->comment_preg . $this->tag_end_delim . "/m";
419
420 // regexp for file includes w/ newlines
421 $this->filevar_delim_nl = "/^\s*" . $this->tag_start_delim . "FILE\s*" . $this->tag_start_delim . "([A-Za-z0-9\._]+?)" . $this->comment_preg . $this->tag_end_delim . $this->comment_preg . $this->tag_end_delim . "\s*\n/m";
422
423 if (empty($this->filecontents)) {
424 // read in template file
425 $this->filecontents = $this->_r_getfile($this->filename);
426 }
427
428 if ($add_outer) {
429 $this->_add_outer_block();
430 }
431
432 // preprocess some stuff
433 $this->blocks = $this->_maketree($this->filecontents, '');
434 $this->filevar_parent = $this->_store_filevar_parents($this->blocks);
435 $this->scan_globals();
436 }
437
438 /**
439 * assign a variable
440 *
441 * @example Simplest case:
442 * @example $xtpl->assign('name', 'value');
443 * @example {name} in template
444 *
445 * @example Array assign:
446 * @example $xtpl->assign(array('name' => 'value', 'name2' => 'value2'));
447 * @example {name} {name2} in template
448 *
449 * @example Value as array assign:
450 * @example $xtpl->assign('name', array('key' => 'value', 'key2' => 'value2'));
451 * @example {name.key} {name.key2} in template
452 *
453 * @example Reset array:
454 * @example $xtpl->assign('name', array('key' => 'value', 'key2' => 'value2'));
455 * @example // Other code then:
456 * @example $xtpl->assign('name', array('key3' => 'value3'), false);
457 * @example {name.key} {name.key2} {name.key3} in template
458 *
459 * @access public
460 * @param string $name Variable to assign $val to
461 * @param string / array $val Value to assign to $name
462 * @param boolean $reset_array Reset the variable array if $val is an array
463 */
464 public function assign ($name, $val = '', $reset_array = true) {
465
466 if (is_array($name)) {
467
468 foreach ($name as $k => $v) {
469
470 $this->vars[$k] = $v;
471 }
472 } elseif (is_array($val)) {
473
474 // Clear the existing values
475 if ($reset_array) {
476 $this->vars[$name] = array();
477 }
478
479 foreach ($val as $k => $v) {
480
481 $this->vars[$name][$k] = $v;
482 }
483
484 } else {
485
486 $this->vars[$name] = $val;
487 }
488 }
489
490 /**
491 * assign a file variable
492 *
493 * @access public
494 * @param string $name Variable to assign $val to
495 * @param string / array $val Values to assign to $name
496 */
497 public function assign_file ($name, $val = '') {
498
499 if (is_array($name)) {
500
501 foreach ($name as $k => $v) {
502
503 $this->_assign_file_sub($k, $v);
504 }
505 } else {
506
507 $this->_assign_file_sub($name, $val);
508 }
509 }
510
511 /**
512 * parse a block
513 *
514 * @access public
515 * @param string $bname Block name to parse
516 */
517 public function parse ($bname) {
518
519 if (isset($this->preparsed_blocks[$bname])) {
520
521 $copy = $this->preparsed_blocks[$bname];
522
523 } elseif (isset($this->blocks[$bname])) {
524
525 $copy = $this->blocks[$bname];
526
527 } elseif ($this->_ignore_missing_blocks) {
528 // ------------------------------------------------------
529 // NW : 17 Oct 2002. Added default of ignore_missing_blocks
530 // to allow for generalised processing where some
531 // blocks may be removed from the HTML without the
532 // processing code needing to be altered.
533 // ------------------------------------------------------
534 // JRC: 3/1/2003 added set error to ignore missing functionality
535 $this->_set_error("parse: blockname [$bname] does not exist");
536 return;
537
538 } else {
539
540 $this->_set_error("parse: blockname [$bname] does not exist");
541 }
542
543 /* from there we should have no more {FILE } directives */
544 if (!isset($copy)) {
545 die('Block: ' . $bname);
546 }
547
548 $copy = preg_replace($this->filevar_delim_nl, '', $copy);
549
550 $var_array = array();
551
552 /* find & replace variables+blocks */
553 preg_match_all("|" . $this->tag_start_delim . "([A-Za-z0-9\._]+?" . $this->comment_preg . ")" . $this->tag_end_delim. "|", $copy, $var_array);
554
555 $var_array = $var_array[1];
556
557 foreach ($var_array as $k => $v) {
558
559 // Are there any comments in the tags {tag#a comment for documenting the template}
560 $any_comments = explode('#', $v);
561 $v = rtrim($any_comments[0]);
562
563 if (sizeof($any_comments) > 1) {
564
565 $comments = $any_comments[1];
566 } else {
567
568 $comments = '';
569 }
570
571 $sub = explode('.', $v);
572
573 if ($sub[0] == '_BLOCK_') {
574
575 unset($sub[0]);
576
577 $bname2 = implode('.', $sub);
578
579 // trinary operator eliminates assign error in E_ALL reporting
580 $var = isset($this->parsed_blocks[$bname2]) ? $this->parsed_blocks[$bname2] : null;
581 $nul = (!isset($this->_null_block[$bname2])) ? $this->_null_block[''] : $this->_null_block[$bname2];
582
583 if ($var === '') {
584
585 if ($nul == '') {
586 // -----------------------------------------------------------
587 // Removed requirement for blocks to be at the start of string
588 // -----------------------------------------------------------
589 // $copy=preg_replace("/^\s*\{".$v."\}\s*\n*/m","",$copy);
590 // Now blocks don't need to be at the beginning of a line,
591 //$copy=preg_replace("/\s*" . $this->tag_start_delim . $v . $this->tag_end_delim . "\s*\n*/m","",$copy);
592 $copy = preg_replace("|" . $this->tag_start_delim . $v . $this->tag_end_delim . "|m", '', $copy);
593
594 } else {
595
596 $copy = preg_replace("|" . $this->tag_start_delim . $v . $this->tag_end_delim . "|m", "$nul", $copy);
597 }
598 } else {
599
600 //$var = trim($var);
601 switch (true) {
602 case preg_match('/^\n/', $var) && preg_match('/\n$/', $var):
603 $var = substr($var, 1, -1);
604 break;
605
606 case preg_match('/^\n/', $var):
607 $var = substr($var, 1);
608 break;
609
610 case preg_match('/\n$/', $var):
611 $var = substr($var, 0, -1);
612 break;
613 }
614
615 // SF Bug no. 810773 - thanks anonymous
616 $var = str_replace('\\', '\\\\', $var);
617 // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04
618 $var = str_replace('$', '\\$', $var);
619 // Replaced str_replaces with preg_quote
620 //$var = preg_quote($var);
621 $var = str_replace('\\|', '|', $var);
622 $copy = preg_replace("|" . $this->tag_start_delim . $v . $this->tag_end_delim . "|m", "$var", $copy);
623
624 if (preg_match('/^\n/', $copy) && preg_match('/\n$/', $copy)) {
625 $copy = substr($copy, 1, -1);
626 }
627 }
628 } else {
629
630 $var = $this->vars;
631
632 foreach ($sub as $v1) {
633
634 // NW 4 Oct 2002 - Added isset and is_array check to avoid NOTICE messages
635 // JC 17 Oct 2002 - Changed EMPTY to stlen=0
636 // if (empty($var[$v1])) { // this line would think that zeros(0) were empty - which is not true
637 if (!isset($var[$v1]) || (!is_array($var[$v1]) && strlen($var[$v1]) == 0)) {
638
639 // Check for constant, when variable not assigned
640 if (defined($v1)) {
641
642 $var[$v1] = constant($v1);
643
644 } else {
645
646 $var[$v1] = null;
647 }
648 }
649
650 $var = $var[$v1];
651 }
652
653 $nul = (!isset($this->_null_string[$v])) ? ($this->_null_string[""]) : ($this->_null_string[$v]);
654 $var = (!isset($var)) ? $nul : $var;
655
656 if ($var === '') {
657 // -----------------------------------------------------------
658 // Removed requriement for blocks to be at the start of string
659 // -----------------------------------------------------------
660 // $copy=preg_replace("|^\s*\{".$v." ?#?".$comments."\}\s*\n|m","",$copy);
661 $copy = preg_replace("|" . $this->tag_start_delim . $v . "( ?#" . $comments . ")?" . $this->tag_end_delim . "|m", '', $copy);
662 }
663
664 $var = trim($var);
665 // SF Bug no. 810773 - thanks anonymous
666 $var = str_replace('\\', '\\\\', $var);
667 // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04
668 $var = str_replace('$', '\\$', $var);
669 // Replace str_replaces with preg_quote
670 //$var = preg_quote($var);
671 $var = str_replace('\\|', '|', $var);
672 $copy = preg_replace("|" . $this->tag_start_delim . $v . "( ?#" . $comments . ")?" . $this->tag_end_delim . "|m", "$var", $copy);
673
674 if (preg_match('/^\n/', $copy) && preg_match('/\n$/', $copy)) {
675 $copy = substr($copy, 1);
676 }
677 }
678 }
679
680 if (isset($this->parsed_blocks[$bname])) {
681 $this->parsed_blocks[$bname] .= $copy;
682 } else {
683 $this->parsed_blocks[$bname] = $copy;
684 }
685
686 /* reset sub-blocks */
687 if ($this->_autoreset && (!empty($this->sub_blocks[$bname]))) {
688
689 reset($this->sub_blocks[$bname]);
690
691 foreach ($this->sub_blocks[$bname] as $k => $v) {
692 $this->reset($v);
693 }
694 }
695 }
696
697 /**
698 * returns the parsed text for a block, including all sub-blocks.
699 *
700 * @access public
701 * @param string $bname Block name to parse
702 */
703 public function rparse ($bname) {
704
705 if (!empty($this->sub_blocks[$bname])) {
706
707 reset($this->sub_blocks[$bname]);
708
709 foreach ($this->sub_blocks[$bname] as $k => $v) {
710
711 if (!empty($v)) {
712 $this->rparse($v);
713 }
714 }
715 }
716
717 $this->parse($bname);
718 }
719
720 /**
721 * inserts a loop ( call assign & parse )
722 *
723 * @access public
724 * @param string $bname Block name to assign
725 * @param string $var Variable to assign values to
726 * @param string / array $value Value to assign to $var
727 */
728 public function insert_loop ($bname, $var, $value = '') {
729
730 $this->assign($var, $value);
731 $this->parse($bname);
732 }
733
734 /**
735 * parses a block for every set of data in the values array
736 *
737 * @access public
738 * @param string $bname Block name to loop
739 * @param string $var Variable to assign values to
740 * @param array $values Values to assign to $var
741 */
742 public function array_loop ($bname, $var, &$values) {
743
744 if (is_array($values)) {
745
746 foreach($values as $v) {
747
748 $this->insert_loop($bname, $var, $v);
749 }
750 }
751 }
752
753 /**
754 * returns the parsed text for a block
755 *
756 * @access public
757 * @param string $bname Block name to return
758 * @return string
759 */
760 public function text ($bname = '') {
761
762 $text = '';
763
764 if ($this->debug && $this->output_type == 'HTML') {
765 // JC 20/11/02 echo the template filename if in development as
766 // html comment
767 $text .= '<!-- XTemplate: ' . realpath($this->filename) . " -->\n";
768 }
769
770 $bname = !empty($bname) ? $bname : $this->mainblock;
771
772 $text .= isset($this->parsed_blocks[$bname]) ? $this->parsed_blocks[$bname] : $this->get_error();
773
774 return $text;
775 }
776
777 /**
778 * prints the parsed text
779 *
780 * @access public
781 * @param string $bname Block name to echo out
782 */
783 public function out ($bname) {
784
785 $out = $this->text($bname);
786 // $length=strlen($out);
787 //header("Content-Length: ".$length); // TODO: Comment this back in later
788
789 echo $out;
790 }
791
792 /**
793 * prints the parsed text to a specified file
794 *
795 * @access public
796 * @param string $bname Block name to write out
797 * @param string $fname File name to write to
798 */
799 public function out_file ($bname, $fname) {
800
801 if (!empty($bname) && !empty($fname) && is_writeable($fname)) {
802
803 $fp = fopen($fname, 'w');
804 fwrite($fp, $this->text($bname));
805 fclose($fp);
806 }
807 }
808
809 /**
810 * resets the parsed text
811 *
812 * @access public
813 * @param string $bname Block to reset
814 */
815 public function reset ($bname) {
816
817 $this->parsed_blocks[$bname] = '';
818 }
819
820 /**
821 * returns true if block was parsed, false if not
822 *
823 * @access public
824 * @param string $bname Block name to test
825 * @return boolean
826 */
827 public function parsed ($bname) {
828
829 return (!empty($this->parsed_blocks[$bname]));
830 }
831
832 /**
833 * sets the string to replace in case the var was not assigned
834 *
835 * @access public
836 * @param string $str Display string for null block
837 * @param string $varname Variable name to apply $str to
838 */
839 public function set_null_string($str, $varname = '') {
840
841 $this->_null_string[$varname] = $str;
842 }
843
844 /**
845 * Backwards compatibility only
846 *
847 * @param string $str
848 * @param string $varname
849 * @deprecated Change to set_null_string to keep in with rest of naming convention
850 */
851 public function SetNullString ($str, $varname = '') {
852 $this->set_null_string($str, $varname);
853 }
854
855 /**
856 * sets the string to replace in case the block was not parsed
857 *
858 * @access public
859 * @param string $str Display string for null block
860 * @param string $bname Block name to apply $str to
861 */
862 public function set_null_block ($str, $bname = '') {
863
864 $this->_null_block[$bname] = $str;
865 }
866
867 /**
868 * Backwards compatibility only
869 *
870 * @param string $str
871 * @param string $bname
872 * @deprecated Change to set_null_block to keep in with rest of naming convention
873 */
874 public function SetNullBlock ($str, $bname = '') {
875 $this->set_null_block($str, $bname);
876 }
877
878 /**
879 * sets AUTORESET to 1. (default is 1)
880 * if set to 1, parse() automatically resets the parsed blocks' sub blocks
881 * (for multiple level blocks)
882 *
883 * @access public
884 */
885 public function set_autoreset () {
886
887 $this->_autoreset = true;
888 }
889
890 /**
891 * sets AUTORESET to 0. (default is 1)
892 * if set to 1, parse() automatically resets the parsed blocks' sub blocks
893 * (for multiple level blocks)
894 *
895 * @access public
896 */
897 public function clear_autoreset () {
898
899 $this->_autoreset = false;
900 }
901
902 /**
903 * scans global variables and assigns to PHP array
904 *
905 * @access public
906 */
907 public function scan_globals () {
908
909 foreach ($GLOBALS as $k => $v) {
910 $GLOB[$k] = $v;
911 }
912
913 /**
914 * Access global variables as:
915 * @example {PHP._SERVER.HTTP_HOST}
916 * in your template!
917 */
918 $this->assign('PHP', $GLOB);
919 }
920
921 /**
922 * gets error condition / string
923 *
924 * @access public
925 * @return boolean / string
926 */
927 public function get_error () {
928
929 // JRC: 3/1/2003 Added ouptut wrapper and detection of output type for error message output
930 $retval = false;
931
932 if ($this->_error != '') {
933
934 switch ($this->output_type) {
935 case 'HTML':
936 case 'html':
937 $retval = '<b>[XTemplate]</b><ul>' . nl2br(str_replace('* ', '<li>', str_replace(" *\n", "</li>\n", $this->_error))) . '</ul>';
938 break;
939
940 default:
941 $retval = '[XTemplate] ' . str_replace(' *\n', "\n", $this->_error);
942 break;
943 }
944 }
945
946 return $retval;
947 }
948
949 /***************************************************************************/
950 /***[ private stuff ]*******************************************************/
951 /***************************************************************************/
952
953 /**
954 * generates the array containing to-be-parsed stuff:
955 * $blocks["main"],$blocks["main.table"],$blocks["main.table.row"], etc.
956 * also builds the reverse parse order.
957 *
958 * @access public - aiming for private
959 * @param string $con content to be processed
960 * @param string $parentblock name of the parent block in the block hierarchy
961 */
962 public function _maketree ($con, $parentblock='') {
963
964 $blocks = array();
965
966 $con2 = explode($this->block_start_delim, $con);
967
968 if (!empty($parentblock)) {
969
970 $block_names = explode('.', $parentblock);
971 $level = sizeof($block_names);
972
973 } else {
974
975 $block_names = array();
976 $level = 0;
977 }
978
979 // JRC 06/04/2005 Added block comments (on BEGIN or END) <!-- BEGIN: block_name#Comments placed here -->
980 //$patt = "($this->block_start_word|$this->block_end_word)\s*(\w+)\s*$this->block_end_delim(.*)";
981 $patt = "(" . $this->block_start_word . "|" . $this->block_end_word . ")\s*(\w+)" . $this->comment_preg . "\s*" . $this->block_end_delim . "(.*)";
982
983 foreach($con2 as $k => $v) {
984
985 $res = array();
986
987 if (preg_match_all("/$patt/ims", $v, $res, PREG_SET_ORDER)) {
988 // $res[0][1] = BEGIN or END
989 // $res[0][2] = block name
990 // $res[0][3] = comment
991 // $res[0][4] = kinda content
992 $block_word = $res[0][1];
993 $block_name = $res[0][2];
994 $comment = $res[0][3];
995 $content = $res[0][4];
996
997 if (strtoupper($block_word) == $this->block_start_word) {
998
999 $parent_name = implode('.', $block_names);
1000
1001 // add one level - array("main","table","row")
1002 $block_names[++$level] = $block_name;
1003
1004 // make block name (main.table.row)
1005 $cur_block_name=implode('.', $block_names);
1006
1007 // build block parsing order (reverse)
1008 $this->block_parse_order[] = $cur_block_name;
1009
1010 //add contents. trinary operator eliminates assign error in E_ALL reporting
1011 $blocks[$cur_block_name] = isset($blocks[$cur_block_name]) ? $blocks[$cur_block_name] . $content : $content;
1012
1013 // add {_BLOCK_.blockname} string to parent block
1014 $blocks[$parent_name] .= str_replace('\\', '', $this->tag_start_delim) . '_BLOCK_.' . $cur_block_name . str_replace('\\', '', $this->tag_end_delim);
1015
1016 // store sub block names for autoresetting and recursive parsing
1017 $this->sub_blocks[$parent_name][] = $cur_block_name;
1018
1019 // store sub block names for autoresetting
1020 $this->sub_blocks[$cur_block_name][] = '';
1021
1022 } else if (strtoupper($block_word) == $this->block_end_word) {
1023
1024 unset($block_names[$level--]);
1025
1026 $parent_name = implode('.', $block_names);
1027
1028 // add rest of block to parent block
1029 $blocks[$parent_name] .= $content;
1030 }
1031 } else {
1032
1033 // no block delimiters found
1034 // Saves doing multiple implodes - less overhead
1035 $tmp = implode('.', $block_names);
1036
1037 if ($k) {
1038 $blocks[$tmp] .= $this->block_start_delim;
1039 }
1040
1041 // trinary operator eliminates assign error in E_ALL reporting
1042 $blocks[$tmp] = isset($blocks[$tmp]) ? $blocks[$tmp] . $v : $v;
1043 }
1044 }
1045
1046 return $blocks;
1047 }
1048
1049 /**
1050 * Sub processing for assign_file method
1051 *
1052 * @access private
1053 * @param string $name
1054 * @param string $val
1055 */
1056 private function _assign_file_sub ($name, $val) {
1057
1058 if (isset($this->filevar_parent[$name])) {
1059
1060 if ($val != '') {
1061
1062 $val = $this->_r_getfile($val);
1063
1064 foreach($this->filevar_parent[$name] as $parent) {
1065
1066 if (isset($this->preparsed_blocks[$parent]) && !isset($this->filevars[$name])) {
1067
1068 $copy = $this->preparsed_blocks[$parent];
1069
1070 } elseif (isset($this->blocks[$parent])) {
1071
1072 $copy = $this->blocks[$parent];
1073 }
1074
1075 $res = array();
1076
1077 preg_match_all($this->filevar_delim, $copy, $res, PREG_SET_ORDER);
1078
1079 if (is_array($res) && isset($res[0])) {
1080
1081 // Changed as per solution in SF bug ID #1261828
1082 foreach ($res as $v) {
1083
1084 // Changed as per solution in SF bug ID #1261828
1085 if ($v[1] == $name) {
1086
1087 // Changed as per solution in SF bug ID #1261828
1088 $copy = preg_replace("/" . preg_quote($v[0]) . "/", "$val", $copy);
1089 $this->preparsed_blocks = array_merge($this->preparsed_blocks, $this->_maketree($copy, $parent));
1090 $this->filevar_parent = array_merge($this->filevar_parent, $this->_store_filevar_parents($this->preparsed_blocks));
1091 }
1092 }
1093 }
1094 }
1095 }
1096 }
1097
1098 $this->filevars[$name] = $val;
1099 }
1100
1101 /**
1102 * store container block's name for file variables
1103 *
1104 * @access public - aiming for private
1105 * @param array $blocks
1106 * @return array
1107 */
1108 public function _store_filevar_parents ($blocks){
1109
1110 $parents = array();
1111
1112 foreach ($blocks as $bname => $con) {
1113
1114 $res = array();
1115
1116 preg_match_all($this->filevar_delim, $con, $res);
1117
1118 foreach ($res[1] as $k => $v) {
1119
1120 $parents[$v][] = $bname;
1121 }
1122 }
1123 return $parents;
1124 }
1125
1126 /**
1127 * Set the error string
1128 *
1129 * @access private
1130 * @param string $str
1131 */
1132 private function _set_error ($str) {
1133
1134 // JRC: 3/1/2003 Made to append the error messages
1135 $this->_error .= '* ' . $str . " *\n";
1136 // JRC: 3/1/2003 Removed trigger error, use this externally if you want it eg. trigger_error($xtpl->get_error())
1137 //trigger_error($this->get_error());
1138 }
1139
1140 /**
1141 * returns the contents of a file
1142 *
1143 * @access protected
1144 * @param string $file
1145 * @return string
1146 */
1147 protected function _getfile ($file) {
1148
1149 if (!isset($file)) {
1150 // JC 19/12/02 added $file to error message
1151 $this->_set_error('!isset file name!' . $file);
1152
1153 return '';
1154 }
1155
1156 // check if filename is mapped to other filename
1157 if (isset($this->files)) {
1158
1159 if (isset($this->files[$file])) {
1160
1161 $file = $this->files[$file];
1162 }
1163 }
1164
1165 // prepend template dir
1166 if (!empty($this->tpldir)) {
1167
1168 /**
1169 * Support hierarchy of file locations to search
1170 *
1171 * @example Supply array of filepaths when instantiating
1172 * First path supplied that has the named file is prioritised
1173 * $xtpl = new XTemplate('myfile.xtpl', array('.','/mypath', '/mypath2'));
1174 * @since 29/05/2007
1175 */
1176 if (is_array($this->tpldir)) {
1177
1178 foreach ($this->tpldir as $dir) {
1179
1180 if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) {
1181 $file = $dir . DIRECTORY_SEPARATOR . $file;
1182 break;
1183 }
1184 }
1185 } else {
1186
1187 $file = $this->tpldir. DIRECTORY_SEPARATOR . $file;
1188 }
1189 }
1190
1191 $file_text = '';
1192
1193 if (isset($this->filecache[$file])) {
1194
1195 $file_text .= $this->filecache[$file];
1196
1197 if ($this->debug) {
1198 $file_text = '<!-- XTemplate debug cached: ' . realpath($file) . ' -->' . "\n" . $file_text;
1199 }
1200
1201 } else {
1202
1203 if (is_file($file) && is_readable($file)) {
1204
1205 if (filesize($file)) {
1206
1207 if (!($fh = fopen($file, 'r'))) {
1208
1209 $this->_set_error('Cannot open file: ' . realpath($file));
1210 return '';
1211 }
1212
1213 $file_text .= fread($fh,filesize($file));
1214 fclose($fh);
1215
1216 }
1217
1218 if ($this->debug) {
1219 $file_text = '<!-- XTemplate debug: ' . realpath($file) . ' -->' . "\n" . $file_text;
1220 }
1221
1222 } elseif (str_replace('.', '', phpversion()) >= '430' && $file_text = @file_get_contents($file, true)) {
1223 // Enable use of include path by using file_get_contents
1224 // Implemented at suggestion of SF Feature Request ID #1529478 michaelgroh
1225 if ($file_text === false) {
1226 $this->_set_error("[" . realpath($file) . "] ($file) does not exist");
1227 $file_text = "<b>__XTemplate fatal error: file [$file] does not exist in the include path__</b>";
1228 } elseif ($this->debug) {
1229 $file_text = '<!-- XTemplate debug: ' . realpath($file) . ' (via include path) -->' . "\n" . $file_text;
1230 }
1231 } elseif (!is_file($file)) {
1232
1233 // NW 17 Oct 2002 : Added realpath around the file name to identify where the code is searching.
1234 $this->_set_error("[" . realpath($file) . "] ($file) does not exist");
1235 $file_text .= "<b>__XTemplate fatal error: file [$file] does not exist__</b>";
1236
1237 } elseif (!is_readable($file)) {
1238
1239 $this->_set_error("[" . realpath($file) . "] ($file) is not readable");
1240 $file_text .= "<b>__XTemplate fatal error: file [$file] is not readable__</b>";
1241 }
1242
1243 $this->filecache[$file] = $file_text;
1244 }
1245
1246 return $file_text;
1247 }
1248
1249 /**
1250 * recursively gets the content of a file with {FILE "filename.tpl"} directives
1251 *
1252 * @access public - aiming for private
1253 * @param string $file
1254 * @return string
1255 */
1256 public function _r_getfile ($file) {
1257
1258 $text = $this->_getfile($file);
1259
1260 $res = array();
1261
1262 while (preg_match($this->file_delim,$text,$res)) {
1263
1264 $text2 = $this->_getfile($res[1]);
1265 $text = preg_replace("'".preg_quote($res[0])."'",$text2,$text);
1266 }
1267
1268 return $text;
1269 }
1270
1271
1272 /**
1273 * add an outer block delimiter set useful for rtfs etc - keeps them editable in word
1274 *
1275 * @access private
1276 */
1277 private function _add_outer_block () {
1278
1279 $before = $this->block_start_delim . $this->block_start_word . ' ' . $this->mainblock . ' ' . $this->block_end_delim;
1280 $after = $this->block_start_delim . $this->block_end_word . ' ' . $this->mainblock . ' ' . $this->block_end_delim;
1281
1282 $this->filecontents = $before . "\n" . $this->filecontents . "\n" . $after;
1283 }
1284
1285 /**
1286 * Debug function - var_dump wrapped in '<pre></pre>' tags
1287 *
1288 * @access private
1289 * @param multiple var_dumps all the supplied arguments
1290 */
1291 private function _pre_var_dump ($args) {
1292
1293 if ($this->debug) {
1294 echo '<pre>';
1295 var_dump(func_get_args());
1296 echo '</pre>';
1297 }
1298 }
1299} /* end of XTemplate class. */
1300
1301?>