41d5c9bb3029af5e2b7b4a70833f7f29d3e36256
[atutor.git] / mods / photo_album / HTML / Template / ITX.php
1 <?php
2 //
3 // +----------------------------------------------------------------------+
4 // | Copyright (c) 1997-2005 Ulf Wendel, Pierre-Alain Joye                |
5 // +----------------------------------------------------------------------+
6 // | This source file is subject to the New BSD license, That is bundled  |
7 // | with this package in the file LICENSE, and is available through      |
8 // | the world-wide-web at                                                |
9 // | http://www.opensource.org/licenses/bsd-license.php                   |
10 // | If you did not receive a copy of the new BSD license and are unable  |
11 // | to obtain it through the world-wide-web, please send a note to       |
12 // | pajoye@php.net so we can mail you a copy immediately.                |
13 // +----------------------------------------------------------------------+
14 // | Author: Ulf Wendel <ulf.wendel@phpdoc.de>                            |
15 // |         Pierre-Alain Joye <pajoye@php.net>                           |
16 // +----------------------------------------------------------------------+
17 //
18 // $Id: ITX.php,v 1.14 2005/11/01 10:14:19 pajoye Exp $
19 //
20
21 require_once 'IT.php';
22 require_once 'IT_Error.php';
23
24 /**
25 * Integrated Template Extension - ITX
26 *
27 * With this class you get the full power of the phplib template class.
28 * You may have one file with blocks in it but you have as well one main file
29 * and multiple files one for each block. This is quite usefull when you have
30 * user configurable websites. Using blocks not in the main template allows
31 * you to modify some parts of your layout easily.
32 *
33 * Note that you can replace an existing block and add new blocks at runtime.
34 * Adding new blocks means changing a variable placeholder to a block.
35 *
36 * @author   Ulf Wendel <uw@netuse.de>
37 * @access   public
38 * @version  $Id: ITX.php,v 1.14 2005/11/01 10:14:19 pajoye Exp $
39 * @package  IT[X]
40 */
41 class HTML_Template_ITX extends HTML_Template_IT
42 {
43     /**
44     * Array with all warnings.
45     * @var    array
46     * @access public
47     * @see    $printWarning, $haltOnWarning, warning()
48     */
49     var $warn = array();
50
51     /**
52     * Print warnings?
53     * @var    array
54     * @access public
55     * @see    $haltOnWarning, $warn, warning()
56     */
57     var $printWarning = false;
58
59     /**
60     * Call die() on warning?
61     * @var    boolean
62     * @access public
63     * @see    $warn, $printWarning, warning()
64     */
65     var $haltOnWarning = false;
66
67     /**
68     * RegExp used to test for a valid blockname.
69     * @var string
70     */
71     var $checkblocknameRegExp = '';
72
73     /**
74     * Functionnameprefix used when searching function calls in the template.
75     * @var string
76     */
77     var $functionPrefix = 'func_';
78
79     /**
80     * Functionname RegExp.
81     * @var string
82     */
83     var $functionnameRegExp = '[_a-zA-Z]+[A-Za-z_0-9]*';
84
85     /**
86     * RegExp used to grep function calls in the template.
87     *
88     * The variable gets set by the constructor.
89     *
90     * @var string
91     * @see HTML_Template_IT()
92     */
93     var $functionRegExp = '';
94
95     /**
96     * List of functions found in the template.
97     *
98     * @var array
99     */
100     var $functions = array();
101
102     /**
103     * List of callback functions specified by the user.
104     *
105     * @var array
106     */
107     var $callback = array();
108
109     /**
110     * Builds some complex regexps and calls the constructor
111     * of the parent class.
112     *
113     * Make sure that you call this constructor if you derive your own
114     * template class from this one.
115     *
116     * @see    HTML_Template_IT()
117     */
118     function HTML_Template_ITX($root = '')
119     {
120
121         $this->checkblocknameRegExp = '@' . $this->blocknameRegExp . '@';
122         $this->functionRegExp = '@' . $this->functionPrefix . '(' .
123                                 $this->functionnameRegExp . ')\s*\(@sm';
124
125         $this->HTML_Template_IT($root);
126     } // end func constructor
127
128     function init()
129     {
130         $this->free();
131         $this->buildFunctionlist();
132         $this->findBlocks($this->template);
133         // we don't need it any more
134         $this->template = '';
135         $this->buildBlockvariablelist();
136
137     } // end func init
138
139     /**
140     * Replaces an existing block with new content.
141     *
142     * This function will replace a block of the template and all blocks
143     * contained in the replaced block and add a new block insted, means
144     * you can dynamically change your template.
145     *
146     * Note that changing the template structure violates one of the IT[X]
147     * development goals. I've tried to write a simple to use template engine
148     * supporting blocks. In contrast to other systems IT[X] analyses the way
149     * you've nested blocks and knows which block belongs into another block.
150     * The nesting information helps to make the API short and simple. Replacing
151     * blocks does not only mean that IT[X] has to update the nesting
152     * information (relatively time consumpting task) but you have to make sure
153     * that you do not get confused due to the template change itself.
154     *
155     * @param    string      Blockname
156     * @param    string      Blockcontent
157     * @param    boolean     true if the new block inherits the content
158     *                       of the old block
159     * @return   boolean
160     * @throws   IT_Error
161     * @see      replaceBlockfile(), addBlock(), addBlockfile()
162     * @access   public
163     */
164     function replaceBlock($block, $template, $keep_content = false)
165     {
166         if (!isset($this->blocklist[$block])) {
167             return new IT_Error(
168             "The block "."'$block'".
169             " does not exist in the template and thus it can't be replaced.",
170             __FILE__, __LINE__
171             );
172         }
173
174         if ($template == '') {
175             return new IT_Error('No block content given.', __FILE__, __LINE__);
176         }
177
178         if ($keep_content) {
179             $blockdata = $this->blockdata[$block];
180         }
181
182         // remove all kinds of links to the block / data of the block
183         $this->removeBlockData($block);
184
185         $template = "<!-- BEGIN $block -->" . $template . "<!-- END $block -->";
186         $parents = $this->blockparents[$block];
187         $this->findBlocks($template);
188         $this->blockparents[$block] = $parents;
189
190         // KLUDGE: rebuild the list for all block - could be done faster
191         $this->buildBlockvariablelist();
192
193         if ($keep_content) {
194             $this->blockdata[$block] = $blockdata;
195         }
196
197         // old TODO - I'm not sure if we need this
198         // update caches
199
200         return true;
201     } // end func replaceBlock
202
203     /**
204     * Replaces an existing block with new content from a file.
205     *
206     * @brother replaceBlock()
207     * @param    string    Blockname
208     * @param    string    Name of the file that contains the blockcontent
209     * @param    boolean   true if the new block inherits the content of the old block
210     */
211     function replaceBlockfile($block, $filename, $keep_content = false)
212     {
213         return $this->replaceBlock($block, $this->getFile($filename), $keep_content);
214     } // end func replaceBlockfile
215
216     /**
217     * Adds a block to the template changing a variable placeholder
218     * to a block placeholder.
219     *
220     * Add means "replace a variable placeholder by a new block".
221     * This is different to PHPLibs templates. The function loads a
222     * block, creates a handle for it and assigns it to a certain
223     * variable placeholder. To to the same with PHPLibs templates you would
224     * call set_file() to create the handle and parse() to assign the
225     * parsed block to a variable. By this PHPLibs templates assume
226     * that you tend to assign a block to more than one one placeholder.
227     * To assign a parsed block to more than only the placeholder you specify
228     * in this function you have to use a combination of getBlock()
229     * and setVariable().
230     *
231     * As no updates to cached data is necessary addBlock() and addBlockfile()
232     * are rather "cheap" meaning quick operations.
233     *
234     * The block content must not start with <!-- BEGIN blockname -->
235     * and end with <!-- END blockname --> this would cause overhead and
236     * produce an error.
237     *
238     * @param    string    Name of the variable placeholder, the name must be unique
239     *                     within the template.
240     * @param    string    Name of the block to be added
241     * @param    string    Content of the block
242     * @return   boolean
243     * @throws   IT_Error
244     * @see      addBlockfile()
245     * @access   public
246     */
247     function addBlock($placeholder, $blockname, $template)
248     {
249         // Don't trust any user even if it's a programmer or yourself...
250         if ($placeholder == '') {
251             return new IT_Error('No variable placeholder given.',
252                                 __FILE__, __LINE__
253                                 );
254         } elseif ($blockname == '' ||
255                     !preg_match($this->checkblocknameRegExp, $blockname)
256         ) {
257             return new IT_Error("No or invalid blockname '$blockname' given.",
258                     __FILE__, __LINE__
259                     );
260         } elseif ($template == '') {
261             return new IT_Error('No block content given.', __FILE__, __LINE__);
262         } elseif (isset($this->blocklist[$blockname])) {
263             return new IT_Error('The block already exists.',
264                                 __FILE__, __LINE__
265                             );
266         }
267
268         // find out where to insert the new block
269         $parents = $this->findPlaceholderBlocks($placeholder);
270         if (count($parents) == 0) {
271
272             return new IT_Error(
273                 "The variable placeholder".
274                 " '$placeholder' was not found in the template.",
275                 __FILE__, __LINE__
276             );
277
278         } elseif (count($parents) > 1) {
279
280             reset($parents);
281             while (list($k, $parent) = each($parents)) {
282                 $msg .= "$parent, ";
283             }
284             $msg = substr($parent, -2);
285
286             return new IT_Error("The variable placeholder "."'$placeholder'".
287                                 " must be unique, found in multiple blocks '$msg'.",
288                                 __FILE__, __LINE__
289                                 );
290         }
291
292         $template = "<!-- BEGIN $blockname -->" . $template . "<!-- END $blockname -->";
293         $this->findBlocks($template);
294         if ($this->flagBlocktrouble) {
295             return false;    // findBlocks() already throws an exception
296         }
297         $this->blockinner[$parents[0]][] = $blockname;
298         $this->blocklist[$parents[0]] = preg_replace(
299                     '@' . $this->openingDelimiter . $placeholder .
300                     $this->closingDelimiter . '@',
301
302                     $this->openingDelimiter . '__' . $blockname . '__' .
303                     $this->closingDelimiter,
304
305                     $this->blocklist[$parents[0]]
306                 );
307
308         $this->deleteFromBlockvariablelist($parents[0], $placeholder);
309         $this->updateBlockvariablelist($blockname);
310     /*
311     // check if any inner blocks were found
312     if(is_array($this->blockinner[$blockname]) and count($this->blockinner[$blockname]) > 0) {
313         // loop through inner blocks, registering the variable placeholders in each
314         foreach($this->blockinner[$blockname] as $childBlock) {
315             $this->updateBlockvariablelist($childBlock);
316         }
317     }
318     */
319         return true;
320     } // end func addBlock
321
322     /**
323     * Adds a block taken from a file to the template changing a variable
324     * placeholder to a block placeholder.
325     *
326     * @param      string    Name of the variable placeholder to be converted
327     * @param      string    Name of the block to be added
328     * @param      string    File that contains the block
329     * @brother    addBlock()
330     */
331     function addBlockfile($placeholder, $blockname, $filename)
332     {
333         return $this->addBlock($placeholder, $blockname, $this->getFile($filename));
334     } // end func addBlockfile
335
336     /**
337     * Returns the name of the (first) block that contains
338     * the specified placeholder.
339     *
340     * @param    string  Name of the placeholder you're searching
341     * @param    string  Name of the block to scan. If left out (default)
342     *                   all blocks are scanned.
343     * @return   string  Name of the (first) block that contains
344     *                   the specified placeholder.
345     *                   If the placeholder was not found or an error occured
346     *                   an empty string is returned.
347     * @throws   IT_Error
348     * @access   public
349     */
350     function placeholderExists($placeholder, $block = '')
351     {
352         if ($placeholder == '') {
353             new IT_Error('No placeholder name given.', __FILE__, __LINE__);
354             return '';
355         }
356
357         if ($block != '' && !isset($this->blocklist[$block])) {
358             new IT_Error("Unknown block '$block'.", __FILE__, __LINE__);
359             return '';
360         }
361
362         // name of the block where the given placeholder was found
363         $found = '';
364
365         if ($block != '') {
366             if (is_array($variables = $this->blockvariables[$block])) {
367                 // search the value in the list of blockvariables
368                 reset($variables);
369                 while (list($k, $variable) = each($variables)) {
370                     if ($k == $placeholder) {
371                         $found = $block;
372                         break;
373                     }
374                 }
375             }
376         } else {
377
378             // search all blocks and return the name of the first block that
379             // contains the placeholder
380             reset($this->blockvariables);
381             while (list($blockname, $variables) = each($this->blockvariables)){
382                 if (is_array($variables) && isset($variables[$placeholder])) {
383                     $found = $blockname;
384                     break;
385                 }
386             }
387         }
388
389         return $found;
390     } // end func placeholderExists
391
392     /**
393     * Checks the list of function calls in the template and
394     * calls their callback function.
395     *
396     * @access    public
397     */
398     function performCallback()
399     {
400         reset($this->functions);
401         while (list($func_id, $function) = each($this->functions)) {
402             if (isset($this->callback[$function['name']])) {
403                 if ($this->callback[$function['name']]['object'] != '') {
404                     $this->variableCache['__function' . $func_id . '__'] =
405                         call_user_func(
406                         array(
407                         &$GLOBALS[$this->callback[$function['name']]['object']],
408                         $this->callback[$function['name']]['function']),
409                         $function['args']
410                        );
411                 } else {
412                     $this->variableCache['__function' . $func_id . '__'] =
413                             call_user_func(
414                             $this->callback[$function['name']]['function'],
415                             $function['args']
416                         );
417                 }
418             }
419         }
420
421     } // end func performCallback
422
423     /**
424     * Returns a list of all function calls in the current template.
425     *
426     * @return   array
427     * @access   public
428     */
429     function getFunctioncalls()
430     {
431         return $this->functions;
432     } // end func getFunctioncalls
433
434     /**
435     * Replaces a function call with the given replacement.
436     *
437     * @param    int       Function ID
438     * @param    string    Replacement
439     * @deprec
440     */
441     function setFunctioncontent($functionID, $replacement)
442     {
443         $this->variableCache['__function' . $functionID . '__'] = $replacement;
444     } // end func setFunctioncontent
445
446     /**
447     * Sets a callback function.
448     *
449     * IT[X] templates (note the X) can contain simple function calls.
450     * "function call" means that the editor of the template can add
451     * special placeholder to the template like 'func_h1("embedded in h1")'.
452     * IT[X] will grab this function calls and allow you to define a callback
453     * function for them.
454     *
455     * This is an absolutely evil feature. If your application makes heavy
456     * use of such callbacks and you're even implementing if-then etc. on
457     * the level of a template engine you're reiventing the wheel... - that's
458     * actually how PHP came into life. Anyway, sometimes it's handy.
459     *
460     * Consider also using XML/XSLT or native PHP. And please do not push
461     * IT[X] any further into this direction of adding logics to the template
462     * engine.
463     *
464     * For those of you ready for the X in IT[X]:
465     *
466     * <?php
467     * ...
468     * function h_one($args) {
469     *    return sprintf('<h1>%s</h1>', $args[0]);
470     * }
471     *
472     * ...
473     * $itx = new HTML_Template_ITX( ... );
474     * ...
475     * $itx->setCallbackFunction('h1', 'h_one');
476     * $itx->performCallback();
477     * ?>
478     *
479     * template:
480     * func_h1('H1 Headline');
481     *
482     * @param    string    Function name in the template
483     * @param    string    Name of the callback function
484     * @param    string    Name of the callback object
485     * @return   boolean   False on failure.
486     * @throws   IT_Error
487     * @access   public
488     */
489     function
490     setCallbackFunction($tplfunction, $callbackfunction, $callbackobject = '')
491     {
492         if ($tplfunction == '' || $callbackfunction == '') {
493             return new IT_Error(
494                 "No template function "."('$tplfunction')".
495                 " and/or no callback function ('$callback') given.",
496                     __FILE__, __LINE__
497                 );
498         }
499         $this->callback[$tplfunction] = array(
500                                           'function' => $callbackfunction,
501                                           'object'   => $callbackobject
502                                         );
503
504         return true;
505     } // end func setCallbackFunction
506
507     /**
508     * Sets the Callback function lookup table
509     *
510     * @param    array    function table
511     *                    array[templatefunction] =
512     *                       array(
513     *                               "function" => userfunction,
514     *                               "object" => userobject
515     *                       )
516     * @access    public
517     */
518     function setCallbackFuntiontable($functions)
519     {
520         $this->callback = $functions;
521     } // end func setCallbackFunctiontable
522
523     /**
524     * Recursively removes all data assiciated with a block, including all inner blocks
525     *
526     * @param    string  block to be removed
527     */
528     function removeBlockData($block)
529     {
530         if (isset($this->blockinner[$block])) {
531             foreach ($this->blockinner[$block] as $k => $inner) {
532                 $this->removeBlockData($inner);
533             }
534
535             unset($this->blockinner[$block]);
536         }
537
538         unset($this->blocklist[$block]);
539         unset($this->blockdata[$block]);
540         unset($this->blockvariables[$block]);
541         unset($this->touchedBlocks[$block]);
542
543     } // end func removeBlockinner
544
545     /**
546     * Returns a list of blocknames in the template.
547     *
548     * @return    array    [blockname => blockname]
549     * @access    public
550     * @see        blockExists()
551     */
552     function getBlocklist()
553     {
554         $blocklist = array();
555         foreach ($this->blocklist as $block => $content) {
556             $blocklist[$block] = $block;
557         }
558
559         return $blocklist;
560     } // end func getBlocklist
561
562     /**
563     * Checks wheter a block exists.
564     *
565     * @param    string
566     * @return    boolean
567     * @access    public
568     * @see        getBlocklist()
569     */
570     function blockExists($blockname)
571     {
572         return isset($this->blocklist[$blockname]);
573     } // end func blockExists
574
575     /**
576     * Returns a list of variables of a block.
577     *
578     * @param    string    Blockname
579     * @return    array    [varname => varname]
580     * @access    public
581     * @see        BlockvariableExists()
582     */
583     function getBlockvariables($block)
584     {
585         if (!isset($this->blockvariables[$block])) {
586             return array();
587         }
588
589         $variables = array();
590         foreach ($this->blockvariables[$block] as $variable => $v) {
591             $variables[$variable] = $variable;
592         }
593
594         return $variables;
595     } // end func getBlockvariables
596
597     /**
598     * Checks wheter a block variable exists.
599     *
600     * @param    string    Blockname
601     * @param    string    Variablename
602     * @return    boolean
603     * @access    public
604     * @see    getBlockvariables()
605     */
606     function BlockvariableExists($block, $variable)
607     {
608         return isset($this->blockvariables[$block][$variable]);
609     } // end func BlockvariableExists
610
611     /**
612     * Builds a functionlist from the template.
613     */
614     function buildFunctionlist()
615     {
616         $this->functions = array();
617
618         $template = $this->template;
619         $num = 0;
620
621         while (preg_match($this->functionRegExp, $template, $regs)) {
622
623             $pos = strpos($template, $regs[0]);
624             $template = substr($template, $pos + strlen($regs[0]));
625
626             $head = $this->getValue($template, ')');
627             $args = array();
628
629             $search = $regs[0] . $head . ')';
630
631             $replace = $this->openingDelimiter .
632                        '__function' . $num . '__' .
633                        $this->closingDelimiter;
634
635             $this->template = str_replace($search, $replace, $this->template);
636             $template       = str_replace($search, $replace, $template);
637
638             while ($head != '' && $args2 = $this->getValue($head, ',')) {
639                 $arg2 = trim($args2);
640                 $args[] = ('"' == $arg2{0} || "'" == $arg2{0}) ?
641                                     substr($arg2, 1, -1) : $arg2;
642                 if ($arg2 == $head) {
643                     break;
644                 }
645                 $head = substr($head, strlen($arg2) + 1);
646             }
647
648             $this->functions[$num++] = array(
649                                                 'name'    => $regs[1],
650                                                 'args'    => $args
651                                             );
652         }
653
654     } // end func buildFunctionlist
655
656     function getValue($code, $delimiter) {
657         if ($code == '') {
658             return '';
659         }
660
661         if (!is_array($delimiter)) {
662             $delimiter = array( $delimiter => true );
663         }
664
665         $len         = strlen($code);
666         $enclosed    = false;
667         $enclosed_by = '';
668
669         if (isset($delimiter[$code[0]])) {
670             $i = 1;
671         } else {
672             for ($i = 0; $i < $len; ++$i) {
673                 $char = $code[$i];
674
675                 if (
676                         ($char == '"' || $char = "'") &&
677                         ($char == $enclosed_by || '' == $enclosed_by) &&
678                         (0 == $i || ($i > 0 && '\\' != $code[$i - 1]))
679                     ) {
680
681                     if (!$enclosed) {
682                         $enclosed_by = $char;
683                     } else {
684                         $enclosed_by = "";
685                     }
686                     $enclosed = !$enclosed;
687
688                 }
689
690                 if (!$enclosed && isset($delimiter[$char])) {
691                     break;
692                 }
693             }
694         }
695
696         return substr($code, 0, $i);
697     } // end func getValue
698
699     /**
700     * Deletes one or many variables from the block variable list.
701     *
702     * @param    string    Blockname
703     * @param    mixed     Name of one variable or array of variables
704     *                     ( array ( name => true ) ) to be stripped.
705     */
706     function deleteFromBlockvariablelist($block, $variables)
707     {
708         if (!is_array($variables)) {
709             $variables = array($variables => true);
710         }
711
712         reset($this->blockvariables[$block]);
713         while (list($varname, $val) = each($this->blockvariables[$block])) {
714             if (isset($variables[$varname])) {
715                 unset($this->blockvariables[$block][$varname]);
716             }
717         }
718     } // end deleteFromBlockvariablelist
719
720     /**
721     * Updates the variable list of a block.
722     *
723     * @param    string    Blockname
724     */
725     function updateBlockvariablelist($block)
726     {
727         preg_match_all( $this->variablesRegExp,
728                         $this->blocklist[$block], $regs
729                     );
730
731         if (count($regs[1]) != 0) {
732             foreach ($regs[1] as $k => $var) {
733                 $this->blockvariables[$block][$var] = true;
734             }
735         } else {
736             $this->blockvariables[$block] = array();
737         }
738
739         // check if any inner blocks were found
740         if (isset($this->blockinner[$block]) &&
741             is_array($this->blockinner[$block]) &&
742             count($this->blockinner[$block]) > 0
743         ) {
744             /*
745              * loop through inner blocks, registering the variable
746              * placeholders in each
747              */
748             foreach ($this->blockinner[$block] as $childBlock) {
749                 $this->updateBlockvariablelist($childBlock);
750             }
751         }
752     } // end func updateBlockvariablelist
753
754     /**
755     * Returns an array of blocknames where the given variable
756     * placeholder is used.
757     *
758     * @param    string    Variable placeholder
759     * @return    array    $parents    parents[0..n] = blockname
760     */
761     function findPlaceholderBlocks($variable)
762     {
763         $parents = array();
764         reset($this->blocklist);
765         while (list($blockname, $content) = each($this->blocklist)) {
766             reset($this->blockvariables[$blockname]);
767             while (
768                 list($varname, $val) = each($this->blockvariables[$blockname]))
769             {
770                 if ($variable == $varname) {
771                     $parents[] = $blockname;
772                 }
773             }
774         }
775
776         return $parents;
777     } // end func findPlaceholderBlocks
778
779     /**
780     * Handles warnings, saves them to $warn and prints them or
781     * calls die() depending on the flags
782     *
783     * @param    string    Warning
784     * @param    string    File where the warning occured
785     * @param    int       Linenumber where the warning occured
786     * @see      $warn, $printWarning, $haltOnWarning
787     */
788     function warning($message, $file = '', $line = 0)
789     {
790         $message = sprintf(
791                     'HTML_Template_ITX Warning: %s [File: %s, Line: %d]',
792                     $message,
793                     $file,
794                     $line
795                 );
796
797         $this->warn[] = $message;
798
799         if ($this->printWarning) {
800             print $message;
801         }
802
803         if ($this->haltOnWarning) {
804             die($message);
805         }
806     } // end func warning
807
808 } // end class HTML_Template_ITX
809 ?>