eZ Publish  [4.0]
eztemplateforeachfunction.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // Definition of eZTemplateForeachFunction class
00004 //
00005 // Created on: <24-Feb-2005 15:47:35 vs>
00006 //
00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00008 // SOFTWARE NAME: eZ Publish
00009 // SOFTWARE RELEASE: 4.0.x
00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS
00011 // SOFTWARE LICENSE: GNU General Public License v2.0
00012 // NOTICE: >
00013 //   This program is free software; you can redistribute it and/or
00014 //   modify it under the terms of version 2.0  of the GNU General
00015 //   Public License as published by the Free Software Foundation.
00016 //
00017 //   This program is distributed in the hope that it will be useful,
00018 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 //   GNU General Public License for more details.
00021 //
00022 //   You should have received a copy of version 2.0 of the GNU General
00023 //   Public License along with this program; if not, write to the Free
00024 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00025 //   MA 02110-1301, USA.
00026 //
00027 //
00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00029 //
00030 
00031 /*!
00032   \class eZTemplateForeachFunction eztemplateforeachfunction.php
00033   \ingroup eZTemplateFunctions
00034   \brief FOREACH loop
00035 
00036   Syntax:
00037 \code
00038     {foreach <array> as [$keyVar =>] $itemVar
00039              [sequence <array> as $sequenceVar]
00040              [offset <offset>]
00041             [max <max>]
00042             [reverse]}
00043 
00044         [{delimiter}...{/delimiter}]
00045         [{break}]
00046         [{continue}]
00047         [{skip}]
00048     {/foreach}
00049 \endcode
00050 
00051   Example:
00052 \code
00053     {foreach $objects as $object}
00054         <tr>
00055         {foreach $object.nodes as $node sequence array(dark,light) as $class}
00056             <td class=$class>
00057             {$node.name|wash}
00058             </td>
00059         {/foreach}
00060         </tr>
00061     {/foreach}
00062 \endcode
00063 */
00064 
00065 class eZTemplateForeachFunction
00066 {
00067     const FUNCTION_NAME = 'foreach';
00068 
00069     /*!
00070      * Returns an array of the function names, required for eZTemplate::registerFunctions().
00071      */
00072     function &functionList()
00073     {
00074         $functionList = array( eZTemplateForeachFunction::FUNCTION_NAME );
00075         return $functionList;
00076     }
00077 
00078     /*!
00079      * Returns the attribute list.
00080      * key:   parameter name
00081      * value: can have children
00082      */
00083     function attributeList()
00084     {
00085         return array( 'delimiter' => true,
00086                       'break'     => false,
00087                       'continue'  => false,
00088                       'skip'      => false );
00089     }
00090 
00091 
00092     /*!
00093      * Returns the array with hits for the template compiler.
00094      */
00095     function functionTemplateHints()
00096     {
00097         return array( eZTemplateForeachFunction::FUNCTION_NAME => array( 'parameters' => true,
00098                                                                   'static' => false,
00099                                                                   'transform-parameters' => true,
00100                                                                   'tree-transformation' => true ) );
00101     }
00102 
00103     /*!
00104      * Compiles the function and its children into PHP code.
00105      */
00106     function templateNodeTransformation( $functionName, &$node,
00107                                          $tpl, $parameters, $privateData )
00108     {
00109         /*
00110         {foreach <array> as [$keyVar =>] $itemVar
00111                  [sequence <array> as $sequenceVar]
00112                  [offset <offset>]
00113                  [max <max>]
00114                  [reverse]
00115         }
00116         */
00117 
00118         $tpl->ForeachCounter++;
00119         $newNodes            = array();
00120         $nodePlacement       = eZTemplateNodeTool::extractFunctionNodePlacement( $node );
00121         $uniqid              =  md5( $nodePlacement[2] ) . "_" . $tpl->ForeachCounter;
00122 
00123         require_once( 'lib/eztemplate/classes/eztemplatecompiledloop.php' );
00124         $loop = new eZTemplateCompiledLoop( eZTemplateForeachFunction::FUNCTION_NAME,
00125                                             $newNodes, $parameters, $nodePlacement, $uniqid,
00126                                             $node, $tpl, $privateData );
00127 
00128 
00129 
00130         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "// foreach begins" );
00131 
00132         $loop->initVars();
00133 
00134         $array           = "fe_array_$uniqid";
00135         $arrayKeys       = "fe_array_keys_$uniqid";
00136         $nItems          = "fe_n_items_$uniqid";
00137         $nItemsProcessed = "fe_n_items_processed_$uniqid";
00138         $i               = "fe_i_$uniqid";
00139         $key             = "fe_key_$uniqid";
00140         $val             = "fe_val_$uniqid";
00141         $offset          = "fe_offset_$uniqid";
00142         $max             = "fe_max_$uniqid";
00143         $reverse         = "fe_reverse_$uniqid";
00144         $firstVal        = "fe_first_val_$uniqid";
00145         $lastVal         = "fe_last_val_$uniqid";
00146 
00147         $variableStack   = "fe_variable_stack_$uniqid";
00148         $namesArray = array( $array, $arrayKeys, $nItems, $nItemsProcessed, $i, $key, $val, $offset, $max, $reverse, $firstVal, $lastVal );
00149 
00150         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "if ( !isset( \$$variableStack ) ) \$$variableStack = array();" );
00151         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$" . $variableStack ."[] = compact( '" . implode( "', '", $namesArray ) . "' );" );
00152 
00153         $newNodes[] = eZTemplateNodeTool::createVariableNode( false, $parameters['array'], $nodePlacement, array( 'text-result' => false ), $array );
00154         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$arrayKeys = is_array( \$$array ) ? array_keys( \$$array ) : array();" );
00155         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$nItems = count( \$$arrayKeys );" );
00156         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$nItemsProcessed = 0;" );
00157 
00158 
00159         // process offset, max and reverse parameters
00160         if ( isset( $parameters['offset'] ) )
00161             $newNodes[] = eZTemplateNodeTool::createVariableNode( false, $parameters['offset'], $nodePlacement, array( 'text-result' => false ), $offset );
00162         else
00163             $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$offset = 0;" );
00164 
00165         if ( isset( $parameters['max'] ) )
00166             $newNodes[] = eZTemplateNodeTool::createVariableNode( false, $parameters['max'], $nodePlacement, array( 'text-result' => false ), $max );
00167         else
00168             $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$max = \$$nItems - \$$offset;" );
00169 
00170         if ( isset( $parameters['reverse'] ) )
00171             $newNodes[] = eZTemplateNodeTool::createVariableNode( false, $parameters['reverse'], $nodePlacement, array( 'text-result' => false ), $reverse );
00172         else
00173             $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$reverse = false;" );
00174 
00175 
00176         // fix definitely incorrect offset
00177         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "if ( \$$offset < 0 || \$$offset >= \$$nItems )\n{\n".
00178                                                                "    \$$offset = ( \$$offset < 0 ) ? 0 : \$$nItems;\n".
00179                                                                "    if ( \$$nItems || \$$offset < 0 )\n {\n".
00180                                                                "        eZDebug::writeWarning(\"Invalid 'offset' parameter specified.\");   \n}\n}" );
00181         // fix definitely incorrect max
00182         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "if ( \$$max < 0 || \$$offset + \$$max > \$$nItems )\n{\n".
00183                                                                "    if ( \$$max < 0 )\n eZDebug::writeWarning(\"Invalid 'max' parameter specified.\");\n".
00184                                                                "    \$$max = \$$nItems - \$$offset;\n}" );
00185 
00186         // initialize first and last indexes to iterate between them
00187         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "if ( \$$reverse )\n" .
00188                                                                "{\n" .
00189                                                                "    \$$firstVal = \$$nItems - 1 - \$$offset;\n" .
00190                                                                "    \$$lastVal  = 0;\n" .
00191                                                                "}\n" .
00192                                                                "else\n" .
00193                                                                "{\n" .
00194                                                                "    \$$firstVal = \$$offset;\n" .
00195                                                                "    \$$lastVal  = \$$nItems - 1;\n" .
00196                                                                "}" );
00197 
00198         // generate loop header
00199         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "// foreach" );
00200         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "for ( \$$i = \$$firstVal; " .
00201                                                                "\$$nItemsProcessed < \$$max && ( \$$reverse ? \$$i >= \$$lastVal : \$$i <= \$$lastVal ); " .
00202                                                                "\$$reverse ? \$$i-- : \$$i++ )\n" .
00203                                                                "{" );
00204         $newNodes[] = eZTemplateNodeTool::createSpacingIncreaseNode();
00205 
00206         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$key = \$${arrayKeys}[\$$i];" );
00207         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$val = \$${array}[\$$key];" );
00208 
00209         // export $itemVar
00210         $newNodes[] = eZTemplateNodeTool::createVariableNode( false, "$val", $nodePlacement, array(),
00211                                                               $parameters['item_var'][0][1],
00212                                                               false, true, true );
00213 
00214         // export $keyVar (if specified)
00215         if ( isset( $parameters['key_var'] ) )
00216         {
00217             $newNodes[] = eZTemplateNodeTool::createVariableNode( false, "$key", $nodePlacement, array(),
00218                                                                   $parameters['key_var'][0][1],
00219                                                                   false, true, true );
00220         }
00221 
00222         $loop->processBody();
00223 
00224         // generate loop footer
00225         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "\$$nItemsProcessed++;" );
00226         $newNodes[] = eZTemplateNodeTool::createSpacingDecreaseNode();
00227         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "} // foreach" );
00228 
00229         $loop->cleanup();
00230 
00231         // unset the loop variables
00232         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "if ( count( \$$variableStack ) ) extract( array_pop( \$$variableStack ) );\n" );
00233         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "else\n{\n" );
00234         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $array );
00235         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $arrayKeys );
00236         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $nItems );
00237         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $nItemsProcessed );
00238         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $i );
00239         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $key );
00240         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $val );
00241         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $offset );
00242         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $max );
00243         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $reverse );
00244         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $firstVal );
00245         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $lastVal );
00246 
00247         $newNodes[] = eZTemplateNodeTool::createVariableUnsetNode( $variableStack );
00248         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "}\n" );
00249 
00250         $newNodes[] = eZTemplateNodeTool::createCodePieceNode( "// foreach ends" );
00251 
00252         return $newNodes;
00253     }
00254 
00255     /*!
00256      * Actually executes the function and its children (in processed mode).
00257      */
00258     function process( $tpl, &$textElements, $functionName, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace )
00259     {
00260         /*
00261         {foreach <array> as [$keyVar =>] $itemVar
00262                  [sequence <array> as $sequenceVar]
00263                  [offset <offset>]
00264                  [max <max>]
00265                  [reverse]
00266         }
00267         */
00268 
00269         //eZDebug::writeDebug( $functionParameters, '$functionParameters' );
00270 
00271         require_once( 'lib/eztemplate/classes/eztemplateloop.php' );
00272         $loop = new eZTemplateLoop( eZTemplateForeachFunction::FUNCTION_NAME,
00273                                     $functionParameters, $functionChildren, $functionPlacement,
00274                                     $tpl, $textElements, $rootNamespace, $currentNamespace );
00275 
00276         if ( !$loop->initialized() )
00277             return;
00278 
00279         $loop->parseParamValue( 'array', $array );
00280         if ( !is_array( $array ) )
00281         {
00282             $tpl->error( eZTemplateForeachFunction::FUNCTION_NAME, "Missing/malformed array to iterate through.", $functionPlacement );
00283             return;
00284         }
00285 
00286         $loop->parseParamVarName( 'item_var', $itemVarName );
00287         if ( !$itemVarName )
00288         {
00289             $tpl->error( eZTemplateForeachFunction::FUNCTION_NAME, "Missing/malformed item variable name.", $functionPlacement );
00290             return;
00291         }
00292 
00293         $loop->parseParamVarName( 'key_var', $keyVarName );
00294         $loop->parseParamValue( 'max',      $max     );
00295         $loop->parseParamValue( 'offset',   $offset  );
00296         $loop->parseParamValue( 'reverse',  $reverse );
00297 
00298         /*
00299          * run the loop itself
00300          */
00301 
00302         /*
00303             $offset and $max parameters must meet the following requirements:
00304              -  $offset + $max <= $nItems
00305              -  $offset >= 0
00306              -  $max    >= 0
00307             Otherwise they are not considered.
00308         */
00309 
00310         $arrayKeys       = array_keys( $array );
00311         $nItems          = count( $arrayKeys );
00312         $nItemsProcessed = 0;
00313 
00314         // do nothing in case of empty array
00315         if ( !$nItems )
00316             return;
00317 
00318         // fix definitely incorrect offset
00319         if ( is_null( $offset ) )
00320             $offset = 0;
00321         elseif ( $offset < 0 || $offset >= $nItems )
00322         {
00323             if ( $nItems || $offset < 0 )
00324             {
00325                 $tpl->warning( eZTemplateForeachFunction::FUNCTION_NAME, "Invalid 'offset' parameter specified.", $functionPlacement );
00326             }
00327             $offset = ( $offset < 0 ) ? 0 : $nItems;
00328         }
00329 
00330         // fix definitely incorrect max
00331         if ( is_null( $max ) )
00332             $max = $nItems - $offset;
00333         elseif ( $max < 0 || $offset+$max > $nItems )
00334         {
00335             if ( $max <0 )
00336                 $tpl->warning( eZTemplateForeachFunction::FUNCTION_NAME, "Invalid 'max' parameter specified.", $functionPlacement );
00337             $max = $nItems - $offset;
00338         }
00339 
00340         // process 'reverse' parameter
00341         if ( is_null( $reverse ) )
00342             $reverse = false;
00343         if ( $reverse )
00344         {
00345             $firstVal = $nItems - 1 - $offset;
00346             $lastVal  = 0;
00347         }
00348         else
00349         {
00350             $firstVal = $offset;
00351             $lastVal  = $nItems - 1;
00352         }
00353 
00354         if ( $firstVal < $lastVal )
00355         {
00356             if ( $keyVarName && !$tpl->hasVariable( $keyVarName, $rootNamespace ) )
00357             {
00358                 $loop->initLoopVariable( $keyVarName );
00359             }
00360             if ( !$tpl->hasVariable( $itemVarName, $rootNamespace ) )
00361             {
00362                 $loop->initLoopVariable( $itemVarName );
00363             }
00364         }
00365 
00366         for ( $i = $firstVal; $nItemsProcessed < $max && ( $reverse ? $i >= $lastVal : $i <= $lastVal ); )
00367         {
00368             $key =& $arrayKeys[$i];
00369             $val =& $array[$key];
00370 
00371             if ( $keyVarName )
00372                 $tpl->setVariable( $keyVarName, $key, $rootNamespace );
00373             $tpl->setVariable( $itemVarName, $val, $rootNamespace );
00374 
00375             $loop->setSequenceVar(); // set sequence variable (if specified)
00376             $loop->processDelimiter( $i );
00377             $loop->resetIteration();
00378 
00379             // process loop body
00380             if ( $loop->processChildren() )
00381                 break;
00382 
00383             // increment loop counter here for delimiter to be processed correctly
00384             $reverse ? $i-- : $i++;
00385 
00386             $loop->incrementSequence();
00387             $nItemsProcessed++;
00388         }
00389 
00390         $loop->cleanup();
00391     }
00392 
00393     /*!
00394      * Returns true, telling the template parser that the function can have children.
00395      */
00396     function hasChildren()
00397     {
00398         return true;
00399     }
00400 }
00401 
00402 ?>