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.14 2005/11/01 10:14:19 pajoye Exp $
21 require_once 'IT.php';
22 require_once '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.14 2005/11/01 10:14:19 pajoye Exp $
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
211 function replaceBlockfile($block, $filename, $keep_content = false)
213 return $this->replaceBlock($block, $this->getFile($filename), $keep_content);
214 } // end func replaceBlockfile
217 * Adds a block to the template changing a variable placeholder
218 * to a block placeholder.
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()
231 * As no updates to cached data is necessary addBlock() and addBlockfile()
232 * are rather "cheap" meaning quick operations.
234 * The block content must not start with <!-- BEGIN blockname -->
235 * and end with <!-- END blockname --> this would cause overhead and
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
244 * @see addBlockfile()
247 function addBlock($placeholder, $blockname, $template)
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.',
254 } elseif ($blockname == '' ||
255 !preg_match($this->checkblocknameRegExp, $blockname)
257 return new IT_Error("No or invalid blockname '$blockname' given.",
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.',
268 // find out where to insert the new block
269 $parents = $this->findPlaceholderBlocks($placeholder);
270 if (count($parents) == 0) {
273 "The variable placeholder".
274 " '$placeholder' was not found in the template.",
278 } elseif (count($parents) > 1) {
281 while (list($k, $parent) = each($parents)) {
284 $msg = substr($parent, -2);
286 return new IT_Error("The variable placeholder "."'$placeholder'".
287 " must be unique, found in multiple blocks '$msg'.",
292 $template = "<!-- BEGIN $blockname -->" . $template . "<!-- END $blockname -->";
293 $this->findBlocks($template);
294 if ($this->flagBlocktrouble) {
295 return false; // findBlocks() already throws an exception
297 $this->blockinner[$parents[0]][] = $blockname;
298 $this->blocklist[$parents[0]] = preg_replace(
299 '@' . $this->openingDelimiter . $placeholder .
300 $this->closingDelimiter . '@',
302 $this->openingDelimiter . '__' . $blockname . '__' .
303 $this->closingDelimiter,
305 $this->blocklist[$parents[0]]
308 $this->deleteFromBlockvariablelist($parents[0], $placeholder);
309 $this->updateBlockvariablelist($blockname);
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);
320 } // end func addBlock
323 * Adds a block taken from a file to the template changing a variable
324 * placeholder to a block placeholder.
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()
331 function addBlockfile($placeholder, $blockname, $filename)
333 return $this->addBlock($placeholder, $blockname, $this->getFile($filename));
334 } // end func addBlockfile
337 * Returns the name of the (first) block that contains
338 * the specified placeholder.
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.
350 function placeholderExists($placeholder, $block = '')
352 if ($placeholder == '') {
353 new IT_Error('No placeholder name given.', __FILE__, __LINE__);
357 if ($block != '' && !isset($this->blocklist[$block])) {
358 new IT_Error("Unknown block '$block'.", __FILE__, __LINE__);
362 // name of the block where the given placeholder was found
366 if (is_array($variables = $this->blockvariables[$block])) {
367 // search the value in the list of blockvariables
369 while (list($k, $variable) = each($variables)) {
370 if ($k == $placeholder) {
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])) {
390 } // end func placeholderExists
393 * Checks the list of function calls in the template and
394 * calls their callback function.
398 function performCallback()
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 . '__'] =
407 &$GLOBALS[$this->callback[$function['name']]['object']],
408 $this->callback[$function['name']]['function']),
412 $this->variableCache['__function' . $func_id . '__'] =
414 $this->callback[$function['name']]['function'],
421 } // end func performCallback
424 * Returns a list of all function calls in the current template.
429 function getFunctioncalls()
431 return $this->functions;
432 } // end func getFunctioncalls
435 * Replaces a function call with the given replacement.
437 * @param int Function ID
438 * @param string Replacement
441 function setFunctioncontent($functionID, $replacement)
443 $this->variableCache['__function' . $functionID . '__'] = $replacement;
444 } // end func setFunctioncontent
447 * Sets a callback function.
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
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.
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
464 * For those of you ready for the X in IT[X]:
468 * function h_one($args) {
469 * return sprintf('<h1>%s</h1>', $args[0]);
473 * $itx = new HTML_Template_ITX( ... );
475 * $itx->setCallbackFunction('h1', 'h_one');
476 * $itx->performCallback();
480 * func_h1('H1 Headline');
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.
490 setCallbackFunction($tplfunction, $callbackfunction, $callbackobject = '')
492 if ($tplfunction == '' || $callbackfunction == '') {
494 "No template function "."('$tplfunction')".
495 " and/or no callback function ('$callback') given.",
499 $this->callback[$tplfunction] = array(
500 'function' => $callbackfunction,
501 'object' => $callbackobject
505 } // end func setCallbackFunction
508 * Sets the Callback function lookup table
510 * @param array function table
511 * array[templatefunction] =
513 * "function" => userfunction,
514 * "object" => userobject
518 function setCallbackFuntiontable($functions)
520 $this->callback = $functions;
521 } // end func setCallbackFunctiontable
524 * Recursively removes all data assiciated with a block, including all inner blocks
526 * @param string block to be removed
528 function removeBlockData($block)
530 if (isset($this->blockinner[$block])) {
531 foreach ($this->blockinner[$block] as $k => $inner) {
532 $this->removeBlockData($inner);
535 unset($this->blockinner[$block]);
538 unset($this->blocklist[$block]);
539 unset($this->blockdata[$block]);
540 unset($this->blockvariables[$block]);
541 unset($this->touchedBlocks[$block]);
543 } // end func removeBlockinner
546 * Returns a list of blocknames in the template.
548 * @return array [blockname => blockname]
552 function getBlocklist()
554 $blocklist = array();
555 foreach ($this->blocklist as $block => $content) {
556 $blocklist[$block] = $block;
560 } // end func getBlocklist
563 * Checks wheter a block exists.
568 * @see getBlocklist()
570 function blockExists($blockname)
572 return isset($this->blocklist[$blockname]);
573 } // end func blockExists
576 * Returns a list of variables of a block.
578 * @param string Blockname
579 * @return array [varname => varname]
581 * @see BlockvariableExists()
583 function getBlockvariables($block)
585 if (!isset($this->blockvariables[$block])) {
589 $variables = array();
590 foreach ($this->blockvariables[$block] as $variable => $v) {
591 $variables[$variable] = $variable;
595 } // end func getBlockvariables
598 * Checks wheter a block variable exists.
600 * @param string Blockname
601 * @param string Variablename
604 * @see getBlockvariables()
606 function BlockvariableExists($block, $variable)
608 return isset($this->blockvariables[$block][$variable]);
609 } // end func BlockvariableExists
612 * Builds a functionlist from the template.
614 function buildFunctionlist()
616 $this->functions = array();
618 $template = $this->template;
621 while (preg_match($this->functionRegExp, $template, $regs)) {
623 $pos = strpos($template, $regs[0]);
624 $template = substr($template, $pos + strlen($regs[0]));
626 $head = $this->getValue($template, ')');
629 $search = $regs[0] . $head . ')';
631 $replace = $this->openingDelimiter .
632 '__function' . $num . '__' .
633 $this->closingDelimiter;
635 $this->template = str_replace($search, $replace, $this->template);
636 $template = str_replace($search, $replace, $template);
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) {
645 $head = substr($head, strlen($arg2) + 1);
648 $this->functions[$num++] = array(
654 } // end func buildFunctionlist
656 function getValue($code, $delimiter) {
661 if (!is_array($delimiter)) {
662 $delimiter = array( $delimiter => true );
665 $len = strlen($code);
669 if (isset($delimiter[$code[0]])) {
672 for ($i = 0; $i < $len; ++$i) {
676 ($char == '"' || $char = "'") &&
677 ($char == $enclosed_by || '' == $enclosed_by) &&
678 (0 == $i || ($i > 0 && '\\' != $code[$i - 1]))
682 $enclosed_by = $char;
686 $enclosed = !$enclosed;
690 if (!$enclosed && isset($delimiter[$char])) {
696 return substr($code, 0, $i);
697 } // end func getValue
700 * Deletes one or many variables from the block variable list.
702 * @param string Blockname
703 * @param mixed Name of one variable or array of variables
704 * ( array ( name => true ) ) to be stripped.
706 function deleteFromBlockvariablelist($block, $variables)
708 if (!is_array($variables)) {
709 $variables = array($variables => true);
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]);
718 } // end deleteFromBlockvariablelist
721 * Updates the variable list of a block.
723 * @param string Blockname
725 function updateBlockvariablelist($block)
727 preg_match_all( $this->variablesRegExp,
728 $this->blocklist[$block], $regs
731 if (count($regs[1]) != 0) {
732 foreach ($regs[1] as $k => $var) {
733 $this->blockvariables[$block][$var] = true;
736 $this->blockvariables[$block] = array();
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
745 * loop through inner blocks, registering the variable
746 * placeholders in each
748 foreach ($this->blockinner[$block] as $childBlock) {
749 $this->updateBlockvariablelist($childBlock);
752 } // end func updateBlockvariablelist
755 * Returns an array of blocknames where the given variable
756 * placeholder is used.
758 * @param string Variable placeholder
759 * @return array $parents parents[0..n] = blockname
761 function findPlaceholderBlocks($variable)
764 reset($this->blocklist);
765 while (list($blockname, $content) = each($this->blocklist)) {
766 reset($this->blockvariables[$blockname]);
768 list($varname, $val) = each($this->blockvariables[$blockname]))
770 if ($variable == $varname) {
771 $parents[] = $blockname;
777 } // end func findPlaceholderBlocks
780 * Handles warnings, saves them to $warn and prints them or
781 * calls die() depending on the flags
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
788 function warning($message, $file = '', $line = 0)
791 'HTML_Template_ITX Warning: %s [File: %s, Line: %d]',
797 $this->warn[] = $message;
799 if ($this->printWarning) {
803 if ($this->haltOnWarning) {
806 } // end func warning
808 } // end class HTML_Template_ITX