PageRenderTime 65ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/eztemplate/classes/eztemplate.php

https://github.com/aurelienRT1/ezpublish
PHP | 2702 lines | 1817 code | 229 blank | 656 comment | 258 complexity | c66ecac8902c70f99ed364aa4b2d2e3e MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. //
  3. // Definition of eZTemplate class
  4. //
  5. // Created on: <01-Mar-2002 13:49:57 amos>
  6. //
  7. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  8. // SOFTWARE NAME: eZ Publish
  9. // SOFTWARE RELEASE: 4.1.x
  10. // COPYRIGHT NOTICE: Copyright (C) 1999-2010 eZ Systems AS
  11. // SOFTWARE LICENSE: GNU General Public License v2.0
  12. // NOTICE: >
  13. // This program is free software; you can redistribute it and/or
  14. // modify it under the terms of version 2.0 of the GNU General
  15. // Public License as published by the Free Software Foundation.
  16. //
  17. // This program is distributed in the hope that it will be useful,
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. // GNU General Public License for more details.
  21. //
  22. // You should have received a copy of version 2.0 of the GNU General
  23. // Public License along with this program; if not, write to the Free
  24. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  25. // MA 02110-1301, USA.
  26. //
  27. //
  28. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  29. //
  30. /*! \file
  31. Template system manager.
  32. */
  33. /*! \defgroup eZTemplate Template system */
  34. /*!
  35. \class eZTemplate eztemplate.php
  36. \ingroup eZTemplate
  37. \brief The main manager for templates
  38. The template systems allows for separation of code and
  39. layout by moving the layout part into template files. These
  40. template files are parsed and processed with template variables set
  41. by the PHP code.
  42. The template system in itself is does not do much, it parses template files
  43. according to a rule set sets up a tree hierarchy and process the data
  44. using functions and operators. The standard template system comes with only
  45. a few functions and no operators, it is meant for these functions and operators
  46. to be specified by the users of the template system. But for simplicity a few
  47. help classes is available which can be easily enabled.
  48. The classes are:
  49. - eZTemplateDelimitFunction - Inserts the left and right delimiter which are normally parsed.
  50. - eZTemplateSectionFunction - Allows for conditional blocks and loops.
  51. - eZTemplateIncludeFunction - Includes external templates
  52. - eZTemplateSequenceFunction - Creates sequences arrays
  53. - eZTemplateSwitchFunction - Conditional output of template
  54. - eZTemplatePHPOperator - Allows for easy redirection of operator names to PHP functions.
  55. - eZTemplateLocaleOperator - Allows for locale conversions.
  56. - eZTemplateArrayOperator - Creates arrays
  57. - eZTemplateAttributeOperator - Displays contents of template variables, useful for debugging
  58. - eZTemplateImageOperator - Converts text to image
  59. - eZTemplateLogicOperator - Various logical operators for boolean handling
  60. - eZTemplateUnitOperator - Unit conversion and display
  61. To enable these functions and operator use registerFunction and registerOperator.
  62. In keeping with the spirit of being simple the template system does not know how
  63. to get the template files itself. Instead it relies on resource handlers, these
  64. handlers fetches the template files using different kind of transport mechanism.
  65. For simplicity a default resource class is available, eZTemplateFileResource fetches
  66. templates from the filesystem.
  67. The parser process consists of three passes, each pass adds a new level of complexity.
  68. The first pass strips text from template blocks which starts with a left delimiter and
  69. ends with a right delimiter (default is { and } ), and places them in an array.
  70. The second pass iterates the text and block elements and removes newlines from
  71. text before function blocks and text after function blocks.
  72. The third pass builds the tree according the function rules.
  73. Processing is done by iterating over the root of the tree, if a text block is found
  74. the text is appended to the result text. If a variable or contant is it's data is extracted
  75. and any operators found are run on it before fetching the result and appending it to
  76. the result text. If a function is found the function is called with the parameters
  77. and it's up to the function handle children if any.
  78. Constants and template variables will usually be called variables since there's little
  79. difference. A template variable expression will start with a $ and consists of a
  80. namespace (optional) a name and attribues(optional). The variable expression
  81. \verbatim $root:var.attr1 \endverbatim exists in the "root" namespace, has the name "var" and uses the
  82. attribute "attr1". Some functions will create variables on demand, to avoid name conflicts
  83. namespaces were introduced, each function will place the new variables in a namespace
  84. specified in the template file. Attribues are used for fetching parts of the variable,
  85. for instance an element in an array or data in an object. Since the syntax is the
  86. same for arrays and objects the PHP code can use simple arrays when speed is required,
  87. the template code will not care.
  88. A different syntax is also available when you want to access an attribute using a variable.
  89. For instance \verbatim $root:var[$attr_var] \endverbatim, if the variable $attr_var contains "attr1" it would
  90. access the same attribute as in the first example.
  91. The syntax for operators is a | and a name, optionally parameters can be specified with
  92. ( and ) delimited with ,. Valid operators are \verbatim |upcase, |l10n(date) \endverbatim.
  93. Functions look a lot like HTML/XML tags. The function consists of a name and parameters
  94. which are assigned using the param=value syntax. Some parameters may be required while
  95. others may be optionally, the exact behaviour is specified by each function.
  96. Valid functions are \verbatim "section name=abc loop=4" \endverbatim
  97. Example of usage:
  98. \code
  99. // Init template
  100. $tpl = eZTemplate::instance();
  101. $tpl->registerOperators( new eZTemplatePHPOperator( array( "upcase" => "strtoupper",
  102. "reverse" => "strrev" ) ) );
  103. $tpl->registerOperators( new eZTemplateLocaleOperator() );
  104. $tpl->registerFunction( "section", new eZTemplateSectionFunction( "section" ) );
  105. $tpl->registerFunctions( new eZTemplateDelimitFunction() );
  106. $tpl->setVariable( "my_var", "{this value set by variable}", "test" );
  107. $tpl->setVariable( "my_arr", array( "1st", "2nd", "third", "fjerde" ) );
  108. $tpl->setVariable( "multidim", array( array( "a", "b" ),
  109. array( "c", "d" ),
  110. array( "e", "f" ),
  111. array( "g", "h" ) ) );
  112. class mytest
  113. {
  114. function mytest( $n, $s )
  115. {
  116. $this->n = $n;
  117. $this->s = $s;
  118. }
  119. function hasAttribute( $attr )
  120. {
  121. return ( $attr == "name" || $attr == "size" );
  122. }
  123. function attribute( $attr )
  124. {
  125. switch ( $attr )
  126. {
  127. case "name";
  128. return $this->n;
  129. case "size";
  130. return $this->s;
  131. default:
  132. $retAttr = null;
  133. return $retAttr;
  134. }
  135. }
  136. };
  137. $tpl->setVariable( "multidim_obj", array( new mytest( "jan", 200 ),
  138. new mytest( "feb", 200 ),
  139. new mytest( "john", 200 ),
  140. new mytest( "doe", 50 ) ) );
  141. $tpl->setVariable( "curdate", time() );
  142. $tpl->display( "lib/eztemplate/example/test.tpl" );
  143. // test.tpl
  144. {section name=outer loop=4}
  145. 123
  146. {delimit}::{/delimit}
  147. {/section}
  148. {literal test=1} This is some {blah arg1="" arg2="abc" /} {/literal}
  149. <title>This is a test</title>
  150. <table border="1">
  151. <tr><th>{$test:my_var}
  152. {"some text!!!"|upcase|reverse}</th></tr>
  153. {section name=abc loop=$my_arr}
  154. <tr><td>{$abc:item}</td></tr>
  155. {/section}
  156. </table>
  157. <table border="1">
  158. {section name=outer loop=$multidim}
  159. <tr>
  160. {section name=inner loop=$outer:item}
  161. <td>{$inner:item}</td>
  162. {/section}
  163. </tr>
  164. {/section}
  165. </table>
  166. <table border="1">
  167. {section name=outer loop=$multidim_obj}
  168. <tr>
  169. <td>{$outer:item.name}</td>
  170. <td>{$outer:item.size}</td>
  171. </tr>
  172. {/section}
  173. </table>
  174. {section name=outer loop=$nonexistingvar}
  175. <b><i>Dette skal ikke vises</b></i>
  176. {section-else}
  177. <b><i>This is shown when the {ldelim}$loop{rdelim} variable is non-existant</b></i>
  178. {/section}
  179. Denne koster {1.4|l10n(currency)}<br>
  180. {-123456789|l10n(number)}<br>
  181. {$curdate|l10n(date)}<br>
  182. {$curdate|l10n(shortdate)}<br>
  183. {$curdate|l10n(time)}<br>
  184. {$curdate|l10n(shorttime)}<br>
  185. {include file="test2.tpl"/}
  186. \endcode
  187. */
  188. class eZTemplate
  189. {
  190. const RESOURCE_FETCH = 1;
  191. const RESOURCE_QUERY = 2;
  192. const ELEMENT_TEXT = 1;
  193. const ELEMENT_SINGLE_TAG = 2;
  194. const ELEMENT_NORMAL_TAG = 3;
  195. const ELEMENT_END_TAG = 4;
  196. const ELEMENT_VARIABLE = 5;
  197. const ELEMENT_COMMENT = 6;
  198. const NODE_ROOT = 1;
  199. const NODE_TEXT = 2;
  200. const NODE_VARIABLE = 3;
  201. const NODE_FUNCTION = 4;
  202. const NODE_OPERATOR = 5;
  203. const NODE_INTERNAL = 100;
  204. const NODE_INTERNAL_CODE_PIECE = 101;
  205. const NODE_INTERNAL_VARIABLE_SET = 105;
  206. const NODE_INTERNAL_VARIABLE_UNSET = 102;
  207. const NODE_INTERNAL_NAMESPACE_CHANGE = 103;
  208. const NODE_INTERNAL_NAMESPACE_RESTORE = 104;
  209. const NODE_INTERNAL_WARNING = 120;
  210. const NODE_INTERNAL_ERROR = 121;
  211. const NODE_INTERNAL_RESOURCE_ACQUISITION = 140;
  212. const NODE_OPTIMIZED_RESOURCE_ACQUISITION = 141;
  213. const NODE_INTERNAL_OUTPUT_ASSIGN = 150;
  214. const NODE_INTERNAL_OUTPUT_READ = 151;
  215. const NODE_INTERNAL_OUTPUT_INCREASE = 152;
  216. const NODE_INTERNAL_OUTPUT_DECREASE = 153;
  217. const NODE_INTERNAL_OUTPUT_SPACING_INCREASE = 160;
  218. const NODE_INTERNAL_SPACING_DECREASE = 161;
  219. const NODE_OPTIMIZED_INIT = 201;
  220. const NODE_USER_CUSTOM = 1000;
  221. const TYPE_VOID = 0;
  222. const TYPE_STRING = 1;
  223. const TYPE_NUMERIC = 2;
  224. const TYPE_IDENTIFIER = 3;
  225. const TYPE_VARIABLE = 4;
  226. const TYPE_ATTRIBUTE = 5;
  227. const TYPE_OPERATOR = 6;
  228. const TYPE_BOOLEAN = 7;
  229. const TYPE_ARRAY = 8;
  230. const TYPE_DYNAMIC_ARRAY = 9;
  231. const TYPE_INTERNAL = 100;
  232. const TYPE_INTERNAL_CODE_PIECE = 101;
  233. const TYPE_PHP_VARIABLE = 102;
  234. const TYPE_OPTIMIZED_NODE = 201;
  235. const TYPE_OPTIMIZED_ARRAY_LOOKUP = 202;
  236. const TYPE_OPTIMIZED_CONTENT_CALL = 203;
  237. const TYPE_OPTIMIZED_ATTRIBUTE_LOOKUP = 204;
  238. const TYPE_INTERNAL_STOP = 999;
  239. const TYPE_STRING_BIT = 1;
  240. const TYPE_NUMERIC_BIT = 2;
  241. const TYPE_IDENTIFIER_BIT = 4;
  242. const TYPE_VARIABLE_BIT = 8;
  243. const TYPE_ATTRIBUTE_BIT = 16;
  244. const TYPE_OPERATOR_BIT = 32;
  245. const TYPE_NONE = 0;
  246. const TYPE_ALL = 63;
  247. const TYPE_BASIC = 47;
  248. const TYPE_MODIFIER_MASK = 48;
  249. const NAMESPACE_SCOPE_GLOBAL = 1;
  250. const NAMESPACE_SCOPE_LOCAL = 2;
  251. const NAMESPACE_SCOPE_RELATIVE = 3;
  252. const DEBUG_INTERNALS = false;
  253. const FILE_ERRORS = 1;
  254. /*!
  255. Intializes the template with left and right delimiters being { and },
  256. and a file resource. The literal tag "literal" is also registered.
  257. */
  258. function eZTemplate()
  259. {
  260. $this->Tree = array( eZTemplate::NODE_ROOT, false );
  261. $this->LDelim = "{";
  262. $this->RDelim = "}";
  263. $this->IncludeText = array();
  264. $this->IncludeOutput = array();
  265. $this->registerLiteral( "literal" );
  266. $res = new eZTemplateFileResource();
  267. $this->DefaultResource = $res;
  268. $this->registerResource( $res );
  269. $this->Resources = array();
  270. $this->Text = null;
  271. $this->IsCachingAllowed = true;
  272. $this->resetErrorLog();
  273. $this->AutoloadPathList = array( 'lib/eztemplate/classes/' );
  274. $this->Variables = array();
  275. $this->LocalVariablesNamesStack = array();
  276. $this->CurrentLocalVariablesNames = null;
  277. $this->Functions = array();
  278. $this->FunctionAttributes = array();
  279. $this->TestCompile = false;
  280. $ini = eZINI::instance( 'template.ini' );
  281. if ( $ini->hasVariable( 'ControlSettings', 'MaxLevel' ) )
  282. $this->MaxLevel = $ini->variable( 'ControlSettings', 'MaxLevel' );
  283. $this->MaxLevelWarning = ezpI18n::tr( 'lib/template',
  284. 'The maximum nesting level of %max has been reached. The execution is stopped to avoid infinite recursion.',
  285. '',
  286. array( '%max' => $this->MaxLevel ) );
  287. eZDebug::createAccumulatorGroup( 'template_total', 'Template Total' );
  288. $this->TemplatesUsageStatistics = array();
  289. // Array of templates which are used in a single fetch()
  290. $this->TemplateFetchList = array();
  291. $this->ForeachCounter = 0;
  292. $this->ForCounter = 0;
  293. $this->WhileCounter = 0;
  294. $this->DoCounter = 0;
  295. $this->ElseifCounter = 0;
  296. }
  297. /*!
  298. Returns the left delimiter being used.
  299. */
  300. function leftDelimiter()
  301. {
  302. return $this->LDelim;
  303. }
  304. /*!
  305. Returns the right delimiter being used.
  306. */
  307. function rightDelimiter()
  308. {
  309. return $this->RDelim;
  310. }
  311. /*!
  312. Sets the left delimiter.
  313. */
  314. function setLeftDelimiter( $delim )
  315. {
  316. $this->LDelim = $delim;
  317. }
  318. /*!
  319. Sets the right delimiter.
  320. */
  321. function setRightDelimiter( $delim )
  322. {
  323. $this->RDelim = $delim;
  324. }
  325. /*!
  326. Fetches the result of the template file and displays it.
  327. If $template is supplied it will load this template file first.
  328. */
  329. function display( $template = false, $extraParameters = false )
  330. {
  331. $output = $this->fetch( $template, $extraParameters );
  332. if ( $this->ShowDetails )
  333. {
  334. echo '<h1>Result:</h1>' . "\n";
  335. echo '<hr/>' . "\n";
  336. }
  337. echo "$output";
  338. if ( $this->ShowDetails )
  339. {
  340. echo '<hr/>' . "\n";
  341. }
  342. if ( $this->ShowDetails )
  343. {
  344. echo "<h1>Template data:</h1>";
  345. echo "<p class=\"filename\">" . $template . "</p>";
  346. echo "<pre class=\"example\">" . htmlspecialchars( $this->Text ) . "</pre>";
  347. reset( $this->IncludeText );
  348. while ( ( $key = key( $this->IncludeText ) ) !== null )
  349. {
  350. $item = $this->IncludeText[$key];
  351. echo "<p class=\"filename\">" . $key . "</p>";
  352. echo "<pre class=\"example\">" . htmlspecialchars( $item ) . "</pre>";
  353. next( $this->IncludeText );
  354. }
  355. echo "<h1>Result text:</h1>";
  356. echo "<p class=\"filename\">" . $template . "</p>";
  357. echo "<pre class=\"example\">" . htmlspecialchars( $output ) . "</pre>";
  358. reset( $this->IncludeOutput );
  359. while ( ( $key = key( $this->IncludeOutput ) ) !== null )
  360. {
  361. $item = $this->IncludeOutput[$key];
  362. echo "<p class=\"filename\">" . $key . "</p>";
  363. echo "<pre class=\"example\">" . htmlspecialchars( $item ) . "</pre>";
  364. next( $this->IncludeOutput );
  365. }
  366. }
  367. }
  368. /*!
  369. * Initialize list of local variables for the current template.
  370. * The list contains only names of variables.
  371. */
  372. function createLocalVariablesList()
  373. {
  374. $this->LocalVariablesNamesStack[] = array();
  375. $this->CurrentLocalVariablesNames =& $this->LocalVariablesNamesStack[ count( $this->LocalVariablesNamesStack ) - 1];
  376. }
  377. /*!
  378. * Check if the given local variable exists.
  379. */
  380. function hasLocalVariable( $varName, $rootNamespace )
  381. {
  382. return ( array_key_exists( $rootNamespace, $this->CurrentLocalVariablesNames ) &&
  383. array_key_exists( $varName, $this->CurrentLocalVariablesNames[$rootNamespace] ) );
  384. }
  385. /*!
  386. * Create a local variable.
  387. */
  388. function setLocalVariable( $varName, $varValue, $rootNamespace )
  389. {
  390. $this->CurrentLocalVariablesNames[$rootNamespace][$varName] = 1;
  391. $this->setVariable( $varName, $varValue, $rootNamespace );
  392. }
  393. /*!
  394. * Destroy a local variable.
  395. */
  396. function unsetLocalVariable( $varName, $rootNamespace )
  397. {
  398. if ( !$this->hasLocalVariable( $varName, $rootNamespace ) )
  399. return;
  400. $this->unsetVariable( $varName, $rootNamespace );
  401. unset( $this->CurrentLocalVariablesNames[$rootNamespace][$varName] );
  402. }
  403. /*!
  404. * Destroy all local variables defined in the current template.
  405. */
  406. function unsetLocalVariables()
  407. {
  408. foreach ( $this->CurrentLocalVariablesNames as $ns => $vars )
  409. {
  410. foreach ( $vars as $var => $val )
  411. $this->unsetLocalVariable( $var, $ns );
  412. }
  413. }
  414. /*!
  415. * Destroy list of local variables defined in the current (innermost) template.
  416. */
  417. function destroyLocalVariablesList()
  418. {
  419. array_pop( $this->LocalVariablesNamesStack );
  420. if ( $this->LocalVariablesNamesStack )
  421. $this->CurrentLocalVariablesNames =& $this->LocalVariablesNamesStack[ count( $this->LocalVariablesNamesStack ) - 1];
  422. else
  423. unset( $this->CurrentLocalVariablesNames );
  424. }
  425. /*!
  426. Tries to fetch the result of the template file and returns it.
  427. If $template is supplied it will load this template file first.
  428. */
  429. function fetch( $template = false, $extraParameters = false, $returnResourceData = false )
  430. {
  431. $this->resetErrorLog();
  432. // Reset fetch list when a new fetch is started
  433. $this->TemplateFetchList = array();
  434. eZDebug::accumulatorStart( 'template_total' );
  435. eZDebug::accumulatorStart( 'template_load', 'template_total', 'Template load' );
  436. $root = null;
  437. if ( is_string( $template ) )
  438. {
  439. $resourceData = $this->loadURIRoot( $template, true, $extraParameters );
  440. if ( $resourceData and
  441. $resourceData['root-node'] !== null )
  442. $root =& $resourceData['root-node'];
  443. }
  444. eZDebug::accumulatorStop( 'template_load' );
  445. if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
  446. {
  447. $savedLocale = setlocale( LC_CTYPE, null );
  448. setlocale( LC_CTYPE, $resourceData['locales'] );
  449. }
  450. $text = "";
  451. if ( $root !== null or
  452. $resourceData['compiled-template'] )
  453. {
  454. if ( $this->ShowDetails )
  455. eZDebug::addTimingPoint( "Process" );
  456. eZDebug::accumulatorStart( 'template_processing', 'template_total', 'Template processing' );
  457. $templateCompilationUsed = false;
  458. if ( $resourceData['compiled-template'] )
  459. {
  460. $textElements = array();
  461. if ( $this->executeCompiledTemplate( $resourceData, $textElements, "", "", $extraParameters ) )
  462. {
  463. $text = implode( '', $textElements );
  464. $templateCompilationUsed = true;
  465. }
  466. }
  467. if ( !$templateCompilationUsed )
  468. {
  469. if ( eZTemplate::isDebugEnabled() )
  470. {
  471. $fname = $resourceData['template-filename'];
  472. eZDebug::writeDebug( "FETCH START URI: $template, $fname" );
  473. }
  474. $this->process( $root, $text, "", "" );
  475. if ( eZTemplate::isDebugEnabled() )
  476. eZDebug::writeDebug( "FETCH END URI: $template, $fname" );
  477. }
  478. eZDebug::accumulatorStop( 'template_processing' );
  479. if ( $this->ShowDetails )
  480. eZDebug::addTimingPoint( "Process done" );
  481. }
  482. eZDebug::accumulatorStop( 'template_total' );
  483. if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
  484. {
  485. setlocale( LC_CTYPE, $savedLocale );
  486. }
  487. if ( $returnResourceData )
  488. {
  489. $resourceData['result_text'] = $text;
  490. return $resourceData;
  491. }
  492. return $text;
  493. }
  494. function process( $root, &$text, $rootNamespace, $currentNamespace )
  495. {
  496. $this->createLocalVariablesList();
  497. $textElements = array();
  498. $this->processNode( $root, $textElements, $rootNamespace, $currentNamespace );
  499. if ( is_array( $textElements ) )
  500. $text = implode( '', $textElements );
  501. else
  502. $text = $textElements;
  503. $this->unsetLocalVariables();
  504. $this->destroyLocalVariablesList();
  505. }
  506. function processNode( $node, &$textElements, $rootNamespace, $currentNamespace )
  507. {
  508. $rslt = null;
  509. $nodeType = $node[0];
  510. if ( $nodeType == eZTemplate::NODE_ROOT )
  511. {
  512. $children = $node[1];
  513. if ( $children )
  514. {
  515. foreach ( $children as $child )
  516. {
  517. $this->processNode( $child, $textElements, $rootNamespace, $currentNamespace );
  518. if ( !is_array( $textElements ) )
  519. eZDebug::writeError( "Textelements is no longer array: '$textElements'",
  520. 'eztemplate::processNode::root' );
  521. }
  522. }
  523. }
  524. else if ( $nodeType == eZTemplate::NODE_TEXT )
  525. {
  526. $textElements[] = $node[2];
  527. }
  528. else if ( $nodeType == eZTemplate::NODE_VARIABLE )
  529. {
  530. $variableData = $node[2];
  531. $variablePlacement = $node[3];
  532. $rslt = $this->processVariable( $textElements, $variableData, $variablePlacement, $rootNamespace, $currentNamespace );
  533. if ( !is_array( $textElements ) )
  534. eZDebug::writeError( "Textelements is no longer array: '$textElements'",
  535. 'eztemplate::processNode::variable' );
  536. }
  537. else if ( $nodeType == eZTemplate::NODE_FUNCTION )
  538. {
  539. $functionChildren = $node[1];
  540. $functionName = $node[2];
  541. $functionParameters = $node[3];
  542. $functionPlacement = $node[4];
  543. $rslt = $this->processFunction( $functionName, $textElements, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace );
  544. if ( !is_array( $textElements ) )
  545. eZDebug::writeError( "Textelements is no longer array: '$textElements'",
  546. "eztemplate::processNode::function( '$functionName' )" );
  547. }
  548. return $rslt;
  549. }
  550. function processVariable( &$textElements, $variableData, $variablePlacement, $rootNamespace, $currentNamespace )
  551. {
  552. $value = $this->elementValue( $variableData, $rootNamespace, $currentNamespace, $variablePlacement );
  553. $this->appendElementText( $textElements, $value, $rootNamespace, $currentNamespace );
  554. }
  555. function processFunction( $functionName, &$textElements, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace )
  556. {
  557. // Note: This code piece is replicated in the eZTemplateCompiler,
  558. // if this code is changed the replicated code must be updated as well.
  559. $func = $this->Functions[$functionName];
  560. if ( is_array( $func ) )
  561. {
  562. $this->loadAndRegisterFunctions( $this->Functions[$functionName] );
  563. $func = $this->Functions[$functionName];
  564. }
  565. if ( isset( $func ) and
  566. is_object( $func ) )
  567. {
  568. if ( eZTemplate::isMethodDebugEnabled() )
  569. eZDebug::writeDebug( "START FUNCTION: $functionName" );
  570. $value = $func->process( $this, $textElements, $functionName, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace );
  571. if ( eZTemplate::isMethodDebugEnabled() )
  572. eZDebug::writeDebug( "END FUNCTION: $functionName" );
  573. return $value;
  574. }
  575. else
  576. {
  577. $this->warning( "", "Function \"$functionName\" is not registered" );
  578. }
  579. }
  580. function fetchFunctionObject( $functionName )
  581. {
  582. $func = $this->Functions[$functionName];
  583. if ( is_array( $func ) )
  584. {
  585. $this->loadAndRegisterFunctions( $this->Functions[$functionName] );
  586. $func = $this->Functions[$functionName];
  587. }
  588. return $func;
  589. }
  590. /*!
  591. Loads the template using the URI $uri and parses it.
  592. \return The root node of the tree if \a $returnResourceData is false,
  593. if \c true the entire resource data structure.
  594. */
  595. function load( $uri, $extraParameters = false, $returnResourceData = false )
  596. {
  597. $resourceData = $this->loadURIRoot( $uri, true, $extraParameters );
  598. if ( !$resourceData or
  599. $resourceData['root-node'] === null )
  600. {
  601. $retValue = null;
  602. return $retValue;
  603. }
  604. else
  605. return $resourceData['root-node'];
  606. }
  607. function parse( $sourceText, &$rootElement, $rootNamespace, &$resourceData )
  608. {
  609. $parser = eZTemplateMultiPassParser::instance();
  610. $parser->parse( $this, $sourceText, $rootElement, $rootNamespace, $resourceData );
  611. }
  612. function loadURIData( $resourceObject, $uri, $resourceName, $template, &$extraParameters, $displayErrors = true )
  613. {
  614. $resourceData = $this->resourceData( $resourceObject, $uri, $resourceName, $template );
  615. $resourceData['text'] = null;
  616. $resourceData['root-node'] = null;
  617. $resourceData['compiled-template'] = false;
  618. $resourceData['time-stamp'] = null;
  619. $resourceData['key-data'] = null;
  620. $resourceData['locales'] = null;
  621. if ( !$resourceObject->handleResource( $this, $resourceData, eZTemplate::RESOURCE_FETCH, $extraParameters ) )
  622. {
  623. $resourceData = null;
  624. if ( $displayErrors )
  625. $this->warning( "", "No template could be loaded for \"$template\" using resource \"$resourceName\"" );
  626. }
  627. return $resourceData;
  628. }
  629. /*!
  630. \static
  631. Creates a resource data structure of the parameters and returns it.
  632. This structure is passed to various parts of the template system.
  633. \note If you only have the URI you should call resourceFor() first to
  634. figure out the resource handler.
  635. */
  636. function resourceData( $resourceObject, $uri, $resourceName, $templateName )
  637. {
  638. $resourceData = array();
  639. $resourceData['uri'] = $uri;
  640. $resourceData['resource'] = $resourceName;
  641. $resourceData['template-name'] = $templateName;
  642. $resourceData['template-filename'] = $templateName;
  643. $resourceData['handler'] = $resourceObject;
  644. $resourceData['test-compile'] = $this->TestCompile;
  645. return $resourceData;
  646. }
  647. /*!
  648. Loads the template using the URI $uri and returns a structure with the text and timestamp,
  649. false otherwise.
  650. The structure keys are:
  651. - "text", the text.
  652. - "time-stamp", the timestamp.
  653. */
  654. function loadURIRoot( $uri, $displayErrors = true, &$extraParameters )
  655. {
  656. $res = "";
  657. $template = "";
  658. $resobj = $this->resourceFor( $uri, $res, $template );
  659. if ( !is_object( $resobj ) )
  660. {
  661. if ( $displayErrors )
  662. $this->warning( "", "No resource handler for \"$res\" and no default resource handler, aborting." );
  663. return null;
  664. }
  665. $canCache = true;
  666. if ( !$resobj->servesStaticData() )
  667. $canCache = false;
  668. if ( !$this->isCachingAllowed() )
  669. $canCache = false;
  670. $resourceData = $this->loadURIData( $resobj, $uri, $res, $template, $extraParameters, $displayErrors );
  671. if ( $resourceData )
  672. {
  673. $root = null;
  674. eZTemplate::appendTemplateToStatisticsIfNeeded( $resourceData['template-name'], $resourceData['template-filename'] );
  675. $this->appendTemplateFetch( $resourceData['template-filename'] );
  676. if ( !$resourceData['compiled-template'] and
  677. $resourceData['root-node'] === null )
  678. {
  679. $resourceData['root-node'] = array( eZTemplate::NODE_ROOT, false );
  680. $templateText = $resourceData["text"];
  681. $keyData = $resourceData['key-data'];
  682. $this->setIncludeText( $uri, $templateText );
  683. $rootNamespace = '';
  684. $this->parse( $templateText, $resourceData['root-node'], $rootNamespace, $resourceData );
  685. if ( eZTemplate::isDebugEnabled() )
  686. {
  687. $this->appendDebugNodes( $resourceData['root-node'], $resourceData );
  688. }
  689. if ( $canCache )
  690. $resobj->setCachedTemplateTree( $keyData, $uri, $res, $template, $extraParameters, $resourceData['root-node'] );
  691. }
  692. if ( !$resourceData['compiled-template'] and
  693. $canCache and
  694. $this->canCompileTemplate( $resourceData, $extraParameters ) )
  695. {
  696. $generateStatus = $this->compileTemplate( $resourceData, $extraParameters );
  697. if ( $generateStatus )
  698. $resourceData['compiled-template'] = true;
  699. }
  700. }
  701. return $resourceData;
  702. }
  703. function processURI( $uri, $displayErrors = true, &$extraParameters,
  704. &$textElements, $rootNamespace, $currentNamespace )
  705. {
  706. $this->Level++;
  707. if ( $this->Level > $this->MaxLevel )
  708. {
  709. eZDebug::writeError( $this->MaxLevelWarning, "eZTemplate:processURI Level: $this->Level @ $uri" );
  710. $textElements[] = $this->MaxLevelWarning;
  711. $this->Level--;
  712. return;
  713. }
  714. $resourceData = $this->loadURIRoot( $uri, $displayErrors, $extraParameters );
  715. if ( !$resourceData or
  716. ( !$resourceData['compiled-template'] and
  717. $resourceData['root-node'] === null ) )
  718. {
  719. $this->Level--;
  720. return;
  721. }
  722. $templateCompilationUsed = false;
  723. if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
  724. {
  725. $savedLocale = setlocale( LC_CTYPE, null );
  726. setlocale( LC_CTYPE, $resourceData['locales'] );
  727. }
  728. if ( $resourceData['compiled-template'] )
  729. {
  730. if ( $this->executeCompiledTemplate( $resourceData, $textElements, $rootNamespace, $currentNamespace, $extraParameters ) )
  731. $templateCompilationUsed = true;
  732. }
  733. if ( !$templateCompilationUsed )
  734. {
  735. $text = null;
  736. if ( eZTemplate::isDebugEnabled() )
  737. {
  738. $fname = $resourceData['template-filename'];
  739. eZDebug::writeDebug( "START URI: $uri, $fname" );
  740. }
  741. $this->process( $resourceData['root-node'], $text, $rootNamespace, $currentNamespace );
  742. if ( eZTemplate::isDebugEnabled() )
  743. eZDebug::writeDebug( "END URI: $uri, $fname" );
  744. $this->setIncludeOutput( $uri, $text );
  745. $textElements[] = $text;
  746. }
  747. if ( $resourceData['locales'] && !empty( $resourceData['locales'] ) )
  748. {
  749. setlocale( LC_CTYPE, $savedLocale );
  750. }
  751. $this->Level--;
  752. }
  753. function canCompileTemplate( $resourceData, &$extraParameters )
  754. {
  755. $resourceObject = $resourceData['handler'];
  756. if ( !$resourceObject )
  757. return false;
  758. $canGenerate = $resourceObject->canCompileTemplate( $this, $resourceData, $extraParameters );
  759. return $canGenerate;
  760. }
  761. /*!
  762. Validates the template file \a $file and returns \c true if the file has correct syntax.
  763. \param $returnResourceData If \c true then the returned value will be the resourcedata structure
  764. \sa compileTemplateFile(), fetch()
  765. */
  766. function validateTemplateFile( $file, $returnResourceData = false )
  767. {
  768. $this->resetErrorLog();
  769. if ( !file_exists( $file ) )
  770. return false;
  771. $resourceHandler = $this->resourceFor( $file, $resourceName, $templateName );
  772. if ( !$resourceHandler )
  773. return false;
  774. $resourceData = $this->resourceData( $resourceHandler, $file, $resourceName, $templateName );
  775. $resourceData['key-data'] = "file:" . $file;
  776. $key = md5( $resourceData['key-data'] );
  777. $extraParameters = array();
  778. // Disable caching/compiling while fetchin the resource
  779. // It will be restored afterwards
  780. $isCachingAllowed = $this->IsCachingAllowed;
  781. $this->IsCachingAllowed = false;
  782. $resourceHandler->handleResource( $this, $resourceData, eZTemplate::RESOURCE_FETCH, $extraParameters );
  783. // Restore previous caching flag
  784. $this->IsCachingAllowed = $isCachingAllowed;
  785. $root =& $resourceData['root-node'];
  786. $root = array( eZTemplate::NODE_ROOT, false );
  787. $templateText = $resourceData["text"];
  788. $rootNamespace = '';
  789. $this->parse( $templateText, $root, $rootNamespace, $resourceData );
  790. if ( eZTemplate::isDebugEnabled() )
  791. {
  792. $this->appendDebugNodes( $root, $resourceData );
  793. }
  794. $result = !$this->hasErrors() and !$this->hasWarnings();
  795. if ( $returnResourceData )
  796. {
  797. $resourceData['result'] = $result;
  798. return $resourceData;
  799. }
  800. return $result;
  801. }
  802. /*!
  803. Compiles the template file \a $file and returns \c true if the compilation was OK.
  804. \param $returnResourceData If \c true then the returned value will be the resourcedata structure
  805. \sa validateTemplateFile(), fetch()
  806. */
  807. function compileTemplateFile( $file, $returnResourceData = false )
  808. {
  809. $this->resetErrorLog();
  810. if ( !file_exists( $file ) )
  811. return false;
  812. $resourceHandler = $this->resourceFor( $file, $resourceName, $templateName );
  813. if ( !$resourceHandler )
  814. return false;
  815. $resourceData = $this->resourceData( $resourceHandler, $file, $resourceName, $templateName );
  816. $resourceData['key-data'] = "file:" . $file;
  817. $key = md5( $resourceData['key-data'] );
  818. $extraParameters = array();
  819. $resourceHandler->handleResource( $this, $resourceData, eZTemplate::RESOURCE_FETCH, $extraParameters );
  820. $isCompiled = false;
  821. if ( isset( $resourceData['compiled-template'] ) )
  822. $isCompiled = $resourceData['compiled-template'];
  823. if ( !$isCompiled )
  824. {
  825. $root =& $resourceData['root-node'];
  826. $root = array( eZTemplate::NODE_ROOT, false );
  827. $templateText = $resourceData["text"];
  828. $rootNamespace = '';
  829. $this->parse( $templateText, $root, $rootNamespace, $resourceData );
  830. if ( eZTemplate::isDebugEnabled() )
  831. {
  832. $this->appendDebugNodes( $root, $resourceData );
  833. }
  834. $result = eZTemplateCompiler::compileTemplate( $this, $key, $resourceData );
  835. }
  836. else
  837. {
  838. $result = true;
  839. }
  840. if ( $returnResourceData )
  841. {
  842. $resourceData['result'] = $result;
  843. return $resourceData;
  844. }
  845. return $result;
  846. }
  847. function compileTemplate( &$resourceData, &$extraParameters )
  848. {
  849. $resourceObject = $resourceData['handler'];
  850. if ( !$resourceObject )
  851. return false;
  852. $keyData = $resourceData['key-data'];
  853. $uri = $resourceData['uri'];
  854. $resourceName = $resourceData['resource'];
  855. $templatePath = $resourceData['template-name'];
  856. return $resourceObject->compileTemplate( $this, $keyData, $uri, $resourceName, $templatePath, $extraParameters, $resourceData );
  857. }
  858. function executeCompiledTemplate( &$resourceData, &$textElements, $rootNamespace, $currentNamespace, &$extraParameters )
  859. {
  860. $resourceObject = $resourceData['handler'];
  861. if ( !$resourceObject )
  862. return false;
  863. $keyData = $resourceData['key-data'];
  864. $uri = $resourceData['uri'];
  865. $resourceName = $resourceData['resource'];
  866. $templatePath = $resourceData['template-name'];
  867. $timestamp = $resourceData['time-stamp'];
  868. return $resourceObject->executeCompiledTemplate( $this, $textElements,
  869. $keyData, $uri, $resourceData, $templatePath,
  870. $extraParameters, $timestamp,
  871. $rootNamespace, $currentNamespace );
  872. }
  873. /*!
  874. Returns the resource object for URI $uri. If a resource type is specified
  875. in the URI it is extracted and set in $res. The template name is set in $template
  876. without any resource specifier. To specify a resource the name and a ":" is
  877. prepended to the URI, for instance file:my.tpl.
  878. If no resource type is found the URI the default resource handler is used.
  879. */
  880. function resourceFor( $uri, &$res, &$template )
  881. {
  882. $args = explode( ":", $uri );
  883. if ( isset( $args[1] ) )
  884. {
  885. $res = $args[0];
  886. $template = $args[1];
  887. }
  888. else
  889. $template = $uri;
  890. if ( eZTemplate::isDebugEnabled() )
  891. {
  892. eZDebug::writeNotice( "eZTemplate: Loading template \"$template\" with resource \"$res\"" );
  893. }
  894. if ( isset( $this->Resources[$res] ) and is_object( $this->Resources[$res] ) )
  895. {
  896. return $this->Resources[$res];
  897. }
  898. return $this->DefaultResource;
  899. }
  900. /*!
  901. \return The resource handler object for resource name \a $resourceName.
  902. \sa resourceFor
  903. */
  904. function resourceHandler( $resourceName )
  905. {
  906. if ( isset( $this->Resources[$resourceName] ) &&
  907. is_object( $this->Resources[$resourceName] ) )
  908. {
  909. return $this->Resources[$resourceName];
  910. }
  911. return $this->DefaultResource;
  912. }
  913. function hasChildren( &$function, $functionName )
  914. {
  915. $hasChildren = $function->hasChildren();
  916. if ( is_array( $hasChildren ) )
  917. return $hasChildren[$functionName];
  918. else
  919. return $hasChildren;
  920. }
  921. /*!
  922. Returns the empty variable type.
  923. */
  924. function emptyVariable()
  925. {
  926. return array( "type" => "null" );
  927. }
  928. /*!
  929. \static
  930. */
  931. function mergeNamespace( $rootNamespace, $additionalNamespace )
  932. {
  933. $namespace = $rootNamespace;
  934. if ( $namespace == '' )
  935. $namespace = $additionalNamespace;
  936. else if ( $additionalNamespace != '' )
  937. $namespace = "$namespace:$additionalNamespace";
  938. return $namespace;
  939. }
  940. /*!
  941. Returns the actual value of a template type or null if an unknown type.
  942. */
  943. function elementValue( &$dataElements, $rootNamespace, $currentNamespace, $placement = false,
  944. $checkExistance = false, $checkForProxy = false )
  945. {
  946. /*
  947. * We use a small dirty hack in this function...
  948. * To help the caller to determine if the value was a proxy object,
  949. * we store boolean true to $dataElements['proxy-object-found'] in this case.
  950. * (it's up to caller to remove this garbage from $dataElements...)
  951. * This behaviour is enabled by $checkForProxy parameter.
  952. */
  953. $value = null;
  954. if ( !is_array( $dataElements ) )
  955. {
  956. $this->error( "elementValue",
  957. "Missing array data structure, got " . gettype( $dataElements ) );
  958. return null;
  959. }
  960. foreach ( $dataElements as $dataElement )
  961. {
  962. if ( $dataElement === null )
  963. {
  964. return null;
  965. }
  966. $dataType = $dataElement[0];
  967. switch ( $dataType )
  968. {
  969. case eZTemplate::TYPE_VOID:
  970. {
  971. if ( !$checkExistance )
  972. $this->warning( 'elementValue',
  973. 'Found void datatype, should not be used' );
  974. else
  975. {
  976. return null;
  977. }
  978. } break;
  979. case eZTemplate::TYPE_STRING:
  980. case eZTemplate::TYPE_NUMERIC:
  981. case eZTemplate::TYPE_IDENTIFIER:
  982. case eZTemplate::TYPE_BOOLEAN:
  983. case eZTemplate::TYPE_ARRAY:
  984. {
  985. $value = $dataElement[1];
  986. } break;
  987. case eZTemplate::TYPE_VARIABLE:
  988. {
  989. $variableData = $dataElement[1];
  990. $variableNamespace = $variableData[0];
  991. $variableNamespaceScope = $variableData[1];
  992. $variableName = $variableData[2];
  993. if ( $variableNamespaceScope == eZTemplate::NAMESPACE_SCOPE_GLOBAL )
  994. $namespace = $variableNamespace;
  995. else if ( $variableNamespaceScope == eZTemplate::NAMESPACE_SCOPE_LOCAL )
  996. $namespace = $this->mergeNamespace( $rootNamespace, $variableNamespace );
  997. else if ( $variableNamespaceScope == eZTemplate::NAMESPACE_SCOPE_RELATIVE )
  998. $namespace = $this->mergeNamespace( $currentNamespace, $variableNamespace );
  999. else
  1000. $namespace = false;
  1001. if ( $this->hasVariable( $variableName, $namespace ) )
  1002. {
  1003. $value = $this->variable( $variableName, $namespace );
  1004. }
  1005. else
  1006. {
  1007. if ( !$checkExistance )
  1008. $this->error( '', "Unknown template variable '$variableName' in namespace '$namespace'", $placement );
  1009. {
  1010. return null;
  1011. }
  1012. }
  1013. } break;
  1014. case eZTemplate::TYPE_ATTRIBUTE:
  1015. {
  1016. $attributeData = $dataElement[1];
  1017. $attributeValue = $this->elementValue( $attributeData, $rootNamespace, $currentNamespace, false, $checkExistance );
  1018. if ( $attributeValue !== null )
  1019. {
  1020. if ( !is_numeric( $attributeValue ) and
  1021. !is_string( $attributeValue ) and
  1022. !is_bool( $attributeValue ) )
  1023. {
  1024. if ( !$checkExistance )
  1025. $this->error( "",
  1026. "Cannot use type " . gettype( $attributeValue ) . " for attribute lookup", $placement );
  1027. {
  1028. return null;
  1029. }
  1030. }
  1031. if ( is_array( $value ) )
  1032. {
  1033. if ( array_key_exists( $attributeValue, $value ) )
  1034. {
  1035. $value = $value[$attributeValue];
  1036. }
  1037. else
  1038. {
  1039. if ( !$checkExistance )
  1040. {
  1041. $arrayAttributeList = array_keys( $value );
  1042. $arrayCount = count( $arrayAttributeList );
  1043. $errorMessage = "No such attribute for array($arrayCount): $attributeValue";
  1044. $chooseText = "Choose one of following: ";
  1045. $errorMessage .= "\n$chooseText";
  1046. $errorMessage .= $this->expandAttributes( $arrayAttributeList, $chooseText, 25 );
  1047. $this->error( "",
  1048. $errorMessage, $placement );
  1049. }
  1050. return null;
  1051. }
  1052. }
  1053. else if ( is_object( $value ) )
  1054. {
  1055. if ( method_exists( $value, "attribute" ) and
  1056. method_exists( $value, "hasattribute" ) )
  1057. {
  1058. if ( $value->hasAttribute( $attributeValue ) )
  1059. {
  1060. $value = $value->attribute( $attributeValue );
  1061. }
  1062. else
  1063. {
  1064. if ( !$checkExistance )
  1065. {
  1066. $objectAttributeList = array();
  1067. if ( method_exists( $value, 'attributes' ) )
  1068. $objectAttributeList = $value->attributes();
  1069. $objectClass= get_class( $value );
  1070. $errorMessage = "No such attribute for object($objectClass): $attributeValue";
  1071. $chooseText = "Choose one of following: ";
  1072. $errorMessage .= "\n$chooseText";
  1073. $errorMessage .= $this->expandAttributes( $objectAttributeList, $chooseText, 25 );
  1074. $this->error( "",
  1075. $errorMessage, $placement );
  1076. }
  1077. return null;
  1078. }
  1079. }
  1080. else
  1081. {
  1082. if ( !$checkExistance )
  1083. $this->error( "",
  1084. "Cannot retrieve attribute of object(" . get_class( $value ) .
  1085. "), no attribute functions available",
  1086. $placement );
  1087. return null;
  1088. }
  1089. }
  1090. else
  1091. {
  1092. if ( !$checkExistance )
  1093. $this->error( "",
  1094. "Cannot retrieve attribute of a " . gettype( $value ),
  1095. $placement );
  1096. return null;
  1097. }
  1098. }
  1099. else
  1100. {
  1101. if ( !$checkExistance )
  1102. $this->error( '',
  1103. 'Attribute value was null, cannot get attribute',
  1104. $placement );
  1105. return null;
  1106. }
  1107. } break;
  1108. case eZTemplate::TYPE_OPERATOR:
  1109. {
  1110. $operatorParameters = $dataElement[1];
  1111. $operatorName = $operatorParameters[0];
  1112. $operatorParameters = array_splice( $operatorParameters, 1 );
  1113. if ( is_object( $value ) and
  1114. method_exists( $value, 'templateValue' ) )
  1115. {
  1116. if ( $checkForProxy )
  1117. $dataElements['proxy-object-found'] = true;
  1118. $value = $value->templateValue();
  1119. }
  1120. $valueData = array( 'value' => $value );
  1121. $this->processOperator( $operatorName, $operatorParameters, $rootNamespace, $currentNamespace,
  1122. $valueData, $placement, $checkExistance );
  1123. $value = $valueData['value'];
  1124. } break;
  1125. default:
  1126. {
  1127. if ( !$checkExistance )
  1128. $this->error( "elementValue",
  1129. "Unknown data type: '$dataType'" );
  1130. return null;
  1131. }
  1132. }
  1133. }
  1134. if ( is_object( $value ) and
  1135. method_exists( $value, 'templateValue' ) )
  1136. {
  1137. if ( $checkForProxy )
  1138. $dataElements['proxy-object-found'] = true;
  1139. return $value->templateValue();
  1140. }
  1141. return $value;
  1142. }
  1143. function expandAttributes( $attributeList, $chooseText, $maxThreshold, $minThreshold = 1 )
  1144. {
  1145. $errorMessage = '';
  1146. $attributeCount = count( $attributeList );
  1147. if ( $attributeCount < $minThreshold )
  1148. return $errorMessage;
  1149. if ( $attributeCount < $maxThreshold )
  1150. {
  1151. $chooseLength = strlen( $chooseText );
  1152. $attributeText = '';
  1153. $i = 0;
  1154. foreach ( $attributeList as $attributeName )
  1155. {
  1156. if ( $i > 0 )
  1157. $attributeText .= ",";
  1158. if ( strlen( $attributeText ) > 40 )
  1159. {
  1160. $attributeText .= "\n";
  1161. $errorMessage .= $attributeText;
  1162. $errorMessage .= str_repeat( ' ', $chooseLength );
  1163. $attributeText = '';
  1164. }
  1165. else if ( $i > 0 )
  1166. $attributeText .= " ";
  1167. $attributeText .= $attributeName;
  1168. ++$i;
  1169. }
  1170. $errorMessage .= $attributeText;
  1171. }

Large files files are truncated, but you can click here to view the full file