eZ Publish  [trunk]
eztemplate.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZTemplate class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package lib
00009  */
00010 
00011 /*! \defgroup eZTemplate Template system */
00012 
00013 /*!
00014   \class eZTemplate eztemplate.php
00015   \ingroup eZTemplate
00016   \brief The main manager for templates
00017 
00018   The template systems allows for separation of code and
00019   layout by moving the layout part into template files. These
00020   template files are parsed and processed with template variables set
00021   by the PHP code.
00022 
00023   The template system in itself is does not do much, it parses template files
00024   according to a rule set sets up a tree hierarchy and process the data
00025   using functions and operators. The standard template system comes with only
00026   a few functions and no operators, it is meant for these functions and operators
00027   to be specified by the users of the template system. But for simplicity a few
00028   help classes is available which can be easily enabled.
00029 
00030   The classes are:
00031   - eZTemplateDelimitFunction - Inserts the left and right delimiter which are normally parsed.
00032   - eZTemplateSectionFunction - Allows for conditional blocks and loops.
00033   - eZTemplateIncludeFunction - Includes external templates
00034   - eZTemplateSequenceFunction - Creates sequences arrays
00035   - eZTemplateSwitchFunction - Conditional output of template
00036 
00037   - eZTemplatePHPOperator - Allows for easy redirection of operator names to PHP functions.
00038   - eZTemplateLocaleOperator - Allows for locale conversions.
00039   - eZTemplateArrayOperator - Creates arrays
00040   - eZTemplateAttributeOperator - Displays contents of template variables, useful for debugging
00041   - eZTemplateImageOperator - Converts text to image
00042   - eZTemplateLogicOperator - Various logical operators for boolean handling
00043   - eZTemplateUnitOperator - Unit conversion and display
00044 
00045   To enable these functions and operator use registerFunction and registerOperator.
00046 
00047   In keeping with the spirit of being simple the template system does not know how
00048   to get the template files itself. Instead it relies on resource handlers, these
00049   handlers fetches the template files using different kind of transport mechanism.
00050   For simplicity a default resource class is available, eZTemplateFileResource fetches
00051   templates from the filesystem.
00052 
00053   The parser process consists of three passes, each pass adds a new level of complexity.
00054   The first pass strips text from template blocks which starts with a left delimiter and
00055   ends with a right delimiter (default is { and } ), and places them in an array.
00056   The second pass iterates the text and block elements and removes newlines from
00057   text before function blocks and text after function blocks.
00058   The third pass builds the tree according the function rules.
00059 
00060   Processing is done by iterating over the root of the tree, if a text block is found
00061   the text is appended to the result text. If a variable or contant is it's data is extracted
00062   and any operators found are run on it before fetching the result and appending it to
00063   the result text. If a function is found the function is called with the parameters
00064   and it's up to the function handle children if any.
00065 
00066   Constants and template variables will usually be called variables since there's little
00067   difference. A template variable expression will start with a $ and consists of a
00068   namespace (optional) a name and attribues(optional). The variable expression
00069   \verbatim $root:var.attr1 \endverbatim exists in the "root" namespace, has the name "var" and uses the
00070   attribute "attr1". Some functions will create variables on demand, to avoid name conflicts
00071   namespaces were introduced, each function will place the new variables in a namespace
00072   specified in the template file. Attribues are used for fetching parts of the variable,
00073   for instance an element in an array or data in an object. Since the syntax is the
00074   same for arrays and objects the PHP code can use simple arrays when speed is required,
00075   the template code will not care.
00076   A different syntax is also available when you want to access an attribute using a variable.
00077   For instance \verbatim $root:var[$attr_var] \endverbatim, if the variable $attr_var contains "attr1" it would
00078   access the same attribute as in the first example.
00079 
00080   The syntax for operators is a | and a name, optionally parameters can be specified with
00081   ( and ) delimited with ,. Valid operators are \verbatim |upcase, |l10n(date) \endverbatim.
00082 
00083   Functions look a lot like HTML/XML tags. The function consists of a name and parameters
00084   which are assigned using the param=value syntax. Some parameters may be required while
00085   others may be optionally, the exact behaviour is specified by each function.
00086   Valid functions are \verbatim "section name=abc loop=4" \endverbatim
00087 
00088   Example of usage:
00089 \code
00090 // Init template
00091 $tpl = eZTemplate::instance();
00092 
00093 $tpl->registerOperators( new eZTemplatePHPOperator( array( "upcase" => "strtoupper",
00094                                                            "reverse" => "strrev" ) ) );
00095 $tpl->registerOperators( new eZTemplateLocaleOperator() );
00096 $tpl->registerFunction( "section", new eZTemplateSectionFunction( "section" ) );
00097 $tpl->registerFunctions( new eZTemplateDelimitFunction() );
00098 
00099 $tpl->setVariable( "my_var", "{this value set by variable}", "test" );
00100 $tpl->setVariable( "my_arr", array( "1st", "2nd", "third", "fjerde" ) );
00101 $tpl->setVariable( "multidim", array( array( "a", "b" ),
00102                                       array( "c", "d" ),
00103                                       array( "e", "f" ),
00104                                       array( "g", "h" ) ) );
00105 
00106 class mytest
00107 {
00108     function mytest( $n, $s )
00109     {
00110         $this->n = $n;
00111         $this->s = $s;
00112     }
00113 
00114     function hasAttribute( $attr )
00115     {
00116         return ( $attr == "name" || $attr == "size" );
00117     }
00118 
00119     function attribute( $attr )
00120     {
00121         switch ( $attr )
00122         {
00123             case "name";
00124                 return $this->n;
00125             case "size";
00126                 return $this->s;
00127             default:
00128                 $retAttr = null;
00129                 return $retAttr;
00130         }
00131     }
00132 
00133 }
00134 
00135 $tpl->setVariable( "multidim_obj", array( new mytest( "jan", 200 ),
00136                                           new mytest( "feb", 200 ),
00137                                           new mytest( "john", 200 ),
00138                                           new mytest( "doe", 50 ) ) );
00139 $tpl->setVariable( "curdate", time() );
00140 
00141 $tpl->display( "lib/eztemplate/example/test.tpl" );
00142 
00143 // test.tpl
00144 
00145 {section name=outer loop=4}
00146 123
00147 {delimit}::{/delimit}
00148 {/section}
00149 
00150 {literal test=1} This is some {blah arg1="" arg2="abc" /} {/literal}
00151 
00152 <title>This is a test</title>
00153 <table border="1">
00154 <tr><th>{$test:my_var}
00155 {"some text!!!"|upcase|reverse}</th></tr>
00156 {section name=abc loop=$my_arr}
00157 <tr><td>{$abc:item}</td></tr>
00158 {/section}
00159 </table>
00160 
00161 <table border="1">
00162 {section name=outer loop=$multidim}
00163 <tr>
00164 {section name=inner loop=$outer:item}
00165 <td>{$inner:item}</td>
00166 {/section}
00167 </tr>
00168 {/section}
00169 </table>
00170 
00171 <table border="1">
00172 {section name=outer loop=$multidim_obj}
00173 <tr>
00174 <td>{$outer:item.name}</td>
00175 <td>{$outer:item.size}</td>
00176 </tr>
00177 {/section}
00178 </table>
00179 
00180 {section name=outer loop=$nonexistingvar}
00181 <b><i>Dette skal ikke vises</b></i>
00182 {section-else}
00183 <b><i>This is shown when the {ldelim}$loop{rdelim} variable is non-existant</b></i>
00184 {/section}
00185 
00186 
00187 Denne koster {1.4|l10n(currency)}<br>
00188 {-123456789|l10n(number)}<br>
00189 {$curdate|l10n(date)}<br>
00190 {$curdate|l10n(shortdate)}<br>
00191 {$curdate|l10n(time)}<br>
00192 {$curdate|l10n(shorttime)}<br>
00193 {include file="test2.tpl"/}
00194 
00195 \endcode
00196 */
00197 
00198 class eZTemplate
00199 {
00200     const RESOURCE_FETCH = 1;
00201     const RESOURCE_QUERY = 2;
00202 
00203     const ELEMENT_TEXT = 1;
00204     const ELEMENT_SINGLE_TAG = 2;
00205     const ELEMENT_NORMAL_TAG = 3;
00206     const ELEMENT_END_TAG = 4;
00207     const ELEMENT_VARIABLE = 5;
00208     const ELEMENT_COMMENT = 6;
00209 
00210     const NODE_ROOT = 1;
00211     const NODE_TEXT = 2;
00212     const NODE_VARIABLE = 3;
00213     const NODE_FUNCTION = 4;
00214     const NODE_OPERATOR = 5;
00215 
00216 
00217     const NODE_INTERNAL = 100;
00218     const NODE_INTERNAL_CODE_PIECE = 101;
00219 
00220     const NODE_INTERNAL_VARIABLE_SET = 105;
00221     const NODE_INTERNAL_VARIABLE_UNSET = 102;
00222 
00223     const NODE_INTERNAL_NAMESPACE_CHANGE = 103;
00224     const NODE_INTERNAL_NAMESPACE_RESTORE = 104;
00225 
00226     const NODE_INTERNAL_WARNING = 120;
00227     const NODE_INTERNAL_ERROR = 121;
00228 
00229     const NODE_INTERNAL_RESOURCE_ACQUISITION = 140;
00230     const NODE_OPTIMIZED_RESOURCE_ACQUISITION = 141;
00231 
00232     const NODE_INTERNAL_OUTPUT_ASSIGN = 150;
00233     const NODE_INTERNAL_OUTPUT_READ = 151;
00234     const NODE_INTERNAL_OUTPUT_INCREASE = 152;
00235     const NODE_INTERNAL_OUTPUT_DECREASE = 153;
00236 
00237     const NODE_INTERNAL_OUTPUT_SPACING_INCREASE = 160;
00238     const NODE_INTERNAL_SPACING_DECREASE = 161;
00239 
00240     const NODE_OPTIMIZED_INIT = 201;
00241 
00242 
00243     const NODE_USER_CUSTOM = 1000;
00244 
00245 
00246     const TYPE_VOID = 0;
00247     const TYPE_STRING = 1;
00248     const TYPE_NUMERIC = 2;
00249     const TYPE_IDENTIFIER = 3;
00250     const TYPE_VARIABLE = 4;
00251     const TYPE_ATTRIBUTE = 5;
00252     const TYPE_OPERATOR = 6;
00253     const TYPE_BOOLEAN = 7;
00254     const TYPE_ARRAY = 8;
00255     const TYPE_DYNAMIC_ARRAY = 9;
00256 
00257     const TYPE_INTERNAL = 100;
00258     const TYPE_INTERNAL_CODE_PIECE = 101;
00259     const TYPE_PHP_VARIABLE = 102;
00260 
00261     const TYPE_OPTIMIZED_NODE = 201;
00262     const TYPE_OPTIMIZED_ARRAY_LOOKUP = 202;
00263     const TYPE_OPTIMIZED_CONTENT_CALL = 203;
00264     const TYPE_OPTIMIZED_ATTRIBUTE_LOOKUP = 204;
00265 
00266     const TYPE_INTERNAL_STOP = 999;
00267 
00268 
00269     const TYPE_STRING_BIT = 1;
00270     const TYPE_NUMERIC_BIT = 2;
00271     const TYPE_IDENTIFIER_BIT = 4;
00272     const TYPE_VARIABLE_BIT = 8;
00273     const TYPE_ATTRIBUTE_BIT = 16;
00274     const TYPE_OPERATOR_BIT = 32;
00275 
00276     const TYPE_NONE = 0;
00277 
00278     const TYPE_ALL = 63;
00279 
00280     const TYPE_BASIC = 47;
00281 
00282     const TYPE_MODIFIER_MASK = 48;
00283 
00284     const NAMESPACE_SCOPE_GLOBAL = 1;
00285     const NAMESPACE_SCOPE_LOCAL = 2;
00286     const NAMESPACE_SCOPE_RELATIVE = 3;
00287 
00288     const DEBUG_INTERNALS = false;
00289 
00290     const FILE_ERRORS = 1;
00291 
00292     /*!
00293      Intializes the template with left and right delimiters being { and },
00294      and a file resource. The literal tag "literal" is also registered.
00295     */
00296     function eZTemplate()
00297     {
00298         $this->Tree = array( eZTemplate::NODE_ROOT, false );
00299         $this->LDelim = "{";
00300         $this->RDelim = "}";
00301 
00302         $this->IncludeText = array();
00303         $this->IncludeOutput = array();
00304 
00305         $this->registerLiteral( "literal" );
00306 
00307         $res = new eZTemplateFileResource();
00308         $this->DefaultResource = $res;
00309         $this->registerResource( $res );
00310 
00311         $this->Resources = array();
00312         $this->Text = null;
00313 
00314         $this->IsCachingAllowed = true;
00315 
00316         $this->resetErrorLog();
00317 
00318         $this->AutoloadPathList = array( 'lib/eztemplate/classes/' );
00319         $this->Variables = array();
00320         $this->LocalVariablesNamesStack = array();
00321         $this->CurrentLocalVariablesNames = null;
00322         $this->Functions = array();
00323         $this->FunctionAttributes = array();
00324 
00325         $this->TestCompile = false;
00326 
00327         $ini = eZINI::instance( 'template.ini' );
00328         if ( $ini->hasVariable( 'ControlSettings', 'MaxLevel' ) )
00329              $this->MaxLevel = $ini->variable( 'ControlSettings', 'MaxLevel' );
00330 
00331         $this->MaxLevelWarning = ezpI18n::tr( 'lib/template',
00332                                          'The maximum nesting level of %max has been reached. The execution is stopped to avoid infinite recursion.',
00333                                          '',
00334                                          array( '%max' => $this->MaxLevel ) );
00335         eZDebug::createAccumulatorGroup( 'template_total', 'Template Total' );
00336 
00337         $this->TemplatesUsageStatistics = array();
00338         // Array of templates which are used in a single fetch()
00339         $this->TemplateFetchList = array();
00340 
00341         $this->ForeachCounter = 0;
00342         $this->ForCounter     = 0;
00343         $this->WhileCounter   = 0;
00344         $this->DoCounter      = 0;
00345         $this->ElseifCounter  = 0;
00346     }
00347 
00348     /*!
00349      Returns the left delimiter being used.
00350     */
00351     function leftDelimiter()
00352     {
00353         return $this->LDelim;
00354     }
00355 
00356     /*!
00357      Returns the right delimiter being used.
00358     */
00359     function rightDelimiter()
00360     {
00361         return $this->RDelim;
00362     }
00363 
00364     /*!
00365      Sets the left delimiter.
00366     */
00367     function setLeftDelimiter( $delim )
00368     {
00369         $this->LDelim = $delim;
00370     }
00371 
00372     /*!
00373      Sets the right delimiter.
00374     */
00375     function setRightDelimiter( $delim )
00376     {
00377         $this->RDelim = $delim;
00378     }
00379 
00380     /*!
00381      Fetches the result of the template file and displays it.
00382      If $template is supplied it will load this template file first.
00383     */
00384     function display( $template = false, $extraParameters = false )
00385     {
00386         $output = $this->fetch( $template, $extraParameters );
00387         if ( $this->ShowDetails )
00388         {
00389             echo '<h1>Result:</h1>' . "\n";
00390             echo '<hr/>' . "\n";
00391         }
00392         echo "$output";
00393         if ( $this->ShowDetails )
00394         {
00395             echo '<hr/>' . "\n";
00396         }
00397         if ( $this->ShowDetails )
00398         {
00399             echo "<h1>Template data:</h1>";
00400             echo "<p class=\"filename\">" . $template . "</p>";
00401             echo "<pre class=\"example\">" . htmlspecialchars( $this->Text ) . "</pre>";
00402             reset( $this->IncludeText );
00403             while ( ( $key = key( $this->IncludeText ) ) !== null )
00404             {
00405                 $item = $this->IncludeText[$key];
00406                 echo "<p class=\"filename\">" . $key . "</p>";
00407                 echo "<pre class=\"example\">" . htmlspecialchars( $item ) . "</pre>";
00408                 next( $this->IncludeText );
00409             }
00410             echo "<h1>Result text:</h1>";
00411             echo "<p class=\"filename\">" . $template . "</p>";
00412             echo "<pre class=\"example\">" . htmlspecialchars( $output ) . "</pre>";
00413             reset( $this->IncludeOutput );
00414             while ( ( $key = key( $this->IncludeOutput ) ) !== null )
00415             {
00416                 $item = $this->IncludeOutput[$key];
00417                 echo "<p class=\"filename\">" . $key . "</p>";
00418                 echo "<pre class=\"example\">" . htmlspecialchars( $item ) . "</pre>";
00419                 next( $this->IncludeOutput );
00420             }
00421         }
00422     }
00423 
00424     /*!
00425      * Initialize list of local variables for the current template.
00426      * The list contains only names of variables.
00427      */
00428     function createLocalVariablesList()
00429     {
00430         $this->LocalVariablesNamesStack[] = array();
00431         $this->CurrentLocalVariablesNames =& $this->LocalVariablesNamesStack[ count( $this->LocalVariablesNamesStack ) - 1];
00432     }
00433 
00434     /*!
00435      * Check if the given local variable exists.
00436      */
00437     function hasLocalVariable( $varName, $rootNamespace )
00438     {
00439         return ( array_key_exists( $rootNamespace, $this->CurrentLocalVariablesNames ) &&
00440                  array_key_exists( $varName, $this->CurrentLocalVariablesNames[$rootNamespace] ) );
00441     }
00442 
00443     /*!
00444      * Create a local variable.
00445      */
00446     function setLocalVariable( $varName, $varValue, $rootNamespace )
00447     {
00448         $this->CurrentLocalVariablesNames[$rootNamespace][$varName] = 1;
00449         $this->setVariable( $varName, $varValue, $rootNamespace );
00450     }
00451 
00452     /*!
00453      * Destroy a local variable.
00454      */
00455     function unsetLocalVariable( $varName, $rootNamespace )
00456     {
00457         if ( !$this->hasLocalVariable( $varName, $rootNamespace ) )
00458             return;
00459 
00460         $this->unsetVariable( $varName, $rootNamespace );
00461         unset( $this->CurrentLocalVariablesNames[$rootNamespace][$varName] );
00462     }
00463 
00464     /*!
00465      * Destroy all local variables defined in the current template.
00466      */
00467     function unsetLocalVariables()
00468     {
00469         foreach ( $this->CurrentLocalVariablesNames as $ns => $vars )
00470         {
00471             foreach ( $vars as $var => $val )
00472                 $this->unsetLocalVariable( $var, $ns );
00473         }
00474     }
00475 
00476     /*!
00477      * Destroy list of local variables defined in the current (innermost) template.
00478      */
00479     function destroyLocalVariablesList()
00480     {
00481         array_pop( $this->LocalVariablesNamesStack );
00482 
00483         if ( $this->LocalVariablesNamesStack )
00484             $this->CurrentLocalVariablesNames =& $this->LocalVariablesNamesStack[ count( $this->LocalVariablesNamesStack ) - 1];
00485         else
00486             unset( $this->CurrentLocalVariablesNames );
00487     }
00488 
00489     /*!
00490      Tries to fetch the result of the template file and returns it.
00491      If $template is supplied it will load this template file first.
00492     */
00493     function fetch( $template = false, $extraParameters = false, $returnResourceData = false )
00494     {
00495         $this->resetErrorLog();
00496         // Reset fetch list when a new fetch is started
00497         $this->TemplateFetchList = array();
00498 
00499         eZDebug::accumulatorStart( 'template_total' );
00500         eZDebug::accumulatorStart( 'template_load', 'template_total', 'Template load' );
00501         $root = null;
00502         if ( is_string( $template ) )
00503         {
00504             $resourceData = $this->loadURIRoot( $template, true, $extraParameters );
00505             if ( $resourceData and
00506                  $resourceData['root-node'] !== null )
00507                 $root =& $resourceData['root-node'];
00508         }
00509         eZDebug::accumulatorStop( 'template_load' );
00510         if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
00511         {
00512             $savedLocale = setlocale( LC_CTYPE, null );
00513             setlocale( LC_CTYPE, $resourceData['locales'] );
00514         }
00515 
00516         $text = "";
00517 
00518         if ( $root !== null or
00519              $resourceData['compiled-template'] )
00520         {
00521             if ( $this->ShowDetails )
00522                 eZDebug::addTimingPoint( "Process" );
00523             eZDebug::accumulatorStart( 'template_processing', 'template_total', 'Template processing' );
00524 
00525             $templateCompilationUsed = false;
00526             if ( $resourceData['compiled-template'] )
00527             {
00528                 $textElements = array();
00529                 if ( $this->executeCompiledTemplate( $resourceData, $textElements, "", "", $extraParameters ) )
00530                 {
00531                     $text = implode( '', $textElements );
00532                     $templateCompilationUsed = true;
00533                 }
00534             }
00535             if ( !$templateCompilationUsed )
00536             {
00537                 if ( eZTemplate::isDebugEnabled() )
00538                 {
00539                     $fname = $resourceData['template-filename'];
00540                     eZDebug::writeDebug( "FETCH START URI: $template, $fname" );
00541                 }
00542                 $this->process( $root, $text, "", "" );
00543                 if ( eZTemplate::isDebugEnabled() )
00544                     eZDebug::writeDebug( "FETCH END URI: $template, $fname" );
00545             }
00546 
00547             eZDebug::accumulatorStop( 'template_processing' );
00548             if ( $this->ShowDetails )
00549                 eZDebug::addTimingPoint( "Process done" );
00550         }
00551 
00552         eZDebug::accumulatorStop( 'template_total' );
00553 
00554         if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
00555         {
00556             setlocale( LC_CTYPE, $savedLocale );
00557         }
00558 
00559         if ( $returnResourceData )
00560         {
00561             $resourceData['result_text'] = $text;
00562             return $resourceData;
00563         }
00564         return $text;
00565     }
00566 
00567     function process( $root, &$text, $rootNamespace, $currentNamespace )
00568     {
00569         $this->createLocalVariablesList();
00570 
00571         $textElements = array();
00572         $this->processNode( $root, $textElements, $rootNamespace, $currentNamespace );
00573         if ( is_array( $textElements ) )
00574             $text = implode( '', $textElements );
00575         else
00576             $text = $textElements;
00577 
00578         $this->unsetLocalVariables();
00579         $this->destroyLocalVariablesList();
00580     }
00581 
00582     function processNode( $node, &$textElements, $rootNamespace, $currentNamespace )
00583     {
00584         $rslt = null;
00585         $nodeType = $node[0];
00586         if ( $nodeType == eZTemplate::NODE_ROOT )
00587         {
00588             $children = $node[1];
00589             if ( $children )
00590             {
00591                 foreach ( $children as $child )
00592                 {
00593                     $this->processNode( $child, $textElements, $rootNamespace, $currentNamespace );
00594                     if ( !is_array( $textElements ) )
00595                         eZDebug::writeError( "Textelements is no longer array: '$textElements'", __METHOD__ . '::root' );
00596                 }
00597             }
00598         }
00599         else if ( $nodeType == eZTemplate::NODE_TEXT )
00600         {
00601             $textElements[] = $node[2];
00602         }
00603         else if ( $nodeType == eZTemplate::NODE_VARIABLE )
00604         {
00605             $variableData = $node[2];
00606             $variablePlacement = $node[3];
00607             $this->processVariable( $textElements, $variableData, $variablePlacement, $rootNamespace, $currentNamespace );
00608             if ( !is_array( $textElements ) )
00609                 eZDebug::writeError( "Textelements is no longer array: '$textElements'", __METHOD__ . '::variable' );
00610         }
00611         else if ( $nodeType == eZTemplate::NODE_FUNCTION )
00612         {
00613             $functionChildren = $node[1];
00614             $functionName = $node[2];
00615             $functionParameters = $node[3];
00616             $functionPlacement = $node[4];
00617             $rslt = $this->processFunction( $functionName, $textElements, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace );
00618             if ( !is_array( $textElements ) )
00619                 eZDebug::writeError( "Textelements is no longer array: '$textElements'", __METHOD__ . "::function( '$functionName' )" );
00620         }
00621 
00622         return $rslt;
00623     }
00624 
00625     function processVariable( &$textElements, $variableData, $variablePlacement, $rootNamespace, $currentNamespace )
00626     {
00627         $value = $this->elementValue( $variableData, $rootNamespace, $currentNamespace, $variablePlacement );
00628         $this->appendElementText( $textElements, $value, $rootNamespace, $currentNamespace );
00629     }
00630 
00631     function processFunction( $functionName, &$textElements, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace )
00632     {
00633         // Note: This code piece is replicated in the eZTemplateCompiler,
00634         //       if this code is changed the replicated code must be updated as well.
00635         $func = $this->Functions[$functionName];
00636         if ( is_array( $func ) )
00637         {
00638             $this->loadAndRegisterFunctions( $this->Functions[$functionName] );
00639             $func = $this->Functions[$functionName];
00640         }
00641         if ( isset( $func ) and
00642              is_object( $func ) )
00643         {
00644             if ( eZTemplate::isMethodDebugEnabled() )
00645                 eZDebug::writeDebug( "START FUNCTION: $functionName" );
00646             $value = $func->process( $this, $textElements, $functionName, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace );
00647             if ( eZTemplate::isMethodDebugEnabled() )
00648                 eZDebug::writeDebug( "END FUNCTION: $functionName" );
00649             return $value;
00650         }
00651         else
00652         {
00653             $this->warning( "", "Function \"$functionName\" is not registered" );
00654             return null;
00655         }
00656     }
00657 
00658     function fetchFunctionObject( $functionName )
00659     {
00660         $func = $this->Functions[$functionName];
00661         if ( is_array( $func ) )
00662         {
00663             $this->loadAndRegisterFunctions( $this->Functions[$functionName] );
00664             $func = $this->Functions[$functionName];
00665         }
00666         return $func;
00667     }
00668 
00669     /*!
00670      Loads the template using the URI $uri and parses it.
00671      \return The root node of the tree if \a $returnResourceData is false,
00672              if \c true the entire resource data structure.
00673     */
00674     function load( $uri, $extraParameters = false, $returnResourceData = false )
00675     {
00676         $resourceData = $this->loadURIRoot( $uri, true, $extraParameters );
00677         if ( !$resourceData or
00678              $resourceData['root-node'] === null )
00679         {
00680             $retValue = null;
00681             return $retValue;
00682         }
00683         else
00684             return $resourceData['root-node'];
00685     }
00686 
00687     function parse( $sourceText, &$rootElement, $rootNamespace, &$resourceData )
00688     {
00689         $parser = eZTemplateMultiPassParser::instance();
00690         $parser->parse( $this, $sourceText, $rootElement, $rootNamespace, $resourceData );
00691     }
00692 
00693     function loadURIData( $resourceObject, $uri, $resourceName, $template, &$extraParameters, $displayErrors = true )
00694     {
00695         $resourceData = $this->resourceData( $resourceObject, $uri, $resourceName, $template );
00696 
00697         $resourceData['text'] = null;
00698         $resourceData['root-node'] = null;
00699         $resourceData['compiled-template'] = false;
00700         $resourceData['time-stamp'] = null;
00701         $resourceData['key-data'] = null;
00702         $resourceData['locales'] = null;
00703 
00704         if ( !$resourceObject->handleResource( $this, $resourceData, eZTemplate::RESOURCE_FETCH, $extraParameters ) )
00705         {
00706             $resourceData = null;
00707             if ( $displayErrors )
00708                 $this->warning( "", "No template could be loaded for \"$template\" using resource \"$resourceName\"" );
00709         }
00710         return $resourceData;
00711     }
00712 
00713     /*!
00714      \static
00715      Creates a resource data structure of the parameters and returns it.
00716      This structure is passed to various parts of the template system.
00717 
00718      \note If you only have the URI you should call resourceFor() first to
00719            figure out the resource handler.
00720     */
00721     function resourceData( $resourceObject, $uri, $resourceName, $templateName )
00722     {
00723         $resourceData = array();
00724         $resourceData['uri'] = $uri;
00725         $resourceData['resource'] = $resourceName;
00726         $resourceData['template-name'] = $templateName;
00727         $resourceData['template-filename'] = $templateName;
00728         $resourceData['handler'] = $resourceObject;
00729         $resourceData['test-compile'] = $this->TestCompile;
00730         return $resourceData;
00731     }
00732 
00733     /*!
00734      Loads the template using the URI $uri and returns a structure with the text and timestamp,
00735      false otherwise.
00736      The structure keys are:
00737      - "text", the text.
00738      - "time-stamp", the timestamp.
00739     */
00740     function loadURIRoot( $uri, $displayErrors = true, &$extraParameters )
00741     {
00742         $res = "";
00743         $template = "";
00744         $resobj = $this->resourceFor( $uri, $res, $template );
00745 
00746         if ( !is_object( $resobj ) )
00747         {
00748             if ( $displayErrors )
00749                 $this->warning( "", "No resource handler for \"$res\" and no default resource handler, aborting." );
00750             return null;
00751         }
00752 
00753         $canCache = true;
00754         if ( !$resobj->servesStaticData() )
00755             $canCache = false;
00756         if ( !$this->isCachingAllowed() )
00757             $canCache = false;
00758 
00759         $resourceData = $this->loadURIData( $resobj, $uri, $res, $template, $extraParameters, $displayErrors );
00760 
00761         if ( $resourceData )
00762         {
00763             eZTemplate::appendTemplateToStatisticsIfNeeded( $resourceData['template-name'], $resourceData['template-filename'] );
00764             $this->appendTemplateFetch( $resourceData['template-filename'] );
00765 
00766             if ( !$resourceData['compiled-template'] and
00767                  $resourceData['root-node'] === null )
00768             {
00769                 $resourceData['root-node'] = array( eZTemplate::NODE_ROOT, false );
00770                 $templateText = $resourceData["text"];
00771                 $keyData = $resourceData['key-data'];
00772                 $this->setIncludeText( $uri, $templateText );
00773                 $rootNamespace = '';
00774                 $this->parse( $templateText, $resourceData['root-node'], $rootNamespace, $resourceData );
00775 
00776                 if ( eZTemplate::isDebugEnabled() )
00777                 {
00778                     $this->appendDebugNodes( $resourceData['root-node'], $resourceData );
00779                 }
00780 
00781                 if ( $canCache )
00782                     $resobj->setCachedTemplateTree( $keyData, $uri, $res, $template, $extraParameters, $resourceData['root-node'] );
00783             }
00784             if ( !$resourceData['compiled-template'] and
00785                  $canCache and
00786                  $this->canCompileTemplate( $resourceData, $extraParameters ) )
00787             {
00788                 $generateStatus = $this->compileTemplate( $resourceData, $extraParameters );
00789                 if ( $generateStatus )
00790                     $resourceData['compiled-template'] = true;
00791             }
00792         }
00793 
00794         return $resourceData;
00795     }
00796 
00797     function processURI( $uri, $displayErrors = true, &$extraParameters,
00798                          &$textElements, $rootNamespace, $currentNamespace )
00799     {
00800         $this->Level++;
00801         if ( $this->Level > $this->MaxLevel )
00802         {
00803             eZDebug::writeError( $this->MaxLevelWarning, __METHOD__ . " Level: $this->Level @ $uri" );
00804             $textElements[] = $this->MaxLevelWarning;
00805             $this->Level--;
00806             return;
00807         }
00808         $resourceData = $this->loadURIRoot( $uri, $displayErrors, $extraParameters );
00809         if ( !$resourceData or
00810              ( !$resourceData['compiled-template'] and
00811                $resourceData['root-node'] === null ) )
00812         {
00813             $this->Level--;
00814             return;
00815         }
00816 
00817         $templateCompilationUsed = false;
00818 
00819         if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
00820         {
00821             $savedLocale = setlocale( LC_CTYPE, null );
00822             setlocale( LC_CTYPE, $resourceData['locales'] );
00823         }
00824 
00825         if ( $resourceData['compiled-template'] )
00826         {
00827             if ( $this->executeCompiledTemplate( $resourceData, $textElements, $rootNamespace, $currentNamespace, $extraParameters ) )
00828                 $templateCompilationUsed = true;
00829         }
00830         if ( !$templateCompilationUsed )
00831         {
00832             $text = null;
00833             if ( eZTemplate::isDebugEnabled() )
00834             {
00835                 $fname = $resourceData['template-filename'];
00836                 eZDebug::writeDebug( "START URI: $uri, $fname" );
00837             }
00838             $this->process( $resourceData['root-node'], $text, $rootNamespace, $currentNamespace );
00839             if ( eZTemplate::isDebugEnabled() )
00840                 eZDebug::writeDebug( "END URI: $uri, $fname" );
00841             $this->setIncludeOutput( $uri, $text );
00842             $textElements[] = $text;
00843         }
00844 
00845         if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
00846         {
00847             setlocale( LC_CTYPE, $savedLocale );
00848         }
00849         $this->Level--;
00850 
00851     }
00852 
00853     function canCompileTemplate( $resourceData, &$extraParameters )
00854     {
00855         $resourceObject = $resourceData['handler'];
00856         if ( !$resourceObject )
00857             return false;
00858         $canGenerate = $resourceObject->canCompileTemplate( $this, $resourceData, $extraParameters );
00859         return $canGenerate;
00860     }
00861 
00862     /*!
00863      Validates the template file \a $file and returns \c true if the file has correct syntax.
00864      \param $returnResourceData If \c true then the returned value will be the resourcedata structure
00865      \sa compileTemplateFile(), fetch()
00866     */
00867     function validateTemplateFile( $file, $returnResourceData = false )
00868     {
00869         $this->resetErrorLog();
00870 
00871         if ( !file_exists( $file ) )
00872             return false;
00873         $resourceHandler = $this->resourceFor( $file, $resourceName, $templateName );
00874         if ( !$resourceHandler )
00875             return false;
00876         $resourceData = $this->resourceData( $resourceHandler, $file, $resourceName, $templateName );
00877         $resourceData['key-data'] = "file:" . $file;
00878         $extraParameters = array();
00879 
00880         // Disable caching/compiling while fetchin the resource
00881         // It will be restored afterwards
00882         $isCachingAllowed = $this->IsCachingAllowed;
00883         $this->IsCachingAllowed = false;
00884 
00885         $resourceHandler->handleResource( $this, $resourceData, eZTemplate::RESOURCE_FETCH, $extraParameters );
00886 
00887         // Restore previous caching flag
00888         $this->IsCachingAllowed = $isCachingAllowed;
00889 
00890         $root =& $resourceData['root-node'];
00891         $root = array( eZTemplate::NODE_ROOT, false );
00892         $templateText = $resourceData["text"];
00893         $rootNamespace = '';
00894         $this->parse( $templateText, $root, $rootNamespace, $resourceData );
00895         if ( eZTemplate::isDebugEnabled() )
00896         {
00897             $this->appendDebugNodes( $root, $resourceData );
00898         }
00899 
00900         $result = !$this->hasErrors() and !$this->hasWarnings();
00901         if ( $returnResourceData )
00902         {
00903             $resourceData['result'] = $result;
00904             return $resourceData;
00905         }
00906         return $result;
00907     }
00908 
00909     /*!
00910      Compiles the template file \a $file and returns \c true if the compilation was OK.
00911      \param $returnResourceData If \c true then the returned value will be the resourcedata structure
00912      \sa validateTemplateFile(), fetch()
00913     */
00914     function compileTemplateFile( $file, $returnResourceData = false )
00915     {
00916         $this->resetErrorLog();
00917 
00918         if ( !file_exists( $file ) )
00919             return false;
00920         $resourceHandler = $this->resourceFor( $file, $resourceName, $templateName );
00921         if ( !$resourceHandler )
00922             return false;
00923         $resourceData = $this->resourceData( $resourceHandler, $file, $resourceName, $templateName );
00924         $resourceData['key-data'] = "file:" . $file;
00925         $key = md5( $resourceData['key-data'] );
00926         $extraParameters = array();
00927         $resourceHandler->handleResource( $this, $resourceData, eZTemplate::RESOURCE_FETCH, $extraParameters );
00928 
00929         $isCompiled = false;
00930         if ( isset( $resourceData['compiled-template'] ) )
00931             $isCompiled = $resourceData['compiled-template'];
00932 
00933         if ( !$isCompiled )
00934         {
00935             $root =& $resourceData['root-node'];
00936             $root = array( eZTemplate::NODE_ROOT, false );
00937             $templateText = $resourceData["text"];
00938             $rootNamespace = '';
00939             $this->parse( $templateText, $root, $rootNamespace, $resourceData );
00940             if ( eZTemplate::isDebugEnabled() )
00941             {
00942                 $this->appendDebugNodes( $root, $resourceData );
00943             }
00944 
00945             $result = eZTemplateCompiler::compileTemplate( $this, $key, $resourceData );
00946         }
00947         else
00948         {
00949             $result = true;
00950         }
00951 
00952         if ( $returnResourceData )
00953         {
00954             $resourceData['result'] = $result;
00955             return $resourceData;
00956         }
00957         return $result;
00958     }
00959 
00960     function compileTemplate( &$resourceData, &$extraParameters )
00961     {
00962         $resourceObject = $resourceData['handler'];
00963         if ( !$resourceObject )
00964             return false;
00965         $keyData = $resourceData['key-data'];
00966         $uri = $resourceData['uri'];
00967         $resourceName = $resourceData['resource'];
00968         $templatePath = $resourceData['template-name'];
00969         return $resourceObject->compileTemplate( $this, $keyData, $uri, $resourceName, $templatePath, $extraParameters, $resourceData );
00970     }
00971 
00972     function executeCompiledTemplate( &$resourceData, &$textElements, $rootNamespace, $currentNamespace, &$extraParameters )
00973     {
00974         $resourceObject = $resourceData['handler'];
00975         if ( !$resourceObject )
00976             return false;
00977         $keyData = $resourceData['key-data'];
00978         $uri = $resourceData['uri'];
00979         $resourceName = $resourceData['resource'];
00980         $templatePath = $resourceData['template-name'];
00981         $timestamp = $resourceData['time-stamp'];
00982         return $resourceObject->executeCompiledTemplate( $this, $textElements,
00983                                                          $keyData, $uri, $resourceData, $templatePath,
00984                                                          $extraParameters, $timestamp,
00985                                                          $rootNamespace, $currentNamespace );
00986     }
00987 
00988     /*!
00989      Returns the resource object for URI $uri. If a resource type is specified
00990      in the URI it is extracted and set in $res. The template name is set in $template
00991      without any resource specifier. To specify a resource the name and a ":" is
00992      prepended to the URI, for instance file:my.tpl.
00993      If no resource type is found the URI the default resource handler is used.
00994     */
00995     function resourceFor( $uri, &$res, &$template )
00996     {
00997         $args = explode( ":", $uri );
00998         if ( isset( $args[1] ) )
00999         {
01000             $res = $args[0];
01001             $template = $args[1];
01002         }
01003         else
01004             $template = $uri;
01005         if ( eZTemplate::isDebugEnabled() )
01006         {
01007             eZDebug::writeNotice( "eZTemplate: Loading template \"$template\" with resource \"$res\"" );
01008         }
01009         if ( isset( $this->Resources[$res] ) and is_object( $this->Resources[$res] ) )
01010         {
01011             return $this->Resources[$res];
01012         }
01013         return $this->DefaultResource;
01014     }
01015 
01016     /*!
01017      \return The resource handler object for resource name \a $resourceName.
01018      \sa resourceFor
01019     */
01020     function resourceHandler( $resourceName )
01021     {
01022         if ( isset( $this->Resources[$resourceName] ) &&
01023              is_object( $this->Resources[$resourceName] ) )
01024         {
01025             return $this->Resources[$resourceName];
01026         }
01027         return $this->DefaultResource;
01028     }
01029 
01030     function hasChildren( &$function, $functionName )
01031     {
01032         $hasChildren = $function->hasChildren();
01033         if ( is_array( $hasChildren ) )
01034             return $hasChildren[$functionName];
01035         else
01036             return $hasChildren;
01037      }
01038 
01039     /*!
01040      Returns the empty variable type.
01041     */
01042     function emptyVariable()
01043     {
01044         return array( "type" => "null" );
01045     }
01046 
01047     /*!
01048      \static
01049     */
01050     function mergeNamespace( $rootNamespace, $additionalNamespace )
01051     {
01052         $namespace = $rootNamespace;
01053         if ( $namespace == '' )
01054             $namespace = $additionalNamespace;
01055         else if ( $additionalNamespace != '' )
01056             $namespace = "$namespace:$additionalNamespace";
01057         return $namespace;
01058     }
01059 
01060     /*!
01061      Returns the actual value of a template type or null if an unknown type.
01062     */
01063     function elementValue( &$dataElements, $rootNamespace, $currentNamespace, $placement = false,
01064                            $checkExistance = false, $checkForProxy = false )
01065     {
01066         /*
01067          * We use a small dirty hack in this function...
01068          * To help the caller to determine if the value was a proxy object,
01069          * we store boolean true to $dataElements['proxy-object-found'] in this case.
01070          * (it's up to caller to remove this garbage from $dataElements...)
01071          * This behaviour is enabled by $checkForProxy parameter.
01072          */
01073 
01074         $value = null;
01075         if ( !is_array( $dataElements ) )
01076         {
01077             $this->error( "elementValue",
01078                           "Missing array data structure, got " . gettype( $dataElements ) );
01079             return null;
01080         }
01081         foreach ( $dataElements as $dataElement )
01082         {
01083             if ( $dataElement === null )
01084             {
01085                 return null;
01086             }
01087             $dataType = $dataElement[0];
01088             switch ( $dataType )
01089             {
01090                 case eZTemplate::TYPE_VOID:
01091                 {
01092                     if ( !$checkExistance )
01093                         $this->warning( 'elementValue',
01094                                         'Found void datatype, should not be used' );
01095                     else
01096                     {
01097                         return null;
01098                     }
01099                 } break;
01100                 case eZTemplate::TYPE_STRING:
01101                 case eZTemplate::TYPE_NUMERIC:
01102                 case eZTemplate::TYPE_IDENTIFIER:
01103                 case eZTemplate::TYPE_BOOLEAN:
01104                 case eZTemplate::TYPE_ARRAY:
01105                 {
01106                     $value = $dataElement[1];
01107                 } break;
01108                 case eZTemplate::TYPE_VARIABLE:
01109                 {
01110                     $variableData = $dataElement[1];
01111                     $variableNamespace = $variableData[0];
01112                     $variableNamespaceScope = $variableData[1];
01113                     $variableName = $variableData[2];
01114                     if ( $variableNamespaceScope == eZTemplate::NAMESPACE_SCOPE_GLOBAL )
01115                         $namespace = $variableNamespace;
01116                     else if ( $variableNamespaceScope == eZTemplate::NAMESPACE_SCOPE_LOCAL )
01117                         $namespace = $this->mergeNamespace( $rootNamespace, $variableNamespace );
01118                     else if ( $variableNamespaceScope == eZTemplate::NAMESPACE_SCOPE_RELATIVE )
01119                         $namespace = $this->mergeNamespace( $currentNamespace, $variableNamespace );
01120                     else
01121                         $namespace = false;
01122                     if ( $this->hasVariable( $variableName, $namespace ) )
01123                     {
01124                         $value = $this->variable( $variableName, $namespace );
01125                     }
01126                     else
01127                     {
01128                         if ( !$checkExistance )
01129                             $this->error( '', "Unknown template variable '$variableName' in namespace '$namespace'", $placement );
01130                         {
01131                             return null;
01132                         }
01133                     }
01134                 } break;
01135                 case eZTemplate::TYPE_ATTRIBUTE:
01136                 {
01137                     $attributeData = $dataElement[1];
01138                     $attributeValue = $this->elementValue( $attributeData, $rootNamespace, $currentNamespace, false, $checkExistance );
01139 
01140                     if ( $attributeValue !== null )
01141                     {
01142                         if ( !is_numeric( $attributeValue ) and
01143                              !is_string( $attributeValue ) and
01144                              !is_bool( $attributeValue ) )
01145                         {
01146                             if ( !$checkExistance )
01147                                 $this->error( "",
01148                                               "Cannot use type " . gettype( $attributeValue ) . " for attribute lookup", $placement );
01149                             {
01150                                 return null;
01151                             }
01152                         }
01153                         if ( is_array( $value ) )
01154                         {
01155                             if ( array_key_exists( $attributeValue, $value ) )
01156                             {
01157                                 $value = $value[$attributeValue];
01158                             }
01159                             else
01160                             {
01161                                 if ( !$checkExistance )
01162                                 {
01163                                     $arrayAttributeList = array_keys( $value );
01164                                     $arrayCount = count( $arrayAttributeList );
01165                                     $errorMessage = "No such attribute for array($arrayCount): $attributeValue";
01166                                     $chooseText = "Choose one of following: ";
01167                                     $errorMessage .= "\n$chooseText";
01168                                     $errorMessage .= $this->expandAttributes( $arrayAttributeList, $chooseText, 25 );
01169                                     $this->error( "",
01170                                                   $errorMessage, $placement );
01171                                 }
01172                                 return null;
01173                             }
01174                         }
01175                         else if ( is_object( $value ) )
01176                         {
01177                             if ( method_exists( $value, "attribute" ) and
01178                                  method_exists( $value, "hasAttribute" ) )
01179                             {
01180                                 if ( $value->hasAttribute( $attributeValue ) )
01181                                 {
01182                                     $value = $value->attribute( $attributeValue );
01183                                 }
01184                                 else
01185                                 {
01186                                     if ( !$checkExistance )
01187                                     {
01188                                         $objectAttributeList = array();
01189                                         if ( method_exists( $value, 'attributes' ) )
01190                                             $objectAttributeList = $value->attributes();
01191                                         $objectClass= get_class( $value );
01192                                         $errorMessage = "No such attribute for object($objectClass): $attributeValue";
01193                                         $chooseText = "Choose one of following: ";
01194                                         $errorMessage .= "\n$chooseText";
01195                                         $errorMessage .= $this->expandAttributes( $objectAttributeList, $chooseText, 25 );
01196                                         $this->error( "",
01197                                                       $errorMessage, $placement );
01198                                     }
01199                                     return null;
01200                                 }
01201                             }
01202                             else
01203                             {
01204                                 if ( !$checkExistance )
01205                                     $this->error( "",
01206                                                   "Cannot retrieve attribute of object(" . get_class( $value ) .
01207                                                   "), no attribute functions available",
01208                                                   $placement );
01209                                 return null;
01210                             }
01211                         }
01212                         else
01213                         {
01214                             if ( !$checkExistance )
01215                                 $this->error( "",
01216                                               "Cannot retrieve attribute of a " . gettype( $value ),
01217                                               $placement );
01218                             return null;
01219                         }
01220                     }
01221                     else
01222                     {
01223                         if ( !$checkExistance )
01224                             $this->error( '',
01225                                           'Attribute value was null, cannot get attribute',
01226                                           $placement );
01227                         return null;
01228                     }
01229                 } break;
01230                 case eZTemplate::TYPE_OPERATOR:
01231                 {
01232                     $operatorParameters = $dataElement[1];
01233                     $operatorName = $operatorParameters[0];
01234                     $operatorParameters = array_splice( $operatorParameters, 1 );
01235                     if ( is_object( $value ) and
01236                          method_exists( $value, 'templateValue' ) )
01237                     {
01238                         if ( $checkForProxy )
01239                             $dataElements['proxy-object-found'] = true;
01240                         $value = $value->templateValue();
01241                     }
01242                     $valueData = array( 'value' => $value );
01243                     $this->processOperator( $operatorName, $operatorParameters, $rootNamespace, $currentNamespace,
01244                                             $valueData, $placement, $checkExistance );
01245                     $value = $valueData['value'];
01246                 } break;
01247                 default:
01248                 {
01249                     if ( !$checkExistance )
01250                         $this->error( "elementValue",
01251                                       "Unknown data type: '$dataType'" );
01252                     return null;
01253                 }
01254             }
01255         }
01256         if ( is_object( $value ) and
01257              method_exists( $value, 'templateValue' ) )
01258         {
01259             if ( $checkForProxy )
01260                 $dataElements['proxy-object-found'] = true;
01261             return $value->templateValue();
01262         }
01263         return $value;
01264     }
01265 
01266     function expandAttributes( $attributeList, $chooseText, $maxThreshold, $minThreshold = 1 )
01267     {
01268         $errorMessage = '';
01269         $attributeCount = count( $attributeList );
01270         if ( $attributeCount < $minThreshold )
01271             return $errorMessage;
01272         if ( $attributeCount < $maxThreshold )
01273         {
01274             $chooseLength = strlen( $chooseText );
01275             $attributeText = '';
01276             $i = 0;
01277             foreach ( $attributeList as $attributeName )
01278             {
01279                 if ( $i > 0 )
01280                     $attributeText .= ",";
01281                 if ( strlen( $attributeText ) > 40 )
01282                 {
01283                     $attributeText .= "\n";
01284                     $errorMessage .= $attributeText;
01285                     $errorMessage .= str_repeat( ' ', $chooseLength );
01286                     $attributeText = '';
01287                 }
01288                 else if ( $i > 0 )
01289                     $attributeText .= " ";
01290                 $attributeText .= $attributeName;
01291                 ++$i;
01292             }
01293             $errorMessage .= $attributeText;
01294         }
01295         return $errorMessage;
01296     }
01297 
01298     function processOperator( $operatorName, $operatorParameters, $rootNamespace, $currentNamespace,
01299                               &$valueData, $placement = false, $checkExistance = false )
01300     {
01301         $namedParameters = array();
01302         $operatorParameterDefinition = $this->operatorParameterList( $operatorName );
01303         $i = 0;
01304         foreach ( $operatorParameterDefinition as $parameterName => $parameterType )
01305         {
01306             if ( !isset( $operatorParameters[$i] ) or
01307                  !isset( $operatorParameters[$i][0] ) or
01308                  $operatorParameters[$i][0] == eZTemplate::TYPE_VOID )
01309             {
01310                 if ( $parameterType["required"] )
01311                 {
01312                     if ( !$checkExistance )
01313                         $this->warning( "eZTemplateOperatorElement", "Parameter '$parameterName' ($i) missing",
01314                                         $placement );
01315                     $namedParameters[$parameterName] = $parameterType["default"];
01316                 }
01317                 else
01318                 {
01319                     $namedParameters[$parameterName] = $parameterType["default"];
01320                 }
01321             }
01322             else
01323             {
01324                 $parameterData = $operatorParameters[$i];
01325                 $namedParameters[$parameterName] = $this->elementValue( $parameterData, $rootNamespace, $currentNamespace, false, $checkExistance );
01326             }
01327             ++$i;
01328         }
01329 
01330         if ( isset( $this->Operators[$operatorName] ) )
01331         {
01332             if ( is_array( $this->Operators[$operatorName] ) )
01333             {
01334                 $this->loadAndRegisterOperators( $this->Operators[$operatorName] );
01335             }
01336 
01337             $op = $this->Operators[$operatorName];
01338 
01339             if ( is_object( $op ) and method_exists( $op, 'modify' ) )
01340             {
01341                 $value = $valueData['value'];
01342                 if ( eZTemplate::isMethodDebugEnabled() )
01343                     eZDebug::writeDebug( "START OPERATOR: $operatorName" );
01344                 $op->modify( $this, $operatorName, $operatorParameters, $rootNamespace, $currentNamespace, $value, $namedParameters,
01345                              $placement );
01346                 if ( eZTemplate::isMethodDebugEnabled() )
01347                     eZDebug::writeDebug( "END OPERATOR: $operatorName" );
01348                 $valueData['value'] = $value;
01349             }
01350             else
01351                 $this->error( '', "Object problem with operator '$operatorName' ",
01352                               $placement );
01353         }
01354         else if ( !$checkExistance )
01355             $this->warning( "", "Operator '$operatorName' is not registered",
01356                             $placement );
01357     }
01358 
01359     /*!
01360      Return the identifier used for attribute lookup.
01361     */
01362     function attributeValue( &$data, $nspace )
01363     {
01364         switch ( $data["type"] )
01365         {
01366             case "map":
01367             {
01368                 return $data["content"];
01369             } break;
01370             case "index":
01371             {
01372                 return $data["content"];
01373             } break;
01374             case "variable":
01375             {
01376                 return $this->elementValue( $data["content"], $nspace );
01377             } break;
01378             default:
01379             {
01380                 $this->error( "attributeValue()", "Unknown attribute type: " . $data["type"] );
01381                 return null;
01382             }
01383         }
01384     }
01385 
01386     /*!
01387      Helper function for creating a displayable text for a variable.
01388     */
01389     function variableText( $var, $namespace = "", $attrs = array() )
01390     {
01391         $txt = "$";
01392         if ( $namespace != "" )
01393             $txt .= "$namespace:";
01394         $txt .= $var;
01395         if ( !empty( $attrs ) )
01396             $txt .= "." . implode( ".", $attrs );
01397         return $txt;
01398     }
01399 
01400     /*!
01401      Returns the named parameter list for the operator $name.
01402     */
01403     function operatorParameterList( $name )
01404     {
01405         $param_list = array();
01406         if ( !isset( $this->Operators[$name] ) )
01407         {
01408             return $param_list;
01409         }
01410 
01411         if ( is_array( $this->Operators[$name] ) )
01412         {
01413             $this->loadAndRegisterOperators( $this->Operators[$name] );
01414         }
01415 
01416         $op = $this->Operators[$name];
01417         if ( isset( $op ) and
01418              method_exists( $op, "namedParameterList" ) )
01419         {
01420             $param_list = $op->namedParameterList();
01421             if ( method_exists( $op, "namedParameterPerOperator" ) and
01422                  $op->namedParameterPerOperator() )
01423             {
01424                 if ( !isset( $param_list[$name] ) )
01425                     return array();
01426                 $param_list = $param_list[$name];
01427             }
01428         }
01429         return $param_list;
01430     }
01431 
01432     /*!
01433      Tries to run the operator $operatorName with parameters $operatorParameters
01434      on the value $value.
01435     */
01436     function doOperator( $element, &$namespace, &$current_nspace, &$value, $operatorName, $operatorParameters, &$named_params )
01437     {
01438         if ( is_array( $this->Operators[$operatorName] ) )
01439         {
01440             $this->loadAndRegisterOperators( $this->Operators[$operatorName] );
01441         }
01442         $op = $this->Operators[$operatorName];
01443         if ( isset( $op ) )
01444         {
01445             $op->modify( $element, $this, $operatorName, $operatorParameters, $namespace, $current_nspace, $value, $named_params );
01446         }
01447         else
01448             $this->warning( "", "Operator \"$operatorName\" is not registered" );
01449     }
01450 
01451     /*!
01452      Tries to run the function object $func_obj
01453     */
01454     function doFunction( $name, $func_obj, $nspace, $current_nspace )
01455     {
01456         $func = $this->Functions[$name];
01457         if ( is_array( $func ) )
01458         {
01459             $this->loadAndRegisterFunctions( $this->Functions[$name] );
01460             $func = $this->Functions[$name];
01461         }
01462         if ( isset( $func ) and
01463              is_object( $func ) )
01464         {
01465             return $func->process( $this, $name, $func_obj, $nspace, $current_nspace );
01466         }
01467         else
01468         {
01469             $this->warning( "", "Function \"$name\" is not registered" );
01470             return false;
01471         }
01472     }
01473 
01474     /**
01475      * Sets the template variable $var to the value $val.
01476      *
01477      * @param string $var
01478      * @param string $val
01479      * @param string $namespace (optional)
01480      * @param bool $scopeSafe If true, will assure that $var is not overridden for $namespace. False by default
01481      */
01482     function setVariable( $var, $val, $namespace = '', $scopeSafe = false )
01483     {
01484         if ( $scopeSafe && isset( $this->Variables[$namespace][$var] ) )
01485         {
01486             $safeNamespace = $namespace;
01487             do
01488             {
01489                 $safeNamespace .= ':safe';
01490             }
01491             while( isset( $this->Variables[$safeNamespace][$var] ) );
01492 
01493             $this->Variables[$safeNamespace][$var] = $this->Variables[$namespace][$var];
01494         }
01495 
01496         $this->Variables[$namespace][$var] = $val;
01497     }
01498 
01499     /**
01500      * Sets the template variable $var to the value $val by ref
01501      *
01502      * @deprecated Since 4.4, have not used references since 3.10
01503      * @uses eZTemplate::setVariable()
01504      *
01505      * @param string $var
01506      * @param string $val
01507      * @param string $namespace (optional)
01508      */
01509     function setVariableRef( $var, $val, $namespace = '' )
01510     {
01511         $this->setVariable( $var, $val, $namespace );
01512     }
01513 
01514     /**
01515      * Unsets the template variable $var.
01516      *
01517      * @param string $var
01518      * @param string $namespace (optional)
01519      */
01520     function unsetVariable( $var, $namespace = '' )
01521     {
01522         if ( isset( $this->Variables[$namespace] ) &&
01523              array_key_exists( $var, $this->Variables[$namespace] ) )
01524         {
01525             $safeNamespace = "{$namespace}:safe";
01526             if ( isset( $this->Variables[$safeNamespace][$var] ) )
01527             {
01528                 // Check if a nested safe namespace for $var
01529                 // If true, then add a level of testing and test again
01530                 while( isset( $this->Variables["{$safeNamespace}:safe"][$var] )  )
01531                 {
01532                     $safeNamespace .= ':safe';
01533                 }
01534 
01535                 // Get the $var backup back and delete it
01536                 $this->Variables[$namespace][$var] = $this->Variables[$safeNamespace][$var];
01537                 unset( $this->Variables[$safeNamespace][$var] );
01538             }
01539             else
01540             {
01541                 unset( $this->Variables[$namespace][$var] );
01542             }
01543         }
01544         else
01545         {
01546             $this->warning( "unsetVariable()", "Undefined Variable: \${$namespace}:{$var}, cannot unset" );
01547         }
01548     }
01549 
01550     /**
01551      * Returns true if the variable $var is set in namespace $namespace,
01552      * if $attrs is supplied all attributes must exist for the function to return true.
01553      *
01554      * @param string $var
01555      * @param string $namespace (optional)
01556      * @param array $attrs (optional) Deprecated as of 4.4.
01557      * @return bool
01558      */
01559     function hasVariable( $var, $namespace = '', $attrs = null )
01560     {
01561         $exists = ( isset( $this->Variables[$namespace] ) &&
01562                     array_key_exists( $var, $this->Variables[$namespace] ) );
01563         if ( $exists && $attrs !== null && !empty( $attrs ) )
01564         {
01565             eZDebug::writeStrict( '$attrs parameter is deprecated as of 4.4', __METHOD__ );
01566             $ptr =& $this->Variables[$namespace][$var];
01567             foreach( $attrs as $attr )
01568             {
01569                 unset( $tmp );
01570                 if ( is_object( $ptr ) )
01571                 {
01572                     if ( $ptr->hasAttribute( $attr ) )
01573                         $tmp = $ptr->attribute( $attr );
01574                     else
01575                         return false;
01576                 }
01577                 else if ( is_array( $ptr ) )
01578                 {
01579                     if ( array_key_exists( $attr, $ptr ) )
01580                         $tmp =& $ptr[$attr];
01581                     else
01582                         return false;
01583                 }
01584                 else
01585                 {
01586                     return false;
01587                 }
01588                 unset( $ptr );
01589                 $ptr =& $tmp;
01590             }
01591         }
01592         return $exists;
01593     }
01594 
01595     /**
01596      * Returns the content of the variable $var using namespace $namespace,
01597      * if $attrs is supplied the result of the attributes is returned.
01598      *
01599      * @param string $var
01600      * @param string $namespace (optional)
01601      * @param array $attrs (optional) Deprecated as of 4.4
01602      * @return string|array
01603      */
01604     function variable( $var, $namespace = '', $attrs = null )
01605     {
01606         $val = null;
01607         $exists = ( isset( $this->Variables[$namespace] ) &&
01608                     array_key_exists( $var, $this->Variables[$namespace] ) );
01609         if ( $exists )
01610         {
01611             if ( $attrs !== null && !empty( $attrs ) )
01612             {
01613                 eZDebug::writeStrict( '$attrs parameter is deprecated as of 4.4', __METHOD__ );
01614                 $element = $this->Variables[$namespace][$var];
01615                 foreach( $attrs as $attr )
01616                 {
01617                     if ( is_object( $element ) )
01618                     {
01619                         if ( $element->hasAttribute( $attr ) )
01620                         {
01621                             $element = $element->attribute( $attr );
01622                         }
01623                         else
01624                         {
01625                             return $val;
01626                         }
01627                     }
01628                     else if ( is_array( $element ) )
01629                     {
01630                         if ( array_key_exists( $attr, $element ) )
01631                         {
01632                             $val = $element[$attr];
01633                         }
01634                         else
01635                         {
01636                             return $val;
01637                         }
01638                     }
01639                     else
01640                     {
01641                         return $val;
01642                     }
01643                     $val = $element;
01644                 }
01645             }
01646             else
01647             {
01648                 $val = $this->Variables[$namespace][$var];
01649             }
01650         }
01651         return $val;
01652     }
01653 
01654     /*!
01655      Returns the attribute(s) of the template variable $var,
01656      $attrs is an array of attribute names to use iteratively for each new variable returned.
01657     */
01658     function variableAttribute( $var, $attrs )
01659     {
01660         foreach( $attrs as $attr )
01661         {
01662             if ( is_object( $var ) )
01663             {
01664                 if ( $var->hasAttribute( $attr ) )
01665                 {
01666                     $var = $var->attribute( $attr );
01667                 }
01668                 else
01669                 {
01670                     return null;
01671                 }
01672             }
01673             else if ( is_array( $var ) )
01674             {
01675                 if ( isset( $var[$attr] ) )
01676                 {
01677                     $var = $var[$attr];
01678                 }
01679                 else
01680                 {
01681                     return null;
01682                 }
01683             }
01684             else
01685             {
01686                 return null;
01687             }
01688         }
01689         if ( isset( $var ) )
01690         {
01691             return $var;
01692         }
01693 
01694         return null;
01695     }
01696 
01697     function appendElement( &$text, $item, $nspace, $name )
01698     {
01699         $this->appendElementText( $textElements, $item, $nspace, $name );
01700         $text .= implode( '', $textElements );
01701     }
01702 
01703     function appendElementText( &$textElements, $item, $nspace, $name )
01704     {
01705         if ( !is_array( $textElements ) )
01706             $textElements = array();
01707         if ( is_object( $item ) and
01708              method_exists( $item, 'templateValue' ) )
01709         {
01710             $item = $item->templateValue();
01711             $textElements[] = "$item";
01712         }
01713         else if ( is_object( $item ) )
01714         {
01715             $hasTemplateData = false;
01716             if ( method_exists( $item, 'templateData' ) )
01717             {
01718                 $templateData = $item->templateData();
01719                 if ( is_array( $templateData ) and
01720                      isset( $templateData['type'] ) )
01721                 {
01722                     if ( $templateData['type'] == 'template' and
01723                          isset( $templateData['uri'] ) and
01724                          isset( $templateData['template_variable_name'] ) )
01725                     {
01726                         $templateURI =& $templateData['uri'];
01727                         $templateVariableName =& $templateData['template_variable_name'];
01728                         $templateText = '';
01729                         $this->setVariable( $templateVariableName, $item, $name );
01730                         eZTemplateIncludeFunction::handleInclude( $textElements, $templateURI, $this, $nspace, $name );
01731                         $hasTemplateData = true;
01732                     }
01733                 }
01734             }
01735             if ( !$hasTemplateData )
01736                 $textElements[] = method_exists( $item, '__toString' ) ? (string)$item : 'Object(' . get_class( $item ) . ')';
01737         }
01738         else
01739             $textElements[] = "$item";
01740         return $textElements;
01741     }
01742 
01743     /*!
01744      Creates some text nodes before and after the children of \a $root.
01745      It will extract the current filename and uri and create some XHTML
01746      comments and inline text.
01747      \sa isXHTMLCodeIncluded
01748     */
01749     function appendDebugNodes( &$root, &$resourceData )
01750     {
01751         $path = $resourceData['template-filename'];
01752         // Do not ouput debug on pagelayout templates to avoid trigering
01753         // browser quirks mode
01754         if ( isset( $root[1][0][2] ) && is_string( $root[1][0][2] ) && stripos( $root[1][0][2], '<!DOCTYPE' ) === 0 )
01755             return;
01756         $uri = $resourceData['uri'];
01757         $preText = "\n<!-- START: including template: $path ($uri) -->\n";
01758         if ( eZTemplate::isXHTMLCodeIncluded() )
01759             $preText .= "<p class=\"small\">$path</p><br/>\n";
01760         $postText = "\n<!-- STOP: including template: $path ($uri) -->\n";
01761 
01762         $preNode = eZTemplateNodeTool::createTextNode( $preText );
01763         $postNode = eZTemplateNodeTool::createTextNode( $postText );
01764 
01765         if ( is_array( $root[1] ) )
01766         {
01767             $root[1] = array_merge( array( $preNode ), $root[1] );
01768         }
01769         else
01770         {
01771             $root[1] = array( $preNode );
01772         }
01773         $root[1][] = $postNode;
01774     }
01775 
01776     /*!
01777      Registers the functions supplied by the object $functionObject.
01778      The object must have a function called functionList()
01779      which returns an array of functions this object handles.
01780      If the object has a function called attributeList()
01781      it is used for registering function attributes.
01782      The function returns an associative array with each key being
01783      the name of the function and the value being a boolean.
01784      If the boolean is true the function will have children.
01785     */
01786     function registerFunctions( &$functionObject )
01787     {
01788         $this->registerFunctionsInternal( $functionObject );
01789     }
01790 
01791     function registerAutoloadFunctions( $functionDefinition )
01792     {
01793         if ( ( ( isset( $functionDefinition['function'] ) ||
01794                  isset( $functionDefinition['class'] ) ) &&
01795                ( isset( $functionDefinition['function_names_function'] ) ||
01796                  isset( $functionDefinition['function_names'] ) ) ) )
01797         {
01798             if ( isset( $functionDefinition['function_names_function'] ) )
01799             {
01800                 $functionNamesFunction = $functionDefinition['function_names_function'];
01801                 if ( !function_exists( $functionNamesFunction ) )
01802                 {
01803                     $this->error( 'registerFunctions', "Cannot register function definition, missing function names function '$functionNamesFunction'" );
01804                     return;
01805                 }
01806                 $functionNames = $functionNamesFunction();
01807             }
01808             else
01809                 $functionNames = $functionDefinition['function_names'];
01810             foreach ( $functionNames as $functionName )
01811             {
01812                 $this->Functions[$functionName] = $functionDefinition;
01813             }
01814             if ( isset( $functionDefinition['function_attributes'] ) )
01815             {
01816                 foreach ( $functionDefinition['function_attributes'] as $functionAttributeName )
01817                 {
01818                     $this->FunctionAttributes[$functionAttributeName] = $functionDefinition;
01819                 }
01820             }
01821         }
01822         else
01823             $this->error( 'registerFunctions', 'Cannot register function definition, missing data' );
01824     }
01825 
01826     function loadAndRegisterFunctions( $functionDefinition )
01827     {
01828         eZDebug::accumulatorStart( 'template_register_function', 'template_total', 'Template load and register function' );
01829         $functionObject = null;
01830         if ( isset( $functionDefinition['function'] ) )
01831         {
01832             $function = $functionDefinition['function'];
01833             if ( function_exists( $function ) )
01834                 $functionObject = $function();
01835         }
01836         else
01837         {
01838             if ( !class_exists( $functionDefinition['class'], false )
01839                     && isset( $functionDefinition['script'] ) )
01840             {
01841                 include_once( $functionDefinition['script'] );
01842             }
01843             $class = $functionDefinition['class'];
01844             if ( class_exists( $class ) )
01845                 $functionObject = new $class();
01846         }
01847         eZDebug::accumulatorStop( 'template_register_function' );
01848         if ( is_object( $functionObject ) )
01849         {
01850             $this->registerFunctionsInternal( $functionObject, true );
01851             return true;
01852         }
01853         return false;
01854     }
01855 
01856     /*!
01857      \private
01858     */
01859     function registerFunctionsInternal( $functionObject, $debug = false )
01860     {
01861         if ( !is_object( $functionObject ) or
01862              !method_exists( $functionObject, 'functionList' ) )
01863             return false;
01864         foreach ( $functionObject->functionList() as $functionName )
01865         {
01866             $this->Functions[$functionName] = $functionObject;
01867         }
01868         if ( method_exists( $functionObject, "attributeList" ) )
01869         {
01870             $functionAttributes = $functionObject->attributeList();
01871             foreach ( $functionAttributes as $attributeName => $hasChildren )
01872             {
01873                 $this->FunctionAttributes[$attributeName] = $hasChildren;
01874             }
01875         }
01876         return true;
01877     }
01878 
01879     /*!
01880      Registers the function $func_name to be bound to object $func_obj.
01881      If the object has a function called attributeList()
01882      it is used for registering function attributes.
01883      The function returns an associative array with each key being
01884      the name of the function and the value being a boolean.
01885      If the boolean is true the function will have children.
01886     */
01887     function registerFunction( $func_name, $func_obj )
01888     {
01889         $this->Functions[$func_name] = $func_obj;
01890         if ( method_exists( $func_obj, "attributeList" ) )
01891         {
01892             $attrs = $func_obj->attributeList();
01893             while ( list( $attr_name, $has_children ) = each( $attrs ) )
01894             {
01895                 $this->FunctionAttributes[$attr_name] = $has_children;
01896             }
01897         }
01898     }
01899 
01900     /*!
01901      Registers a new literal tag in which the tag will be transformed into
01902      a text element.
01903     */
01904     function registerLiteral( $func_name )
01905     {
01906         $this->Literals[$func_name] = true;
01907     }
01908 
01909     /*!
01910      Removes the literal tag $func_name.
01911     */
01912     function unregisterLiteral( $func_name )
01913     {
01914         unset( $this->Literals[$func_name] );
01915     }
01916 
01917     function registerAutoloadOperators( $operatorDefinition )
01918     {
01919         if ( ( ( isset( $operatorDefinition['function'] ) ||
01920                  isset( $operatorDefinition['class'] ) ) &&
01921                ( isset( $operatorDefinition['operator_names_function'] ) ||
01922                  isset( $operatorDefinition['operator_names'] ) ) ) )
01923         {
01924             if ( isset( $operatorDefinition['operator_names_function'] ) )
01925             {
01926                 $operatorNamesFunction = $operatorDefinition['operator_names_function'];
01927                 if ( !function_exists( $operatorNamesFunction ) )
01928                 {
01929                     $this->error( 'registerOperators', "Cannot register operator definition, missing operator names function '$operatorNamesFunction'" );
01930                     return;
01931                 }
01932                 $operatorNames = $operatorNamesFunction();
01933             }
01934             else
01935                 $operatorNames = $operatorDefinition['operator_names'];
01936             foreach ( $operatorNames as $operatorName )
01937             {
01938                 $this->Operators[$operatorName] = $operatorDefinition;
01939             }
01940         }
01941         else
01942             $this->error( 'registerOperators', 'Cannot register operator definition, missing data' );
01943     }
01944 
01945     function loadAndRegisterOperators( $operatorDefinition )
01946     {
01947         $operatorObject = null;
01948         if ( isset( $operatorDefinition['function'] ) )
01949         {
01950             $function = $operatorDefinition['function'];
01951             if ( function_exists( $function ) )
01952                 $operatorObject = $function();
01953         }
01954         else
01955         {
01956             $class = $operatorDefinition['class'];
01957             if ( !class_exists( $class, false ) && isset( $operatorDefinition['script'] ) )
01958             {
01959                 include_once( $operatorDefinition['script'] );
01960             }
01961             if ( class_exists( $class ) )
01962             {
01963                 if ( isset( $operatorDefinition['class_parameter'] ) )
01964                     $operatorObject = new $class( $operatorDefinition['class_parameter'] );
01965                 else
01966                     $operatorObject = new $class();
01967             }
01968         }
01969         if ( is_object( $operatorObject ) )
01970         {
01971             $this->registerOperatorsInternal( $operatorObject, true );
01972             return true;
01973         }
01974         return false;
01975     }
01976 
01977     /*!
01978      Registers the operators supplied by the object $operatorObject.
01979      The function operatorList() must return an array of operator names.
01980     */
01981     function registerOperators( &$operatorObject )
01982     {
01983         $this->registerOperatorsInternal( $operatorObject );
01984     }
01985 
01986     function registerOperatorsInternal( $operatorObject, $debug = false )
01987     {
01988         if ( !is_object( $operatorObject ) or
01989              !method_exists( $operatorObject, 'operatorList' ) )
01990             return false;
01991         foreach( $operatorObject->operatorList() as $operatorName )
01992         {
01993             $this->Operators[$operatorName] = $operatorObject;
01994         }
01995     }
01996 
01997     /*!
01998      Registers the operator $op_name to use the object $op_obj.
01999     */
02000     function registerOperator( $op_name, $op_obj )
02001     {
02002         $this->Operators[$op_name] = $op_obj;
02003     }
02004 
02005     /*!
02006      Unregisters the operator $op_name.
02007     */
02008     function unregisterOperator( $op_name )
02009     {
02010         if ( is_array( $op_name ) )
02011         {
02012             foreach ( $op_name as $op )
02013             {
02014                 $this->unregisterOperator( $op_name );
02015             }
02016         }
02017         else if ( isset( $this->Operators ) )
02018             unset( $this->Operators[$op_name] );
02019         else
02020             $this->warning( "unregisterOpearator()", "Operator $op_name is not registered, cannot unregister" );
02021     }
02022 
02023     /*!
02024      Not implemented yet.
02025     */
02026     function registerFilter()
02027     {
02028     }
02029 
02030     /*!
02031      Registers a new resource object $res.
02032      The resource object take care of fetching templates using an URI.
02033     */
02034     function registerResource( $res )
02035     {
02036         if ( is_object( $res ) )
02037             $this->Resources[$res->resourceName()] =& $res;
02038         else
02039             $this->warning( "registerResource()", "Supplied argument is not a resource object" );
02040     }
02041 
02042     /*!
02043      Unregisters the resource $res_name.
02044     */
02045     function unregisterResource( $res_name )
02046     {
02047         if ( is_array( $res_name ) )
02048         {
02049             foreach ( $res_name as $res )
02050             {
02051                 $this->unregisterResource( $res );
02052             }
02053         }
02054         else if ( isset( $this->Resources[$res_name] ) )
02055             unset( $this->Resources[$res_name] );
02056         else
02057             $this->warning( "unregisterResource()", "Resource $res_name is not registered, cannot unregister" );
02058     }
02059 
02060     /*!
02061      Sets whether detail output is used or not.
02062      Detail output is useful for debug output where you want to examine the template
02063      and the output text.
02064     */
02065     function setShowDetails( $show )
02066     {
02067         $this->ShowDetails = $show;
02068     }
02069 
02070     /*!
02071      Outputs a warning about the parameter $param missing for function/operator $name.
02072     */
02073     function missingParameter( $name, $param )
02074     {
02075         $this->warning( $name, "Missing parameter $param" );
02076     }
02077 
02078     /*!
02079      Outputs a warning about the parameter count being to high for function/operator $name.
02080     */
02081     function extraParameters( $name, $count, $maxCount )
02082     {
02083         $this->warning( $name, "Passed $count parameters but correct count is $maxCount" );
02084     }
02085 
02086     /*!
02087      Outputs a warning about the variable $var being undefined.
02088     */
02089     function undefinedVariable( $name, $var )
02090     {
02091         $this->warning( $name, "Undefined variable: $var" );
02092     }
02093 
02094     /*!
02095      Outputs an error about the template function $func_name being undefined.
02096     */
02097     function undefinedFunction( $func_name )
02098     {
02099         $this->error( "", "Undefined function: $func_name" );
02100     }
02101 
02102     /*!
02103      Creates a string for the placement information and returns it.
02104      \note The placement information can either be in indexed or associative
02105     */
02106     function placementText( $placement = false )
02107     {
02108         $placementText = false;
02109         if ( $placement !== false )
02110         {
02111             if ( isset( $placement['start'] ) and
02112                  isset( $placement['stop'] ) and
02113                  isset( $placement['templatefile'] ) )
02114             {
02115                 $line = $placement['start']['line'];
02116                 $column = $placement['start']['column'];
02117                 $templateFile = $placement['templatefile'];
02118             }
02119             else
02120             {
02121                 $line = $placement[0][0];
02122                 $column = $placement[0][1];
02123                 $templateFile = $placement[2];
02124             }
02125 
02126             $placementText = " @ $templateFile:$line" . "[$column]";
02127         }
02128         return $placementText;
02129     }
02130 
02131     /*!
02132      Displays a warning for the function/operator $name and text $txt.
02133     */
02134     function warning( $name, $txt, $placement = false )
02135     {
02136         $this->WarningLog[] = array( 'name' => $name,
02137                                      'text' => $txt,
02138                                      'placement' => $placement );
02139 
02140         if ( !is_string( $placement ) )
02141             $placementText = $this->placementText( $placement );
02142         else
02143             $placementText = $placement;
02144         $placementText = $this->placementText( $placement );
02145         if ( $name != "" )
02146             eZDebug::writeWarning( $txt, "eZTemplate:$name" . $placementText );
02147         else
02148             eZDebug::writeWarning( $txt, "eZTemplate" . $placementText );
02149     }
02150 
02151     /*!
02152      Displays an error for the function/operator $name and text $txt.
02153     */
02154     function error( $name, $txt, $placement = false )
02155     {
02156         $this->ErrorLog[] = array( 'name' => $name,
02157                                    'text' => $txt,
02158                                    'placement' => $placement );
02159 
02160         if ( !is_string( $placement ) )
02161             $placementText = $this->placementText( $placement );
02162         else
02163             $placementText = $placement;
02164         if ( $name != "" )
02165             $nameText = "eZTemplate:$name";
02166         else
02167             $nameText = "eZTemplate";
02168         eZDebug::writeError( $txt, $nameText . $placementText );
02169         $hasAppendWarning =& $GLOBALS['eZTemplateHasAppendWarning'];
02170         $ini = $this->ini();
02171         if ( $ini->variable( 'ControlSettings', 'DisplayWarnings' ) == 'enabled' )
02172         {
02173             if ( !isset( $hasAppendWarning ) or
02174                  !$hasAppendWarning )
02175             {
02176                 if ( function_exists( 'eZAppendWarningItem' ) )
02177                 {
02178                     eZAppendWarningItem( array( 'error' => array( 'type' => 'template',
02179                                                                   'number' => eZTemplate::FILE_ERRORS ),
02180                                                 'text' => ezpI18n::tr( 'lib/eztemplate', 'Some template errors occurred, see debug for more information.' ) ) );
02181                     $hasAppendWarning = true;
02182                 }
02183             }
02184         }
02185     }
02186 
02187 
02188     function operatorInputSupported( $operatorName )
02189     {
02190     }
02191 
02192     /*!
02193      Sets the original text for uri $uri to $text.
02194     */
02195     function setIncludeText( $uri, $text )
02196     {
02197         $this->IncludeText[$uri] = $text;
02198     }
02199 
02200     /*!
02201      Sets the output for uri $uri to $output.
02202     */
02203     function setIncludeOutput( $uri, $output )
02204     {
02205         $this->IncludeOutput[$uri] = $output;
02206     }
02207 
02208     /*!
02209      \return the path list which is used for autoloading functions and operators.
02210     */
02211     function autoloadPathList()
02212     {
02213         return $this->AutoloadPathList;
02214     }
02215 
02216     /*!
02217      Sets the path list for autoloading.
02218     */
02219     function setAutoloadPathList( $pathList )
02220     {
02221         $this->AutoloadPathList = $pathList;
02222     }
02223 
02224     /*!
02225      Looks trough the pathes specified in autoloadPathList() and fetches autoload
02226      definition files used for autoloading functions and operators.
02227     */
02228     function autoload()
02229     {
02230         $pathList = $this->autoloadPathList();
02231         foreach ( $pathList as $path )
02232         {
02233             $autoloadFile = $path . 'eztemplateautoload.php';
02234             if ( file_exists( $autoloadFile ) )
02235             {
02236                 unset( $eZTemplateOperatorArray );
02237                 unset( $eZTemplateFunctionArray );
02238                 include( $autoloadFile );
02239                 if ( isset( $eZTemplateOperatorArray ) &&
02240                      is_array( $eZTemplateOperatorArray ) )
02241                 {
02242                     foreach ( $eZTemplateOperatorArray as $operatorDefinition )
02243                     {
02244                         $this->registerAutoloadOperators( $operatorDefinition );
02245                     }
02246                 }
02247                 if ( isset( $eZTemplateFunctionArray ) &&
02248                      is_array( $eZTemplateFunctionArray ) )
02249                 {
02250                     foreach ( $eZTemplateFunctionArray as $functionDefinition )
02251                     {
02252                         $this->registerAutoloadFunctions( $functionDefinition );
02253                     }
02254                 }
02255             }
02256             else
02257             {
02258                 eZDebug::writeWarning( "Path '$path' does not have the file 'eztemplateautoload.php' allthough it reported it had one.\n" .
02259                                        "Looked for file '" . $autoloadFile . "'\n" .
02260                                        "Check the setting [TemplateSettings]/ExtensionAutoloadPath or AutoloadPathList in your site.ini settings." );
02261             }
02262         }
02263     }
02264 
02265     /*!
02266      Resets all template variables.
02267     */
02268     function resetVariables()
02269     {
02270         $this->Variables = array();
02271     }
02272 
02273     /*!
02274      Resets all template functions and operators by calling the resetFunction and resetOperator
02275      on all elements that supports it.
02276     */
02277     function resetElements()
02278     {
02279         foreach ( $this->Functions as $functionName => $functionObject )
02280         {
02281             if ( is_object( $functionObject ) and
02282                  method_exists( $functionObject, 'resetFunction' ) )
02283             {
02284                 $functionObject->resetFunction( $functionName );
02285             }
02286         }
02287 
02288         foreach ( $this->Operators as  $operatorName => $operatorObject )
02289         {
02290             if ( is_object( $operatorObject ) and
02291                  method_exists( $operatorObject, 'resetOperator' ) )
02292             {
02293                 $operatorObject->resetOperator( $operatorName );
02294             }
02295         }
02296     }
02297 
02298     /*!
02299      Resets all template variables, functions, operators and error counts.
02300     */
02301     function reset()
02302     {
02303         $this->resetVariables();
02304         $this->resetElements();
02305         $this->IsCachingAllowed = true;
02306 
02307         $this->resetErrorLog();
02308 
02309         $this->TemplatesUsageStatistics = array();
02310         $this->TemplateFetchList = array();
02311     }
02312 
02313     /*!
02314       \return The number of errors that occured with the last fetch
02315       \sa hasErrors()
02316     */
02317     function errorCount()
02318     {
02319         return count( $this->ErrorLog );
02320     }
02321 
02322     /*!
02323       \return \ true if errors occured with the last fetch.
02324       \sa errorCount()
02325     */
02326     function hasErrors()
02327     {
02328         return $this->errorCount() > 0;
02329     }
02330 
02331     /*!
02332      \return error log.
02333      \sa errorCount()
02334     */
02335     function errorLog()
02336     {
02337         return $this->ErrorLog;
02338     }
02339 
02340     /*!
02341       \return The number of warnings that occured with the last fetch
02342       \sa hasWarnings()
02343     */
02344     function warningCount()
02345     {
02346         return count( $this->WarningLog );
02347     }
02348 
02349     /*!
02350       \return \ true if warnings occured with the last fetch.
02351       \sa warningCount()
02352     */
02353     function hasWarnings()
02354     {
02355         return $this->warningCount() > 0;
02356     }
02357 
02358     /*!
02359      \return waring log.
02360      \sa warningCount()
02361     */
02362     function warningLog()
02363     {
02364         return $this->WarningLog;
02365     }
02366 
02367     /**
02368      * Returns a shared instance of the eZTemplate class.
02369      *
02370      * @return eZTemplate
02371      */
02372     public static function instance()
02373     {
02374         if ( self::$instance === null )
02375         {
02376             self::$instance = new eZTemplate();
02377         }
02378         return self::$instance;
02379     }
02380 
02381     /**
02382      * Returns a shared instance of the eZTemplate class with
02383      * default settings applied, like:
02384      * - Autoload operators loaded
02385      * - Debug mode set
02386      * - eZTemplateDesignResource::instance registered
02387      *
02388      * @since 4.3
02389      * @return eZTemplate
02390      */
02391     public static function factory()
02392     {
02393         if ( self::$factory === false )
02394         {
02395             $instance = self::instance();
02396 
02397             $ini = eZINI::instance();
02398             if (!isset($GLOBALS['eZTemplateDebugInternalsEnabled']) && $ini->variable( 'TemplateSettings', 'Debug' ) == 'enabled' )
02399                 eZTemplate::setIsDebugEnabled( true );
02400 
02401             $compatAutoLoadPath = $ini->variableArray( 'TemplateSettings', 'AutoloadPath' );
02402             $autoLoadPathList   = $ini->variable( 'TemplateSettings', 'AutoloadPathList' );
02403 
02404             $extensionAutoloadPath = $ini->variable( 'TemplateSettings', 'ExtensionAutoloadPath' );
02405             $extensionPathList     = eZExtension::expandedPathList( $extensionAutoloadPath, 'autoloads/' );
02406 
02407             $autoLoadPathList = array_unique( array_merge( $compatAutoLoadPath, $autoLoadPathList, $extensionPathList ) );
02408 
02409             $instance->setAutoloadPathList( $autoLoadPathList );
02410             $instance->autoload();
02411 
02412             $instance->registerResource( eZTemplateDesignResource::instance() );
02413             self::$factory = true;
02414         }
02415         return self::instance();
02416     }
02417 
02418     /**
02419      * Reset shared instance of the eZTemplate class and factory flag
02420      * as used by {@link eZTemplate::instance()} and {@link eZTemplate::factory()}
02421      *
02422      * @since 4.3
02423      */
02424     public static function resetInstance()
02425     {
02426         self::$instance = null;
02427         self::$factory  = false;
02428     }
02429 
02430     /**
02431      * Returns the eZINI object instance for the template.ini file.
02432      *
02433      * @return eZINI
02434      */
02435     public function ini()
02436     {
02437         return eZINI::instance( "template.ini" );
02438     }
02439 
02440     /*!
02441      \static
02442      \return true if special XHTML code should be included before the included template file.
02443              This code will display the template filename in the browser but will eventually
02444              break the design.
02445     */
02446     static function isXHTMLCodeIncluded()
02447     {
02448         if ( !isset( $GLOBALS['eZTemplateDebugXHTMLCodeEnabled'] ) )
02449         {
02450             $ini = eZINI::instance();
02451             $GLOBALS['eZTemplateDebugXHTMLCodeEnabled'] = $ini->variable( 'TemplateSettings', 'ShowXHTMLCode' ) == 'enabled';
02452         }
02453         return $GLOBALS['eZTemplateDebugXHTMLCodeEnabled'];
02454     }
02455 
02456     /*!
02457      \static
02458      \return \c true if debug output of template functions and operators should be enabled.
02459     */
02460     static function isMethodDebugEnabled()
02461     {
02462         if ( !isset( $GLOBALS['eZTemplateDebugMethodEnabled'] ) )
02463         {
02464             $ini = eZINI::instance();
02465             $GLOBALS['eZTemplateDebugMethodEnabled'] = $ini->variable( 'TemplateSettings', 'ShowMethodDebug' ) == 'enabled';
02466         }
02467         return $GLOBALS['eZTemplateDebugMethodEnabled'];
02468     }
02469 
02470     /*!
02471      \static
02472      \return true if debugging of internals is enabled, this will display
02473      which files are loaded and when cache files are created.
02474       Set the option with setIsDebugEnabled().
02475     */
02476     static function isDebugEnabled()
02477     {
02478         if ( !isset( $GLOBALS['eZTemplateDebugInternalsEnabled'] ) )
02479              $GLOBALS['eZTemplateDebugInternalsEnabled'] = eZTemplate::DEBUG_INTERNALS;
02480         return $GLOBALS['eZTemplateDebugInternalsEnabled'];
02481     }
02482 
02483     /*!
02484      \static
02485      Sets whether internal debugging is enabled or not.
02486     */
02487     static function setIsDebugEnabled( $debug )
02488     {
02489         $GLOBALS['eZTemplateDebugInternalsEnabled'] = $debug;
02490     }
02491 
02492     /*!
02493       \return \c true if caching is allowed (default) or \c false otherwise.
02494               This also affects template compiling.
02495       \sa setIsCachingAllowed
02496     */
02497     function isCachingAllowed()
02498     {
02499         return $this->IsCachingAllowed;
02500     }
02501 
02502     /*!
02503       Sets whether caching/compiling is allowed or not. This is useful
02504       if you need to make sure templates are parsed and processed
02505       without any caching mechanisms.
02506       \note The default is to allow caching.
02507       \sa isCachingAllowed
02508     */
02509     function setIsCachingAllowed( $allowed )
02510     {
02511         $this->IsCachingAllowed = $allowed;
02512     }
02513 
02514     /*!
02515      \static
02516      \return \c true if templates usage statistics should be enabled.
02517     */
02518     static function isTemplatesUsageStatisticsEnabled()
02519     {
02520         if ( !isset( $GLOBALS['eZTemplateDebugTemplatesUsageStatisticsEnabled'] ) )
02521         {
02522             $ini = eZINI::instance();
02523             $GLOBALS['eZTemplateDebugTemplatesUsageStatisticsEnabled'] = $ini->variable( 'TemplateSettings', 'ShowUsedTemplates' ) == 'enabled';
02524         }
02525         return ( $GLOBALS['eZTemplateDebugTemplatesUsageStatisticsEnabled'] );
02526     }
02527 
02528     /*!
02529      \static
02530      Sets whether templates usage statistics enabled or not.
02531      \return \c true if templates usage statistics was enabled, otherwise \c false.
02532     */
02533     function setIsTemplatesUsageStatisticsEnabled( $enabled )
02534     {
02535         $wasEnabled = false;
02536         if( isset( $GLOBALS['eZTemplateDebugTemplatesUsageStatisticsEnabled'] ) )
02537             $wasEnabled = $GLOBALS['eZTemplateDebugTemplatesUsageStatisticsEnabled'];
02538 
02539         $GLOBALS['eZTemplateDebugTemplatesUsageStatisticsEnabled'] = $enabled;
02540         return $wasEnabled;
02541     }
02542 
02543     /*!
02544      \static
02545      Checks settings and if 'ShowUsedTemplates' is enabled appends template info to stats.
02546     */
02547     function appendTemplateToStatisticsIfNeeded( &$templateName, &$templateFileName )
02548     {
02549         if ( eZTemplate::isTemplatesUsageStatisticsEnabled() )
02550             eZTemplate::appendTemplateToStatistics( $templateName, $templateFileName );
02551     }
02552 
02553     /*!
02554      \static
02555      Appends template info to stats.
02556     */
02557     function appendTemplateToStatistics( $templateName, $templateFileName )
02558     {
02559         $actualTemplateName = preg_replace( "#^[\w/]+templates/#", '', $templateFileName );
02560         $requestedTemplateName = preg_replace( "#^[\w/]+templates/#", '', $templateName );
02561 
02562         $tpl = eZTemplate::instance();
02563         $needToAppend = true;
02564 
02565         // don't add template info if it is a duplicate of previous.
02566         $statsSize = count( $tpl->TemplatesUsageStatistics );
02567         if ( $statsSize > 0 )
02568         {
02569             $lastTemplateInfo = $tpl->TemplatesUsageStatistics[$statsSize-1];
02570             if ( $lastTemplateInfo['actual-template-name'] === $actualTemplateName &&
02571                  $lastTemplateInfo['requested-template-name'] === $requestedTemplateName &&
02572                  $lastTemplateInfo['template-filename'] === $templateFileName )
02573             {
02574                 $needToAppend = false;
02575             }
02576         }
02577 
02578         if ( $needToAppend )
02579         {
02580             $templateInfo = array( 'actual-template-name' => $actualTemplateName,
02581                                    'requested-template-name' => $requestedTemplateName,
02582                                    'template-filename' => $templateFileName );
02583 
02584             $tpl->TemplatesUsageStatistics[] = $templateInfo;
02585         }
02586     }
02587 
02588     /*!
02589      Appends template info for current fetch.
02590     */
02591     function appendTemplateFetch( $actualTemplateName )
02592     {
02593         $this->TemplateFetchList[] = $actualTemplateName;
02594         $this->TemplateFetchList = array_unique( $this->TemplateFetchList );
02595     }
02596 
02597     /*!
02598      Reset error and warning logs
02599     */
02600     function resetErrorLog()
02601     {
02602         $this->ErrorLog = array();
02603         $this->WarningLog = array();
02604     }
02605 
02606     /*!
02607      \static
02608      Returns template usage statistics
02609     */
02610     static function templatesUsageStatistics()
02611     {
02612         $tpl = eZTemplate::instance();
02613         return $tpl->TemplatesUsageStatistics;
02614     }
02615 
02616     /*!
02617      Returns template list for the last fetch.
02618     */
02619     function templateFetchList()
02620     {
02621         return $this->TemplateFetchList;
02622     }
02623 
02624     /*!
02625      Set template compilation test mode.
02626 
02627      \param true, will set template compilation in test mode ( no disc writes ).
02628             false, will compile templates to disc
02629     */
02630     function setCompileTest( $val )
02631     {
02632         $this->TestCompile = $val;
02633     }
02634 
02635     /*!
02636      Get if template session is test compile
02637     */
02638     function testCompile()
02639     {
02640         return $this->TestCompile;
02641     }
02642 
02643     /// \privatesection
02644     /// Associative array of resource objects
02645     public $Resources;
02646     /// Reference to the default resource object
02647     public $DefaultResource;
02648     /// The original template text
02649     public $Text;
02650     /// Included texts, usually performed by custom functions
02651     public $IncludeText;
02652     /// Included outputs, usually performed by custom functions
02653     public $IncludeOutput;
02654     /// The timestamp of the template when it was last modified
02655     public $TimeStamp;
02656     /// The left delimiter used for parsing
02657     public $LDelim;
02658     /// The right delimiter used for parsing
02659     public $RDelim;
02660 
02661     /// The resulting object tree of the template
02662     public $Tree;
02663     /// An associative array of template variables
02664     public $Variables;
02665     /*!
02666      Last element of this stack contains names of
02667      all variables created in the innermost template, for them
02668      to be destroyed after the template execution finishes.
02669      */
02670     public $LocalVariablesNamesStack;
02671     // Reference to the last element of $LocalVariablesNamesStack.
02672     public $CurrentLocalVariablesNames;
02673     /// An associative array of operators
02674     public $Operators;
02675     /// An associative array of functions
02676     public $Functions;
02677     /// An associative array of function attributes
02678     public $FunctionAttributes;
02679     /// An associative array of literal tags
02680     public $Literals;
02681     /// True if output details is to be shown
02682     public $ShowDetails = false;
02683     /// \c true if caching is allowed
02684     public $IsCachingAllowed;
02685 
02686     /// Array containing all errors occured during a fetch
02687     public $ErrorLog;
02688     /// Array containing all warnings occured during a fetch
02689     public $WarningLog;
02690 
02691     public $AutoloadPathList;
02692     /// include level
02693     public $Level = 0;
02694     public $MaxLevel = 40;
02695 
02696     /// A list of templates used by a rendered page
02697     public $TemplatesUsageStatistics;
02698 
02699     // counter to make unique names for {foreach} loop variables in com
02700     public $ForeachCounter;
02701     public $ForCounter;
02702     public $WhileCounter;
02703     public $DoCounter;
02704     public $ElseifCounter;
02705 
02706     // Flag for setting compilation in test mode
02707     public $TestCompile;
02708 
02709     /**
02710      * Singelton instance of eZTemplate used by {@link eZTemplate::instance()}
02711      * Reset with {@link eZTemplate::resetInstance()}
02712      *
02713      * @var null|eZTemplate
02714      */
02715     protected static $instance;
02716 
02717     /**
02718      * Factory flag as used by {@link eZTemplate::factory()}
02719      * Reset with {@link eZTemplate::resetInstance()}
02720      *
02721      * @var bool
02722      */
02723     protected static $factory = false;
02724 
02725 //     public $CurrentRelatedResource;
02726 //     public $CurrentRelatedTemplateName;
02727 }
02728 
02729 ?>