move code up one directory
[atutor.git] / include / classes / Savant2 / Savant2.php
1 <?php
2
3 /**
4
5 * Error constants.
6
7 */
8
9 define('SAVANT2_ERROR_ASSIGN',       -1);
10 define('SAVANT2_ERROR_ASSIGNREF',    -2);
11 define('SAVANT2_ERROR_COMPILER',     -3);
12 define('SAVANT2_ERROR_NOFILTER',     -4);
13 define('SAVANT2_ERROR_NOPLUGIN',     -5);
14 define('SAVANT2_ERROR_NOSCRIPT',     -6);
15 define('SAVANT2_ERROR_NOTEMPLATE',   -7);
16 define('SAVANT2_ERROR_COMPILE_FAIL', -8);
17
18
19 /**
20
21 * Error messages.
22
23 */
24
25 if (! isset($GLOBALS['_SAVANT2']['error'])) {
26         $GLOBALS['_SAVANT2']['error'] = array(
27                 SAVANT2_ERROR_ASSIGN       => 'assign() parameters not correct',
28                 SAVANT2_ERROR_ASSIGNREF    => 'assignRef() parameters not correct',
29                 SAVANT2_ERROR_COMPILER     => 'compiler not an object or has no compile() method',
30                 SAVANT2_ERROR_NOFILTER     => 'filter file not found',
31                 SAVANT2_ERROR_NOPLUGIN     => 'plugin file not found',
32                 SAVANT2_ERROR_NOSCRIPT     => 'compiled template script file not found',
33                 SAVANT2_ERROR_NOTEMPLATE   => 'template source file not found',
34                 SAVANT2_ERROR_COMPILE_FAIL => 'template source failed to compile'
35         );
36 }
37
38
39 /**
40
41 * Provides an object-oriented template system.
42
43 * Savant2 helps you separate model logic from view logic using PHP as
44 * the template language. By default, Savant2 does not compile templates.
45 * However, you may pass an optional compiler object to compile template
46 * source to include-able PHP code.
47
48 * Please see the documentation at {@link http://phpsavant.com/}, and be
49 * sure to donate! :-)
50
51 * $Id: Savant2.php,v 1.32 2006/03/05 16:58:38 pmjones Exp $
52
53 * @author Paul M. Jones <pmjones@ciaweb.net>
54
55 * @package Savant2
56
57 * @version 2.4.3 stable
58
59 * @license LGPL http://www.gnu.org/copyleft/lesser.html
60
61 * This program is free software; you can redistribute it and/or modify
62 * it under the terms of the GNU Lesser General Public License as
63 * published by the Free Software Foundation; either version 2.1 of the
64 * License, or (at your option) any later version.
65 *
66 * This program is distributed in the hope that it will be useful, but
67 * WITHOUT ANY WARRANTY; without even the implied warranty of
68 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
69 * Lesser General Public License for more details.
70
71 */
72
73 class Savant2 {
74         
75         
76         /**
77         * 
78         * PHP5 ONLY:  Whether or not to use __autoload().  Default is false.
79         * 
80         * @access private
81         * 
82         * @var bool
83         * 
84         */
85         
86         var $_autoload = false;
87         
88         
89         /**
90         * 
91         * PHP5 ONLY: What method __call() will alias to.
92         * 
93         * Generally 'plugin' or 'splugin' (as __call() is intended for those).
94         * 
95         * @access private
96         * 
97         * @var string
98         * 
99         */
100         
101         var $_call = 'plugin';
102         
103         
104         /**
105         * 
106         * The custom compiler (pre-processor) object, if any.
107         * 
108         * @access private
109         * 
110         * @var object
111         * 
112         */
113         
114         var $_compiler = null;
115         
116         
117         /**
118         * 
119         * The class type to use when instantiating error objects.
120         * 
121         * @access private
122         * 
123         * @var string
124         * 
125         */
126         
127         var $_error = null;
128         
129         
130         /**
131         * 
132         * Array of callbacks used to escape output.
133         * 
134         * @access private
135         * 
136         * @var array
137         * 
138         * @see setEscape()
139         * 
140         * @see addEscape()
141         * 
142         * @see escape()
143         * 
144         * @see _()
145         * 
146         */
147         
148         var $_escape = array('htmlspecialchars');
149         
150         
151         /**
152         * 
153         * Whether or not to extract assigned variables into fetch() scope.
154         * 
155         * When true, all variables and references assigned to Savant2 are
156         * extracted into the local scope of the template script at fetch()
157         * time, and may be addressed as "$varname" instead of
158         * "$this->varname".  The "$this->varname" notation will also work.
159         * 
160         * When false, you //must// use "$this->varname" in your templates to
161         * address a variable instead of "$varname".  This has three
162         * benefits: speed (no time spent extracting variables), memory use
163         * (saves RAM by not making new references to variables), and clarity
164         * (any $this->varname is obviously an assigned var, and vars created
165         * within the template are not prefixed with $this).
166         * 
167         * @access private
168         * 
169         * @var bool
170         * 
171         */
172         
173         var $_extract = false;
174         
175         
176         /**
177         * 
178         * The output of the template script.
179         * 
180         * @access private
181         * 
182         * @var string
183         * 
184         */
185         
186         var $_output = null;
187         
188         
189         /**
190         * 
191         * The set of search directories for resources (plugins/filters) and
192         * templates.
193         * 
194         * @access private
195         * 
196         * @var array
197         * 
198         */
199         
200         var $_path = array(
201                 'resource' => array(),
202                 'template' => array()
203         );
204         
205         
206         /**
207         * 
208         * Array of resource (plugin/filter) object instances.
209         * 
210         * @access private
211         * 
212         * @var array
213         * 
214         */
215         
216         var $_resource = array(
217                 'plugin' => array(),
218                 'filter' => array()
219         );
220         
221         
222         /**
223         * 
224         * Whether or not to automatically self-reference in plugins and filters.
225         * 
226         * @access private
227         * 
228         * @var bool
229         * 
230         */
231         
232         var $_reference = false;
233         
234         
235         /**
236         * 
237         * Whether or not to restrict template includes only to registered paths.
238         * 
239         * @access private
240         * 
241         * @var bool
242         * 
243         */
244         
245         var $_restrict = false;
246         
247         
248         /**
249         * 
250         * The path to the compiled template script file.
251         * 
252         * By default, the template source and template script are the same file.
253         *
254         * @access private
255         * 
256         * @var string
257         * 
258         */
259         
260         var $_script = null;
261         
262         
263         /**
264         * 
265         * The name of the default template source file.
266         * 
267         * @access private
268         * 
269         * @var string
270         * 
271         */
272         
273         var $_template = null;
274         
275         
276         // -----------------------------------------------------------------
277         //
278         // Constructor and general property setters
279         //
280         // -----------------------------------------------------------------
281         
282         
283         /**
284         * 
285         * Constructor.
286         * 
287         * @access public
288         * 
289         * @param array $conf An associative array of configuration keys for
290         * the Savant2 object.  Any, or none, of the keys may be set. The
291         * keys are:
292         * 
293         * 'template_path' => The default path string or array of directories
294         * to search for templates.
295         * 
296         * 'resource_path' => The default path string or array of directories
297         * to search for plugin and filter resources.
298         * 
299         * 'error' => The custom error class that Savant2 should use
300         * when returning errors.
301         * 
302         * 'extract' => Whether or not to extract variables into the local
303         * scope when executing a template.
304         * 
305         * 'template' => The default template source name to use.
306         * 
307         */
308         
309         function Savant2($conf = array())
310         {
311                 // set the default template search dirs
312                 if (isset($conf['template_path'])) {
313                         // user-defined dirs
314                         $this->setPath('template', $conf['template_path']);
315                 } else {
316                         // default directory only
317                         $this->setPath('template', null);
318                 }
319                 
320                 // set the default filter search dirs
321                 if (isset($conf['resource_path'])) {
322                         // user-defined dirs
323                         $this->setPath('resource', $conf['resource_path']);
324                 } else {
325                         // default directory only
326                         $this->setPath('resource', null);
327                 }
328                 
329                 // do we allow __autoload() use?
330                 if (isset($conf['autoload'])) {
331                         $this->setAutoload($conf['autoload']);
332                 }
333                 
334                 // set the error class
335                 if (isset($conf['error'])) {
336                         $this->setError($conf['error']);
337                 }
338                 
339                 // set the extraction flag
340                 if (isset($conf['extract'])) {
341                         $this->setExtract($conf['extract']);
342                 }
343                 
344                 // set the restrict flag
345                 if (isset($conf['restrict'])) {
346                         $this->setRestrict($conf['restrict']);
347                 }
348                 
349                 // set the Savant reference flag
350                 if (isset($conf['reference'])) {
351                         $this->setReference($conf['reference']);
352                 }
353                 
354                 // set the default template
355                 if (isset($conf['template'])) {
356                         $this->setTemplate($conf['template']);
357                 }
358                 
359                 // set the output escaping callbacks
360                 if (isset($config['escape'])) {
361                         call_user_func_array(
362                                 array($this, 'setEscape'),
363                                 (array) $config['escape']
364                         );
365                 }       
366         }
367         
368         /**
369         * 
370         * Sets whether or not __autoload() is used when loading classes.
371         * 
372         * @access public
373         * 
374         * @param bool $flag True to use __autoload(), false to not use it.
375         * 
376         * @return void
377         * 
378         */
379         
380         function setAutoload($flag) {
381                 $this->_autoload = (bool) $flag;
382         }
383         
384         
385         /**
386         * 
387         * Sets a custom compiler/pre-processor for template sources.
388         * 
389         * By default, Savant2 does not use a compiler; use this to set your
390         * own custom compiler (pre-processor) for template sources.
391         * 
392         * @access public
393         * 
394         * @param object $compiler The compiler object; it must have a
395         * "compile()" method.  If null or false, the current compiler object
396         * is removed from Savant2.
397         * 
398         * @return void
399         * 
400         * @throws object An error object with a SAVANT2_ERROR_COMPILER code.
401         * 
402         */
403         
404         function setCompiler(&$compiler)
405         {
406                 if (! $compiler) {
407                         // nullify any current compiler
408                         $this->_compiler = null;
409                 } elseif (is_object($compiler) && method_exists($compiler, 'compile')) {
410                         // refer to a compiler object
411                         $this->_compiler =& $compiler;
412                 } else {
413                         // no usable compiler passed
414                         $this->_compiler = null;
415                         return $this->error(SAVANT2_ERROR_COMPILER);
416                 }
417         }
418         
419         
420         /**
421         * 
422         * Sets the method that __call() will alias to.
423         * 
424         * @access public
425         * 
426         * @param string $method The Savant2 method for __call() to alias to,
427         * generally 'plugin' or 'splugin'.
428         * 
429         * @return void
430         * 
431         */
432         
433         function setCall($method = 'plugin')
434         {
435                 $this->_call = $method;
436         }
437         
438         
439         /**
440         * 
441         * Sets the custom error class for Savant2 errors.
442         * 
443         * @access public
444         * 
445         * @param string $error The name of the custom error class name; if
446         * null or false, resets the error class to 'Savant2_Error'.
447         * 
448         * @return void
449         * 
450         */
451         
452         function setError($error)
453         {
454                 if (! $error) {
455                         $this->_error = null;
456                 } else {
457                         $this->_error = $error;
458                 }
459         }
460         
461         
462         /**
463         *
464         * Turns path checking on/off.
465         * 
466         * @access public
467         *
468         * @param bool $flag True to turn on path checks, false to turn off.
469         *
470         * @return void
471         *
472         */
473         
474         function setRestrict($flag = false)
475         {
476                 if ($flag) {
477                         $this->_restrict = true;
478                 } else {
479                         $this->_restrict = false;
480                 }
481         }
482         
483         
484         /**
485         *
486         * Turns extraction of variables on/off.
487         * 
488         * @access public
489         *
490         * @param bool $flag True to turn on extraction, false to turn off.
491         *
492         * @return void
493         *
494         */
495         
496         function setExtract($flag = true)
497         {
498                 if ($flag) {
499                         $this->_extract = true;
500                 } else {
501                         $this->_extract = false;
502                 }
503         }
504         
505         
506         /**
507         *
508         * Sets the automated Savant reference for plugins and filters.
509         *
510         * @access public
511         *
512         * @param bool $flag Whether to reference Savant2 or not.
513         *
514         * @return void
515         *
516         */
517         
518         function setReference($flag = false)
519         {
520                 $this->_reference = $flag;
521         }
522         
523         
524         /**
525         *
526         * Sets the default template name.
527         *
528         * @access public
529         *
530         * @param string $template The default template name.
531         *
532         * @return void
533         *
534         */
535         
536         function setTemplate($template)
537         {
538                 $this->_template = $template;
539         }
540         
541         
542         /**
543         * 
544         * Internal version of class_exists() to allow for differing behaviors.
545         * 
546         * Under PHP4, there is only 1 param to class_exists(); in PHP5, there 
547         * are two.  However, if you pass 2 params to the PHP4 version, you get
548         * a parameter count warning; hence, this method.
549         * 
550         * Under PHP5, checks $this->_autload to see if __autoload() should be
551         * called.
552         * 
553         * @access public
554         * 
555         * @param string $class A class name.
556         * 
557         * @return bool Whether or not the class exists.
558         * 
559         */
560         
561         function _classExists($class) {
562                 if (PHP_VERSION < '5') {
563                         // version 4.x
564                         return class_exists($class);
565                 } else {
566                         // version 5.x
567                         return class_exists($class, $this->_autoload);
568                 }
569         }
570         
571         
572         // -----------------------------------------------------------------
573         //
574         // Output escaping and management.
575         //
576         // -----------------------------------------------------------------
577         
578         
579         /**
580         * 
581         * Clears then sets the callbacks to use when calling $this->escape().
582         * 
583         * Each parameter passed to this function is treated as a separate
584         * callback.  For example:
585         * 
586         * <code>
587         * $savant->setEscape(
588         *        'stripslashes',
589         *        'htmlspecialchars',
590         *        array('StaticClass', 'method'),
591         *        array($object, $method)
592         * );
593         * </code>
594         * 
595         * @access public
596         *
597         * @return void
598         *
599         */
600         
601         function setEscape()
602         {
603                 $this->_escape = func_get_args();
604         }
605         
606         
607         /**
608         * 
609         * Adds to the callbacks used when calling $this->escape().
610         * 
611         * Each parameter passed to this function is treated as a separate
612         * callback.  For example:
613         * 
614         * <code>
615         * $savant->addEscape(
616         *        'stripslashes',
617         *        'htmlspecialchars',
618         *        array('StaticClass', 'method'),
619         *        array($object, $method)
620         * );
621         * </code>
622         * 
623         * @access public
624         *
625         * @return void
626         *
627         */
628         
629         function addEscape()
630         {
631                 $args = func_get_args();
632                 $this->_escape = array_merge($this->_escape, $args);
633         }
634         
635         
636         /**
637         *
638         * Gets the array of output-escaping callbacks.
639         *
640         * @access public
641         *
642         * @return array The array of output-escaping callbacks.
643         *
644         */
645         
646         function getEscape()
647         {
648                 return $this->_escape;
649         }
650         
651         
652         /**
653         *
654         * Applies escaping to a value.
655         * 
656         * You can override the predefined escaping callbacks by passing
657         * added parameters as replacement callbacks.
658         * 
659         * <code>
660         * // use predefined callbacks
661         * $result = $savant->escape($value);
662         * 
663         * // use replacement callbacks
664         * $result = $savant->escape(
665         *        $value,
666         *        'stripslashes',
667         *        'htmlspecialchars',
668         *        array('StaticClass', 'method'),
669         *        array($object, $method)
670         * );
671         * </code>
672         * 
673         * @access public
674         * 
675         * @param mixed $value The value to be escaped.
676         * 
677         * @return mixed
678         *
679         */
680         
681         function escape($value)
682         {
683                 // were custom callbacks passed?
684                 if (func_num_args() == 1) {
685                 
686                         // no, only a value was passed.
687                         // loop through the predefined callbacks.
688                         foreach ($this->_escape as $func) {
689                                 $value = call_user_func($func, $value);
690                         }
691                         
692                 } else {
693                 
694                         // yes, use the custom callbacks instead.
695                         $callbacks = func_get_args();
696                         
697                         // drop $value
698                         array_shift($callbacks);
699                         
700                         // loop through custom callbacks.
701                         foreach ($callbacks as $func) {
702                                 $value = call_user_func($func, $value);
703                         }
704                         
705                 }
706                 
707                 return $value;
708         }
709         
710         
711         /**
712         *
713         * Prints a value after escaping it for output.
714         * 
715         * You can override the predefined escaping callbacks by passing
716         * added parameters as replacement callbacks.
717         * 
718         * <code>
719         * // use predefined callbacks
720         * $this->_($value);
721         * 
722         * // use replacement callbacks
723         * $this->_(
724         *        $value,
725         *        'stripslashes',
726         *        'htmlspecialchars',
727         *        array('StaticClass', 'method'),
728         *        array($object, $method)
729         * );
730         * </code>
731         * 
732         * @access public
733         * 
734         * @param mixed $value The value to be escaped and printed.
735         * 
736         * @return void
737         *
738         */
739         
740         function eprint($value)
741         {
742                 $args = func_get_args();
743                 echo call_user_func_array(
744                         array($this, 'escape'),
745                         $args
746                 );
747         }
748         
749         
750         /**
751         *
752         * Alias to eprint() and identical in every way.
753         * 
754         * @access public
755         * 
756         * @param mixed $value The value to be escaped and printed.
757         * 
758         * @return void
759         *
760         */
761         
762         function _($value)
763         {
764                 $args = func_get_args();
765                 return call_user_func_array(
766                         array($this, 'eprint'),
767                         $args
768                 );
769         }
770         
771         
772         
773         // -----------------------------------------------------------------
774         //
775         // Path management and file finding
776         //
777         // -----------------------------------------------------------------
778         
779         
780         /**
781         *
782         * Sets an entire array of search paths.
783         *
784         * @access public
785         *
786         * @param string $type The type of path to set, typcially 'template'
787         * or 'resource'.
788         * 
789         * @param string|array $new The new set of search paths.  If null or
790         * false, resets to the current directory only.
791         *
792         * @return void
793         *
794         */
795         
796         function setPath($type, $new)
797         {
798                 // clear out the prior search dirs
799                 $this->_path[$type] = array();
800                 
801                 // convert from string to path
802                 if (is_string($new) && ! strpos($new, '://')) {
803                         // the search config is a string, and it's not a stream
804                         // identifier (the "://" piece), add it as a path
805                         // string.
806                         $new = explode(PATH_SEPARATOR, $new);
807                 } else {
808                         // force to array
809                         settype($new, 'array');
810                 }
811                 
812                 // always add the fallback directories as last resort
813                 switch (strtolower($type)) {
814                 case 'template':
815                         $this->addPath($type, '.');
816                         break;
817                 case 'resource':
818                         $this->addPath($type, dirname(__FILE__) . '/Savant2/');
819                         break;
820                 }
821                 
822                 // actually add the user-specified directories
823                 foreach ($new as $dir) {
824                         $this->addPath($type, $dir);
825                 }
826         }
827         
828         
829         /**
830         *
831         * Adds a search directory for templates.
832         *
833         * @access public
834         *
835         * @param string $dir The directory or stream to search.
836         *
837         * @return void
838         *
839         */
840         
841         function addPath($type, $dir)
842         {
843                 // no surrounding spaces allowed!
844                 $dir = trim($dir);
845                 
846                 // add trailing separators as needed
847                 if (strpos($dir, '://') && substr($dir, -1) != '/') {
848                         // stream
849                         $dir .= '/';
850                 } elseif (substr($dir, -1) != DIRECTORY_SEPARATOR) {
851                         // directory
852                         $dir .= DIRECTORY_SEPARATOR;
853                 }
854                 
855                 // add to the top of the search dirs
856                 array_unshift($this->_path[$type], $dir);
857         }
858         
859         
860         /**
861         *
862         * Gets the array of search directories for template sources.
863         *
864         * @access public
865         *
866         * @return array The array of search directories for template sources.
867         *
868         */
869         
870         function getPath($type = null)
871         {
872                 if (! $type) {
873                         return $this->_path;
874                 } else {
875                         return $this->_path[$type];
876                 }
877         }
878         
879         
880         /**
881         * 
882         * Searches a series of paths for a given file.
883         * 
884         * @param array $type The type of paths to search (template, plugin,
885         * or filter).
886         * 
887         * @param string $file The file name to look for.
888         * 
889         * @return string|bool The full path and file name for the target file,
890         * or boolean false if the file is not found in any of the paths.
891         *
892         */
893         
894         function findFile($type, $file)
895         {
896                 // get the set of paths
897                 $set = $this->getPath($type);
898                 
899                 // start looping through them
900                 foreach ($set as $path) {
901                         
902                         // get the path to the file
903                         $fullname = $path . $file;
904                         
905                         // are we doing path checks?
906                         if (! $this->_restrict) {
907                         
908                                 // no.  this is faster but less secure.
909                                 if (file_exists($fullname) && is_readable($fullname)) {
910                                         return $fullname;
911                                 }
912                                 
913                         } else {
914                                 
915                                 // yes.  this is slower, but attempts to restrict
916                                 // access only to defined paths.
917                                 
918                                 // is the path based on a stream?
919                                 if (strpos($path, '://') === false) {
920                                         // not a stream, so do a realpath() to avoid
921                                         // directory traversal attempts on the local file
922                                         // system. Suggested by Ian Eure, initially
923                                         // rejected, but then adopted when the secure
924                                         // compiler was added.
925                                         $path = realpath($path); // needed for substr() later
926                                         $fullname = realpath($fullname);
927                                 }
928                                 
929                                 // the substr() check added by Ian Eure to make sure
930                                 // that the realpath() results in a directory registered
931                                 // with Savant so that non-registered directores are not
932                                 // accessible via directory traversal attempts.
933                                 if (file_exists($fullname) && is_readable($fullname) &&
934                                         substr($fullname, 0, strlen($path)) == $path) {
935                                         return $fullname;
936                                 }
937                         }
938                 }
939                 
940                 // could not find the file in the set of paths
941                 return false;
942         }
943         
944         
945         // -----------------------------------------------------------------
946         //
947         // Variable and reference assignment
948         //
949         // -----------------------------------------------------------------
950         
951         
952         /**
953         * 
954         * Sets variables for the template.
955         * 
956         * This method is overloaded; you can assign all the properties of
957         * an object, an associative array, or a single value by name.
958         * 
959         * You are not allowed to set variables that begin with an underscore;
960         * these are either private properties for Savant2 or private variables
961         * within the template script itself.
962         * 
963         * <code>
964         * 
965         * $Savant2 = new Savant2();
966         * 
967         * // assign directly
968         * $Savant2->var1 = 'something';
969         * $Savant2->var2 = 'else';
970         * 
971         * // assign by name and value
972         * $Savant2->assign('var1', 'something');
973         * $Savant2->assign('var2', 'else');
974         * 
975         * // assign by assoc-array
976         * $ary = array('var1' => 'something', 'var2' => 'else');
977         * $Savant2->assign($obj);
978         * 
979         * // assign by object
980         * $obj = new stdClass;
981         * $obj->var1 = 'something';
982         * $obj->var2 = 'else';
983         * $Savant2->assign($obj);
984         * 
985         * </code>
986         * 
987         * Greg Beaver came up with the idea of assigning to public class
988         * properties.
989         * 
990         * @access public
991         * 
992         * @return void
993         * 
994         * @throws object An error object with a SAVANT2_ERROR_ASSIGN code.
995         * 
996         */
997         
998         function assign()
999         {
1000                 // this method is overloaded.
1001                 $arg = func_get_args();
1002                 
1003                 // must have at least one argument. no error, just do nothing.
1004                 if (! isset($arg[0])) {
1005                         return;
1006                 }
1007                 
1008                 // assign by object
1009                 if (is_object($arg[0])) {
1010                         // assign public properties
1011                         foreach (get_object_vars($arg[0]) as $key => $val) {
1012                                 if (substr($key, 0, 1) != '_') {
1013                                         $this->$key = $val;
1014                                 }
1015                         }
1016                         return;
1017                 }
1018                 
1019                 // assign by associative array
1020                 if (is_array($arg[0])) {
1021                         foreach ($arg[0] as $key => $val) {
1022                                 if (substr($key, 0, 1) != '_') {
1023                                         $this->$key = $val;
1024                                 }
1025                         }
1026                         return;
1027                 }
1028                 
1029                 // assign by string name and mixed value.
1030                 // 
1031                 // we use array_key_exists() instead of isset() becuase isset()
1032                 // fails if the value is set to null.
1033                 if (is_string($arg[0]) &&
1034                         substr($arg[0], 0, 1) != '_' &&
1035                         array_key_exists(1, $arg)) {
1036                         $this->$arg[0] = $arg[1];
1037                 } else {
1038                         return $this->error(SAVANT2_ERROR_ASSIGN, $arg);
1039                 }
1040         }
1041         
1042         
1043         /**
1044         * 
1045         * Sets references for the template.
1046         * 
1047         * // assign by name and value
1048         * $Savant2->assignRef('ref', $reference);
1049         * 
1050         * // assign directly
1051         * $Savant2->ref =& $reference;
1052         * 
1053         * Greg Beaver came up with the idea of assigning to public class
1054         * properties.
1055         * 
1056         * @access public
1057         * 
1058         * @param string $key The name for the reference in the template.
1059         *
1060         * @param mixed &$val The referenced variable.
1061         * 
1062         * @return void
1063         * 
1064         * @throws object An error object with a SAVANT2_ERROR_ASSIGNREF code.
1065         * 
1066         */
1067         
1068         function assignRef($key, &$val)
1069         {
1070                 if (is_string($key) && substr($key, 0, 1) != '_') {
1071                         $this->$key =& $val;
1072                 } else {
1073                         return $this->error(
1074                                 SAVANT2_ERROR_ASSIGNREF,
1075                                 array('key' => $key, 'val' => $val)
1076                         );
1077                 }
1078         }
1079         
1080         
1081         /**
1082         *
1083         * Unsets assigned variables and references.
1084         * 
1085         * @access public
1086         * 
1087         * @param mixed $var If null, clears all variables; if a string, clears
1088         * the one variable named by the string; if a sequential array, clears
1089         * the variables names in that array.
1090         * 
1091         * @return void
1092         *
1093         */
1094         
1095         function clear($var = null)
1096         {
1097                 if (is_null($var)) {
1098                         // clear all variables
1099                         $var = array_keys(get_object_vars($this));
1100                 } else {
1101                         // clear specific variables
1102                         settype($var, 'array');
1103                 }
1104                 
1105                 // clear out the selected variables
1106                 foreach ($var as $name) {
1107                         if (substr($name, 0, 1) != '_' && isset($this->$name)) {
1108                                 unset($this->$name);
1109                         }
1110                 }
1111         }
1112         
1113         
1114         /**
1115         * 
1116         * Gets the current value of one, many, or all assigned variables.
1117         * 
1118         * Never returns variables starting with an underscore; these are
1119         * reserved for internal Savant2 use.
1120         * 
1121         * @access public
1122         * 
1123         * @param mixed $key If null, returns a copy of all variables and
1124         * their values; if an array, returns an only those variables named
1125         * in the array; if a string, returns only that variable.
1126         * 
1127         * @return mixed If multiple variables were reqested, returns an
1128         * associative array where the key is the variable name and the 
1129         * value is the variable value; if one variable was requested,
1130         * returns the variable value only.
1131         * 
1132         */
1133         
1134         function getVars($key = null)
1135         {
1136                 if (is_null($key)) {
1137                         $key = array_keys(get_object_vars($this));
1138                 }
1139                 
1140                 if (is_array($key)) {
1141                         // return a series of vars
1142                         $tmp = array();
1143                         foreach ($key as $var) {
1144                                 if (substr($var, 0, 1) != '_' && isset($this->$var)) {
1145                                         $tmp[$var] = $this->$var;
1146                                 }
1147                         }
1148                         return $tmp;
1149                 } else {
1150                         // return a single var
1151                         if (substr($key, 0, 1) != '_' && isset($this->$key)) {
1152                                 return $this->$key;
1153                         }
1154                 }
1155         }
1156         
1157         
1158         // -----------------------------------------------------------------
1159         //
1160         // Template processing
1161         //
1162         // -----------------------------------------------------------------
1163         
1164         
1165         /**
1166         *
1167         * Loads a template script for execution (does not execute the script).
1168         * 
1169         * This will optionally compile the template source into a PHP script
1170         * if a compiler object has been passed into Savant2.
1171         * 
1172         * Also good for including templates from the template paths within
1173         * another template, like so:
1174         *
1175         * include $this->loadTemplate('template.tpl.php');
1176         * 
1177         * @access public
1178         *
1179         * @param string $tpl The template source name to look for.
1180         * 
1181         * @param bool $setScript Default false; if true, sets the $this->_script
1182         * property to the resulting script path (or null on error).  Normally,
1183         * only $this->fetch() will need to set this to true.
1184         * 
1185         * @return string The full path to the compiled template script.
1186         * 
1187         * @throws object An error object with a SAVANT2_ERROR_NOTEMPLATE code.
1188         * 
1189         */
1190         
1191         function loadTemplate($tpl = null, $setScript = false)
1192         {
1193                 // set to default template if none specified.
1194                 if (is_null($tpl)) {
1195                         $tpl = $this->_template;
1196                 }
1197                 
1198                 // find the template source.
1199                 $file = $this->findFile('template', $tpl);
1200                 if (! $file) {
1201                         return $this->error(
1202                                 SAVANT2_ERROR_NOTEMPLATE,
1203                                 array('template' => $tpl)
1204                         );
1205                 }
1206                 
1207                 // are we compiling source into a script?
1208                 if (is_object($this->_compiler)) {
1209                         // compile the template source and get the path to the
1210                         // compiled script (will be returned instead of the
1211                         // source path)
1212                         $result = $this->_compiler->compile($file);
1213                 } else {
1214                         // no compiling requested, return the source path
1215                         $result = $file;
1216                 }
1217                 
1218                 // is there a script from the compiler?
1219                 if (! $result || $this->isError($result)) {
1220                 
1221                         if ($setScript) {
1222                                 $this->_script = null;
1223                         }
1224                         
1225                         // return an error, along with any error info
1226                         // generated by the compiler.
1227                         return $this->error(
1228                                 SAVANT2_ERROR_NOSCRIPT,
1229                                 array(
1230                                         'template' => $tpl,
1231                                         'compiler' => $result
1232                                 )
1233                         );
1234                         
1235                 } else {
1236                 
1237                         if ($setScript) {
1238                                 $this->_script = $result;
1239                         }
1240                         
1241                         return $result;
1242                         
1243                 }
1244         }
1245         
1246         
1247         /**
1248         * 
1249         * This is a an alias to loadTemplate() that cannot set the script.
1250         * 
1251         * @access public
1252         *
1253         * @param string $tpl The template source name to look for.
1254         * 
1255         * @return string The full path to the compiled template script.
1256         * 
1257         * @throws object An error object with a SAVANT2_ERROR_NOTEMPLATE code.
1258         * 
1259         */
1260         
1261         function findTemplate($tpl = null)
1262         {
1263                 return $this->loadTemplate($tpl, false);
1264         }
1265         
1266         
1267         /**
1268         * 
1269         * Executes a template script and returns the results as a string.
1270         * 
1271         * @param string $_tpl The name of the template source file ...
1272         * automatically searches the template paths and compiles as needed.
1273         * 
1274         * @return string The output of the the template script.
1275         * 
1276         * @throws object An error object with a SAVANT2_ERROR_NOSCRIPT code.
1277         * 
1278         */
1279         
1280         function fetch($_tpl = null)
1281         {
1282                 // clear prior output
1283                 $this->_output = null;
1284                 
1285                 // load the template script
1286                 $_result = $this->loadTemplate($_tpl, true);
1287                 
1288                 // is there a template script to be processed?
1289                 if ($this->isError($_result)) {
1290                         return $_result;
1291                 }
1292                 
1293                 // unset so as not to introduce into template scope
1294                 unset($_tpl);
1295                 unset($_result);
1296                 
1297                 // never allow a 'this' property
1298                 if (isset($this->this)) {
1299                         unset($this->this);
1300                 }
1301                 
1302                 // are we extracting variables into local scope?
1303                 if ($this->_extract) {
1304                         // extract references to this object's public properties.
1305                         // this allows variables assigned by-reference to refer all
1306                         // the way back to the model logic.  variables assigned
1307                         // by-copy only refer back to the property.
1308                         foreach (array_keys(get_object_vars($this)) as $_prop) {
1309                                 if (substr($_prop, 0, 1) != '_') {
1310                                         // set a variable-variable to an object property
1311                                         // reference
1312                                         $$_prop =& $this->$_prop;
1313                                 }
1314                         }
1315                         
1316                         // unset private loop vars
1317                         unset($_prop);
1318                 }
1319                 
1320                 // start capturing output into a buffer
1321                 ob_start();
1322                 
1323                 // include the requested template filename in the local scope
1324                 // (this will execute the view logic).
1325                 include $this->_script;
1326                 
1327                 // done with the requested template; get the buffer and 
1328                 // clear it.
1329                 $this->_output = ob_get_contents();
1330                 ob_end_clean();
1331                 
1332                 // done!
1333                 return $this->applyFilters();
1334         }
1335         
1336         
1337         /**
1338         * 
1339         * Execute and display a template script.
1340         * 
1341         * @param string $tpl The name of the template file to parse;
1342         * automatically searches through the template paths.
1343         * 
1344         * @return void
1345         * 
1346         * @throws object An error object with a SAVANT2_ERROR_NOSCRIPT code.
1347         * 
1348         * @see fetch()
1349         * 
1350         */
1351         
1352         function display($tpl = null)
1353         {
1354                 $result = $this->fetch($tpl);
1355                 if ($this->isError($result)) {
1356                         return $result;
1357                 } else {
1358                         echo $result;
1359                 }
1360         }
1361         
1362         
1363         // -----------------------------------------------------------------
1364         //
1365         // Plugins
1366         //
1367         // -----------------------------------------------------------------
1368         
1369         
1370         /**
1371         *
1372         * Loads a plugin class and instantiates it within Savant2.
1373         *
1374         * @access public
1375         *
1376         * @param string $name The plugin name (not including Savant2_Plugin_
1377         * prefix).
1378         *
1379         * @param array $conf An associative array of plugin configuration
1380         * options.
1381         *
1382         * @param bool $savantRef Default false.  When true, sets the $Savant
1383         * property of the filter to a reference to this Savant object.
1384         *
1385         * @return void
1386         * 
1387         * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
1388         * 
1389         */
1390         
1391         function loadPlugin($name, $conf = array(), $savantRef = null)
1392         {
1393                 // if no $savantRef is provided, use the default.
1394                 if (is_null($savantRef)) {
1395                         $savantRef = $this->_reference;
1396                 }
1397                 
1398                 // some basic information
1399                 $class = "Savant2_Plugin_$name";
1400                 $file = "$class.php";
1401                 
1402                 // is it loaded?
1403                 if (! $this->_classExists($class)) {
1404                         
1405                         $result = $this->findFile('resource', $file);
1406                         if (! $result) {
1407                                 return $this->error(
1408                                         SAVANT2_ERROR_NOPLUGIN,
1409                                         array('plugin' => $name)
1410                                 );
1411                         } else {
1412                                 include_once $result;
1413                         }
1414                 }
1415                 
1416                 // is it instantiated?
1417                 if (! isset($this->_resource['plugin'][$name]) ||
1418                         ! is_object($this->_resource['plugin'][$name]) ||
1419                         ! is_a($this->_resource['plugin'][$name], $class)) {
1420                         
1421                         // instantiate it
1422                         $this->_resource['plugin'][$name] = new $class($conf);
1423                         
1424                         // add a Savant reference if requested
1425                         if ($savantRef) {
1426                                 $this->_resource['plugin'][$name]->Savant = $this;
1427                         }
1428                         
1429                 }
1430         }
1431         
1432         
1433         /**
1434         *
1435         * Unloads one or more plugins from Savant2.
1436         *
1437         * @access public
1438         *
1439         * @param string|array $name The plugin name (not including Savant2_Plugin_
1440         * prefix).  If null, unloads all plugins; if a string, unloads that one
1441         * plugin; if an array, unloads all plugins named as values in the array.
1442         *
1443         * @return void
1444         * 
1445         */
1446         
1447         function unloadPlugin($name = null)
1448         {
1449                 if (is_null($name)) {
1450                         $this->_resource['plugin'] = array();
1451                 } else {
1452                         settype($name, 'array');
1453                         foreach ($name as $key) {
1454                                 if (isset($this->_resource['plugin'][$key])) {
1455                                         unset($this->_resource['plugin'][$key]);
1456                                 }
1457                         }
1458                 }
1459         }
1460         
1461         
1462         /**
1463         *
1464         * Executes a plugin with arbitrary parameters and returns the
1465         * result.
1466         * 
1467         * @access public
1468         * 
1469         * @param string $name The plugin name (not including Savant2_Plugin_
1470         * prefix).
1471         *
1472         * @return mixed The plugin results.
1473         *
1474         * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
1475         * 
1476         * @see loadPlugin()
1477         * 
1478         */
1479         
1480         function splugin($name)
1481         {
1482                 // attempt to load the plugin
1483                 $result = $this->loadPlugin($name);
1484                 if ($this->isError($result)) {
1485                         return $result;
1486                 }
1487                 
1488                 // call the plugin's "plugin()" method with arguments,
1489                 // dropping the first argument (the plugin name)
1490                 $args = func_get_args();
1491                 array_shift($args);
1492                 return call_user_func_array(
1493                         array(&$this->_resource['plugin'][$name], 'plugin'), $args
1494                 );
1495         }
1496         
1497         
1498         /**
1499         *
1500         * Executes a plugin with arbitrary parameters and displays the
1501         * result.
1502         * 
1503         * @access public
1504         * 
1505         * @param string $name The plugin name (not including Savant2_Plugin_
1506         * prefix).
1507         *
1508         * @return void
1509         * 
1510         * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
1511         * 
1512         */
1513         
1514         function plugin($name)
1515         {
1516                 $args = func_get_args();
1517                 
1518                 $result = call_user_func_array(
1519                         array(&$this, 'splugin'),
1520                         $args
1521                 );
1522                 
1523                 if ($this->isError($result)) {
1524                         return $result;
1525                 } else {
1526                         echo $result;
1527                 }
1528         }
1529         
1530         
1531         /**
1532         *
1533         * PHP5 ONLY: Magic method alias to plugin().
1534         * 
1535         * E.g., instead of $this->plugin('form', ...) you would use
1536         * $this->form(...).  You can set this to use any other Savant2 method
1537         * by issuing, for example, setCall('splugin') to use splugin() ... which 
1538         * is really the only other sensible choice.
1539         * 
1540         * @access public
1541         * 
1542         * @param string $func The plugin name.
1543         *
1544         * @param array $args Arguments passed to the plugin.
1545         *
1546         * @return void
1547         * 
1548         * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
1549         * 
1550         */
1551         
1552         function __call($func, $args)
1553         {
1554                 // add the plugin name to the args
1555                 array_unshift($args, $func);
1556                 
1557                 // call the plugin() method
1558                 return call_user_func_array(
1559                         array(&$this, $this->_call),
1560                         $args
1561                 );
1562         }
1563         
1564         
1565         // -----------------------------------------------------------------
1566         //
1567         // Filters
1568         //
1569         // -----------------------------------------------------------------
1570         
1571         
1572         /**
1573         *
1574         * Loads a filter class and instantiates it within Savant2.
1575         *
1576         * @access public
1577         *
1578         * @param string $name The filter name (not including Savant2_Filter_
1579         * prefix).
1580         *
1581         * @param array $conf An associative array of filter configuration
1582         * options.
1583         * 
1584         * @param bool $savantRef Default false.  When true, sets the $Savant
1585         * property of the filter to a reference to this Savant object.
1586         *
1587         * @return void
1588         * 
1589         * @throws object An error object with a SAVANT2_ERROR_NOFILTER code.
1590         * 
1591         */
1592         
1593         function loadFilter($name, $conf = array(), $savantRef = null)
1594         {
1595                 // if no $savantRef is provided, use the default.
1596                 if (is_null($savantRef)) {
1597                         $savantRef = $this->_reference;
1598                 }
1599                 
1600                 // some basic information
1601                 $class = "Savant2_Filter_$name";
1602                 $file = "$class.php";
1603                 
1604                 // is it loaded?
1605                 if (! $this->_classExists($class)) {
1606                         
1607                         $result = $this->findFile('resource', $file);
1608                         if (! $result) {
1609                                 return $this->error(
1610                                         SAVANT2_ERROR_NOFILTER,
1611                                         array('filter' => $name)
1612                                 );
1613                         } else {
1614                                 include_once $result;
1615                         }
1616                 }
1617                 
1618                 // is it instantiated?
1619                 if (! isset($this->_resource['filter'][$name]) ||
1620                         ! is_object($this->_resource['filter'][$name]) ||
1621                         ! is_a($this->_resource['filter'][$name], $class)) {
1622                         
1623                         // instantiate it
1624                         $this->_resource['filter'][$name] = new $class($conf);
1625                         
1626                         // add a Savant reference if requested
1627                         if ($savantRef) {
1628                                 $this->_resource['filter'][$name]->Savant = $this;
1629                         }
1630                         
1631                 }
1632         }
1633         
1634         
1635         /**
1636         *
1637         * Unloads one or more filters from Savant2.
1638         *
1639         * @access public
1640         *
1641         * @param string|array $name The filter name (not including Savant2_Filter_
1642         * prefix).  If null, unloads all filters; if a string, unloads that one
1643         * filter; if an array, unloads all filters named as values in the array.
1644         *
1645         * @return void
1646         * 
1647         */
1648         
1649         function unloadFilter($name = null)
1650         {
1651                 if (is_null($name)) {
1652                         $this->_resource['filter'] = array();
1653                 } else {
1654                         settype($name, 'array');
1655                         foreach ($name as $key) {
1656                                 if (isset($this->_resource['filter'][$key])) {
1657                                         unset($this->_resource['filter'][$key]);
1658                                 }
1659                         }
1660                 }
1661         }
1662         
1663         
1664         /**
1665         *
1666         * Apply all loaded filters, in order, to text.
1667         *
1668         * @access public
1669         *
1670         * @param string $text The text to which filters should be applied. 
1671         * If null, sets the text to $this->_output.
1672         * 
1673         * @return string The text after being passed through all loded
1674         * filters.
1675         * 
1676         */
1677         
1678         function applyFilters($text = null)
1679         {
1680                 // set to output text if no text specified
1681                 if (is_null($text)) {
1682                         $text = $this->_output;
1683                 }
1684                 
1685                 // get the list of filter names...
1686                 $filter = array_keys($this->_resource['filter']);
1687                 
1688                 // ... and apply them each in turn.
1689                 foreach ($filter as $name) {
1690                         $this->_resource['filter'][$name]->filter($text);
1691                 }
1692                 
1693                 // done
1694                 return $text;
1695         }
1696         
1697         
1698         // -----------------------------------------------------------------
1699         //
1700         // Error handling
1701         //
1702         // -----------------------------------------------------------------
1703         
1704         
1705         /**
1706         *
1707         * Returns an error object.
1708         * 
1709         * @access public
1710         * 
1711         * @param int $code A SAVANT2_ERROR_* constant.
1712         * 
1713         * @param array $info An array of error-specific information.
1714         * 
1715         * @return object An error object of the type specified by
1716         * $this->_error.
1717         * 
1718         */
1719         
1720         function &error($code, $info = array())
1721         {
1722                 // the error config array
1723                 $conf = array(
1724                         'code' => $code,
1725                         'text' => 'Savant2: ',
1726                         'info' => (array) $info
1727                 );
1728                 
1729                 // set an error message from the globals
1730                 if (isset($GLOBALS['_SAVANT2']['error'][$code])) {
1731                         $conf['text'] .= $GLOBALS['_SAVANT2']['error'][$code];
1732                 } else {
1733                         $conf['text'] .= '???';
1734                 }
1735                 
1736                 // set up the error class name
1737                 if ($this->_error) {
1738                         $class = 'Savant2_Error_' . $this->_error;
1739                 } else {
1740                         $class = 'Savant2_Error';
1741                 }
1742
1743                 // set up the error class file name
1744                 $file = $class . '.php';
1745                 
1746                 // is it loaded?
1747                 if (! $this->_classExists($class)) {
1748                         
1749                         // find the error class
1750                         $result = $this->findFile('resource', $file);
1751                         if (! $result) {
1752                                 // could not find the custom error class, revert to
1753                                 // Savant_Error base class.
1754                                 $class = 'Savant2_Error';
1755                                 $result = dirname(__FILE__) . '/Savant2/Error.php';
1756                         }
1757                         
1758                         // include the error class
1759                         include_once $result;
1760                 }
1761                 
1762                 // instantiate and return the error class
1763                 $err = new $class($conf);
1764                 return $err;
1765         }
1766         
1767         
1768         /**
1769         *
1770         * Tests if an object is of the Savant2_Error class.
1771         * 
1772         * @access public
1773         * 
1774         * @param object &$obj The object to be tested.
1775         * 
1776         * @return boolean True if $obj is an error object of the type
1777         * Savant2_Error, or is a subclass that Savant2_Error. False if not.
1778         *
1779         */
1780         
1781         function isError(&$obj)
1782         {
1783                 if (is_object($obj)) {
1784                         if (is_a($obj, 'Savant2_Error') ||
1785                                 is_subclass_of($obj, 'Savant2_Error')) {
1786                                 return true;
1787                         }
1788                 }
1789                 
1790                 return false;
1791         }
1792 }
1793 ?>