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 // +----------------------------------------------------------------------+
18 // $Id: ITX.php,v 1.16 2006/08/17 15:47:22 dsp Exp $
21 require_once 'HTML/Template/IT.php';
22 require_once 'HTML/Template/IT_Error.php';
25 * Integrated Template Extension - ITX
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.
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.
36 * @author Ulf Wendel <uw@netuse.de>
38 * @version $Id: ITX.php,v 1.16 2006/08/17 15:47:22 dsp Exp $
39 * @package HTML_Template_IT
41 class HTML_Template_ITX
extends HTML_Template_IT
44 * Array with all warnings.
47 * @see $printWarning, $haltOnWarning, warning()
55 * @see $haltOnWarning, $warn, warning()
57 var $printWarning = false;
60 * Call die() on warning?
63 * @see $warn, $printWarning, warning()
65 var $haltOnWarning = false;
68 * RegExp used to test for a valid blockname.
71 var $checkblocknameRegExp = '';
74 * Functionnameprefix used when searching function calls in the template.
77 var $functionPrefix = 'func_';
80 * Functionname RegExp.
83 var $functionnameRegExp = '[_a-zA-Z]+[A-Za-z_0-9]*';
86 * RegExp used to grep function calls in the template.
88 * The variable gets set by the constructor.
91 * @see HTML_Template_IT()
93 var $functionRegExp = '';
96 * List of functions found in the template.
100 var $functions = array();
103 * List of callback functions specified by the user.
107 var $callback = array();
110 * Builds some complex regexps and calls the constructor
111 * of the parent class.
113 * Make sure that you call this constructor if you derive your own
114 * template class from this one.
116 * @see HTML_Template_IT()
118 function HTML_Template_ITX($root = '')
121 $this->checkblocknameRegExp
= '@' . $this->blocknameRegExp
. '@';
122 $this->functionRegExp
= '@' . $this->functionPrefix
. '(' .
123 $this->functionnameRegExp
. ')\s*\(@sm';
125 $this->HTML_Template_IT($root);
126 } // end func constructor
131 $this->buildFunctionlist();
132 $this->findBlocks($this->template
);
133 // we don't need it any more
134 $this->template
= '';
135 $this->buildBlockvariablelist();
140 * Replaces an existing block with new content.
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.
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.
155 * @param string Blockname
156 * @param string Blockcontent
157 * @param boolean true if the new block inherits the content
161 * @see replaceBlockfile(), addBlock(), addBlockfile()
164 function replaceBlock($block, $template, $keep_content = false)
166 if (!isset($this->blocklist
[$block])) {
168 "The block "."'$block'".
169 " does not exist in the template and thus it can't be replaced.",
174 if ($template == '') {
175 return new IT_Error('No block content given.', __FILE__
, __LINE__
);
179 $blockdata = $this->blockdata
[$block];
182 // remove all kinds of links to the block / data of the block
183 $this->removeBlockData($block);
185 $template = "<!-- BEGIN $block -->" . $template . "<!-- END $block -->";
186 $parents = $this->blockparents
[$block];
187 $this->findBlocks($template);
188 $this->blockparents
[$block] = $parents;
190 // KLUDGE: rebuild the list for all block - could be done faster
191 $this->buildBlockvariablelist();
194 $this->blockdata
[$block] = $blockdata;
197 // old TODO - I'm not sure if we need this
201 } // end func replaceBlock
204 * Replaces an existing block with new content from a file.
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
212 function replaceBlockfile($block, $filename, $keep_content = false)
214 return $this->replaceBlock($block, $this->getFile($filename), $keep_content);
215 } // end func replaceBlockfile
218 * Adds a block to the template changing a variable placeholder
219 * to a block placeholder.
221 * Add means "replace a variable placeholder by a new block".
222 * This is different to PHPLibs templates. The function loads a
223 * block, creates a handle for it and assigns it to a certain
224 * variable placeholder. To to the same with PHPLibs templates you would
225 * call set_file() to create the handle and parse() to assign the
226 * parsed block to a variable. By this PHPLibs templates assume
227 * that you tend to assign a block to more than one one placeholder.
228 * To assign a parsed block to more than only the placeholder you specify
229 * in this function you have to use a combination of getBlock()
232 * As no updates to cached data is necessary addBlock() and addBlockfile()
233 * are rather "cheap" meaning quick operations.
235 * The block content must not start with <!-- BEGIN blockname -->
236 * and end with <!-- END blockname --> this would cause overhead and
239 * @param string Name of the variable placeholder, the name must be unique
240 * within the template.
241 * @param string Name of the block to be added
242 * @param string Content of the block
245 * @see addBlockfile()
248 function addBlock($placeholder, $blockname, $template)
250 // Don't trust any user even if it's a programmer or yourself...
251 if ($placeholder == '') {
252 return new IT_Error('No variable placeholder given.',
255 } elseif ($blockname == '' ||
256 !preg_match($this->checkblocknameRegExp
, $blockname)
258 return new IT_Error("No or invalid blockname '$blockname' given.",
261 } elseif ($template == '') {
262 return new IT_Error('No block content given.', __FILE__
, __LINE__
);
263 } elseif (isset($this->blocklist
[$blockname])) {
264 return new IT_Error('The block already exists.',
269 // find out where to insert the new block
270 $parents = $this->findPlaceholderBlocks($placeholder);
271 if (count($parents) == 0) {
274 "The variable placeholder".
275 " '$placeholder' was not found in the template.",
279 } elseif (count($parents) > 1) {
282 while (list($k, $parent) = each($parents)) {
285 $msg = substr($parent, -2);
287 return new IT_Error("The variable placeholder "."'$placeholder'".
288 " must be unique, found in multiple blocks '$msg'.",
293 $template = "<!-- BEGIN $blockname -->" . $template . "<!-- END $blockname -->";
294 $this->findBlocks($template);
295 if ($this->flagBlocktrouble
) {
296 return false; // findBlocks() already throws an exception
298 $this->blockinner
[$parents[0]][] = $blockname;
299 $this->blocklist
[$parents[0]] = preg_replace(
300 '@' . $this->openingDelimiter
. $placeholder .
301 $this->closingDelimiter
. '@',
303 $this->openingDelimiter
. '__' . $blockname . '__' .
304 $this->closingDelimiter
,
306 $this->blocklist
[$parents[0]]
309 $this->deleteFromBlockvariablelist($parents[0], $placeholder);
310 $this->updateBlockvariablelist($blockname);
313 } // end func addBlock
316 * Adds a block taken from a file to the template changing a variable
317 * placeholder to a block placeholder.
319 * @param string Name of the variable placeholder to be converted
320 * @param string Name of the block to be added
321 * @param string File that contains the block
322 * @brother addBlock()
325 function addBlockfile($placeholder, $blockname, $filename)
327 return $this->addBlock($placeholder, $blockname, $this->getFile($filename));
328 } // end func addBlockfile
331 * Returns the name of the (first) block that contains
332 * the specified placeholder.
334 * @param string Name of the placeholder you're searching
335 * @param string Name of the block to scan. If left out (default)
336 * all blocks are scanned.
337 * @return string Name of the (first) block that contains
338 * the specified placeholder.
339 * If the placeholder was not found or an error occured
340 * an empty string is returned.
344 function placeholderExists($placeholder, $block = '')
346 if ($placeholder == '') {
347 new IT_Error('No placeholder name given.', __FILE__
, __LINE__
);
351 if ($block != '' && !isset($this->blocklist
[$block])) {
352 new IT_Error("Unknown block '$block'.", __FILE__
, __LINE__
);
356 // name of the block where the given placeholder was found
360 if (is_array($variables = $this->blockvariables
[$block])) {
361 // search the value in the list of blockvariables
363 while (list($k, $variable) = each($variables)) {
364 if ($k == $placeholder) {
372 // search all blocks and return the name of the first block that
373 // contains the placeholder
374 reset($this->blockvariables
);
375 while (list($blockname, $variables) = each($this->blockvariables
)){
376 if (is_array($variables) && isset($variables[$placeholder])) {
384 } // end func placeholderExists
387 * Checks the list of function calls in the template and
388 * calls their callback function.
392 function performCallback()
394 reset($this->functions
);
395 while (list($func_id, $function) = each($this->functions
)) {
396 if (isset($this->callback
[$function['name']])) {
397 if ($this->callback
[$function['name']]['expandParameters']) {
398 $callFunction = 'call_user_func_array';
400 $callFunction = 'call_user_func';
403 if ($this->callback
[$function['name']]['object'] != '') {
407 &$GLOBALS[$this->callback
[$function['name']]['object']],
408 $this->callback
[$function['name']]['function']),
415 $this->callback
[$function['name']]['function'],
419 $this->variableCache
['__function' . $func_id . '__'] = $call;
423 } // end func performCallback
426 * Returns a list of all function calls in the current template.
431 function getFunctioncalls()
433 return $this->functions
;
434 } // end func getFunctioncalls
437 * Replaces a function call with the given replacement.
439 * @param int Function ID
440 * @param string Replacement
443 function setFunctioncontent($functionID, $replacement)
445 $this->variableCache
['__function' . $functionID . '__'] = $replacement;
446 } // end func setFunctioncontent
449 * Sets a callback function.
451 * IT[X] templates (note the X) can contain simple function calls.
452 * "function call" means that the editor of the template can add
453 * special placeholder to the template like 'func_h1("embedded in h1")'.
454 * IT[X] will grab this function calls and allow you to define a callback
457 * This is an absolutely evil feature. If your application makes heavy
458 * use of such callbacks and you're even implementing if-then etc. on
459 * the level of a template engine you're reiventing the wheel... - that's
460 * actually how PHP came into life. Anyway, sometimes it's handy.
462 * Consider also using XML/XSLT or native PHP. And please do not push
463 * IT[X] any further into this direction of adding logics to the template
466 * For those of you ready for the X in IT[X]:
470 * function h_one($args) {
471 * return sprintf('<h1>%s</h1>', $args[0]);
475 * $itx = new HTML_Template_ITX( ... );
477 * $itx->setCallbackFunction('h1', 'h_one');
478 * $itx->performCallback();
482 * func_h1('H1 Headline');
484 * @param string Function name in the template
485 * @param string Name of the callback function
486 * @param string Name of the callback object
487 * @param boolean If the callback is called with a list of parameters or
488 * with an array holding the parameters
489 * @return boolean False on failure.
492 * @deprecated The $callbackobject parameter is depricated since
493 * version 1.2 and might be dropped in further versions.
496 setCallbackFunction($tplfunction, $callbackfunction, $callbackobject = '', $expandCallbackParameters=false)
498 if ($tplfunction == '' ||
$callbackfunction == '') {
500 "No template function "."('$tplfunction')".
501 " and/or no callback function ('$callback') given.",
505 $this->callback
[$tplfunction] = array(
506 'function' => $callbackfunction,
507 'object' => $callbackobject,
508 'expandParameters' => (boolean
) $expandCallbackParameters
512 } // end func setCallbackFunction
515 * Sets the Callback function lookup table
517 * @param array function table
518 * array[templatefunction] =
520 * "function" => userfunction,
521 * "object" => userobject
525 function setCallbackFuntiontable($functions)
527 $this->callback
= $functions;
528 } // end func setCallbackFunctiontable
531 * Recursively removes all data assiciated with a block, including all inner blocks
533 * @param string block to be removed
536 function removeBlockData($block)
538 if (isset($this->blockinner
[$block])) {
539 foreach ($this->blockinner
[$block] as $k => $inner) {
540 $this->removeBlockData($inner);
543 unset($this->blockinner
[$block]);
546 unset($this->blocklist
[$block]);
547 unset($this->blockdata
[$block]);
548 unset($this->blockvariables
[$block]);
549 unset($this->touchedBlocks
[$block]);
551 } // end func removeBlockinner
554 * Returns a list of blocknames in the template.
556 * @return array [blockname => blockname]
560 function getBlocklist()
562 $blocklist = array();
563 foreach ($this->blocklist
as $block => $content) {
564 $blocklist[$block] = $block;
568 } // end func getBlocklist
571 * Checks wheter a block exists.
576 * @see getBlocklist()
578 function blockExists($blockname)
580 return isset($this->blocklist
[$blockname]);
581 } // end func blockExists
584 * Returns a list of variables of a block.
586 * @param string Blockname
587 * @return array [varname => varname]
589 * @see BlockvariableExists()
591 function getBlockvariables($block)
593 if (!isset($this->blockvariables
[$block])) {
597 $variables = array();
598 foreach ($this->blockvariables
[$block] as $variable => $v) {
599 $variables[$variable] = $variable;
603 } // end func getBlockvariables
606 * Checks wheter a block variable exists.
608 * @param string Blockname
609 * @param string Variablename
612 * @see getBlockvariables()
614 function BlockvariableExists($block, $variable)
616 return isset($this->blockvariables
[$block][$variable]);
617 } // end func BlockvariableExists
620 * Builds a functionlist from the template.
623 function buildFunctionlist()
625 $this->functions
= array();
627 $template = $this->template
;
630 while (preg_match($this->functionRegExp
, $template, $regs)) {
632 $pos = strpos($template, $regs[0]);
633 $template = substr($template, $pos +
strlen($regs[0]));
635 $head = $this->getValue($template, ')');
638 $search = $regs[0] . $head . ')';
640 $replace = $this->openingDelimiter
.
641 '__function' . $num . '__' .
642 $this->closingDelimiter
;
644 $this->template
= str_replace($search, $replace, $this->template
);
645 $template = str_replace($search, $replace, $template);
647 while ($head != '' && $args2 = $this->getValue($head, ',')) {
648 $arg2 = trim($args2);
649 $args[] = ('"' == $arg2{0} ||
"'" == $arg2{0}) ?
650 substr($arg2, 1, -1) : $arg2;
651 if ($arg2 == $head) {
654 $head = substr($head, strlen($arg2) +
1);
657 $this->functions
[$num++
] = array(
663 } // end func buildFunctionlist
666 * Truncates the given code from the first occurence of
667 * $delimiter but ignores $delimiter enclosed by " or '.
670 * @param string The code which should be parsed
671 * @param string The delimiter char
673 * @see buildFunctionList()
675 function getValue($code, $delimiter) {
680 if (!is_array($delimiter)) {
681 $delimiter = array( $delimiter => true );
684 $len = strlen($code);
688 if (isset($delimiter[$code[0]])) {
691 for ($i = 0; $i < $len; ++
$i) {
695 ($char == '"' ||
$char == "'") &&
696 ($char == $enclosed_by ||
'' == $enclosed_by) &&
697 (0 == $i ||
($i > 0 && '\\' != $code[$i - 1]))
701 $enclosed_by = $char;
705 $enclosed = !$enclosed;
709 if (!$enclosed && isset($delimiter[$char])) {
715 return substr($code, 0, $i);
716 } // end func getValue
719 * Deletes one or many variables from the block variable list.
721 * @param string Blockname
722 * @param mixed Name of one variable or array of variables
723 * ( array ( name => true ) ) to be stripped.
726 function deleteFromBlockvariablelist($block, $variables)
728 if (!is_array($variables)) {
729 $variables = array($variables => true);
732 reset($this->blockvariables
[$block]);
733 while (list($varname, $val) = each($this->blockvariables
[$block])) {
734 if (isset($variables[$varname])) {
735 unset($this->blockvariables
[$block][$varname]);
738 } // end deleteFromBlockvariablelist
741 * Updates the variable list of a block.
743 * @param string Blockname
746 function updateBlockvariablelist($block)
748 preg_match_all( $this->variablesRegExp
,
749 $this->blocklist
[$block], $regs
752 if (count($regs[1]) != 0) {
753 foreach ($regs[1] as $k => $var) {
754 $this->blockvariables
[$block][$var] = true;
757 $this->blockvariables
[$block] = array();
760 // check if any inner blocks were found
761 if (isset($this->blockinner
[$block]) &&
762 is_array($this->blockinner
[$block]) &&
763 count($this->blockinner
[$block]) > 0
766 * loop through inner blocks, registering the variable
767 * placeholders in each
769 foreach ($this->blockinner
[$block] as $childBlock) {
770 $this->updateBlockvariablelist($childBlock);
773 } // end func updateBlockvariablelist
776 * Returns an array of blocknames where the given variable
777 * placeholder is used.
779 * @param string Variable placeholder
780 * @return array $parents parents[0..n] = blockname
783 function findPlaceholderBlocks($variable)
786 reset($this->blocklist
);
787 while (list($blockname, $content) = each($this->blocklist
)) {
788 reset($this->blockvariables
[$blockname]);
790 list($varname, $val) = each($this->blockvariables
[$blockname]))
792 if ($variable == $varname) {
793 $parents[] = $blockname;
799 } // end func findPlaceholderBlocks
802 * Handles warnings, saves them to $warn and prints them or
803 * calls die() depending on the flags
805 * @param string Warning
806 * @param string File where the warning occured
807 * @param int Linenumber where the warning occured
808 * @see $warn, $printWarning, $haltOnWarning
811 function warning($message, $file = '', $line = 0)
814 'HTML_Template_ITX Warning: %s [File: %s, Line: %d]',
820 $this->warn
[] = $message;
822 if ($this->printWarning
) {
826 if ($this->haltOnWarning
) {
829 } // end func warning
831 } // end class HTML_Template_ITX