TYPO3 API  SVNRelease
class.tx_em_tools_xmlhandler.php
Go to the documentation of this file.
00001 <?php
00002 /* **************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2006-2010 Karsten Dambekalns <karsten@typo3.org>
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *  A copy is found in the textfile GPL.txt and important notices to the license
00017 *  from the author is found in LICENSE.txt distributed with these scripts.
00018 *
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 
00028 /**
00029  * XML handling class for the TYPO3 Extension Manager.
00030  *
00031  * It contains methods for handling the XML files involved with the EM,
00032  * such as the list of extension mirrors and the list of available extensions.
00033  *
00034  * @author Karsten Dambekalns <karsten@typo3.org>
00035  * @package TYPO3
00036  * @subpackage EM
00037  */
00038 class tx_em_Tools_XmlHandler {
00039 
00040 
00041     /**
00042      * Holds the parsed XML from extensions.xml.gz
00043      * @see parseExtensionsXML()
00044      *
00045      * @var array
00046      */
00047     protected $extXMLResult = array();
00048 
00049     /**
00050      * @var array
00051      */
00052     public $extensionsXML = array();
00053 
00054     /**
00055      * @var NULL
00056      */
00057     protected $reviewStates = NULL;
00058 
00059     /**
00060      * @var bool
00061      */
00062     public $useObsolete = FALSE;
00063 
00064     /**
00065      * @var array
00066      */
00067     protected $catArr = array();
00068 
00069     /**
00070      * @var array
00071      */
00072     protected $stateArr = array();
00073 
00074     /**
00075      * @var int
00076      */
00077     public $matchingCount = 0;
00078 
00079     /**
00080      * @var array
00081      */
00082     protected $revCatArr = array();
00083 
00084     /**
00085      * @var array
00086      */
00087     protected $revStateArr = array();
00088 
00089     /**
00090      * @var string
00091      */
00092     protected $currentExt = '';
00093 
00094     /**
00095      * @var string
00096      */
00097     protected $currentVersion = '';
00098 
00099     /**
00100      * @var string
00101      */
00102     protected $currentTag = '';
00103 
00104     /**
00105      * Reduces the entries in $this->extensionsXML to the latest version per extension and removes entries not matching the search parameter
00106      *
00107      * @param   string      $search     The list of extensions is reduced to entries matching this. If empty, the full list is returned.
00108      * @param   string      $owner      If set only extensions of that user are fetched
00109      * @param   string      $order      A field to order the result by
00110      * @param   boolean     $allExt     If set also unreviewed and obsolete extensions are shown
00111      * @param   boolean     $allVer     If set returns all version of an extension, otherwise only the last
00112      * @param   integer     $offset     Offset to return result from (goes into LIMIT clause)
00113      * @param   integer     $limit      Maximum number of entries to return (goes into LIMIT clause)
00114      * @param   boolean     $exactMatch If set search is done for exact matches of extension keys only
00115      * @return  void
00116      */
00117     function searchExtensionsXML($search, $owner = '', $order = '', $allExt = FALSE, $allVer = FALSE, $offset = 0, $limit = 500, $exactMatch = FALSE) {
00118         $where = '1=1';
00119         if ($search && $exactMatch) {
00120             $where .= ' AND extkey=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($search, 'cache_extensions');
00121         } elseif ($search) {
00122             $quotedSearch = $GLOBALS['TYPO3_DB']->escapeStrForLike(
00123                 $GLOBALS['TYPO3_DB']->quoteStr($search, 'cache_extensions'),
00124                 'cache_extensions'
00125             );
00126             $where .= ' AND (extkey LIKE \'%' . $quotedSearch . '%\' OR title LIKE \'%' . $quotedSearch . '%\')';
00127 
00128         }
00129         if ($owner) {
00130             $where .= ' AND ownerusername=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($owner, 'cache_extensions');
00131         }
00132 
00133         // Show extensions without a review or that have passed a review, but not insecure extensions
00134         $where .= ' AND reviewstate >= 0';
00135 
00136         if (!$this->useObsolete) {
00137             // 5 == obsolete
00138             $where .= ' AND state != 5';
00139         }
00140 
00141         switch ($order) {
00142             case 'author_company':
00143                 $forder = 'authorname, authorcompany';
00144             break;
00145             case 'state':
00146                 $forder = 'state';
00147             break;
00148             case 'cat':
00149             default:
00150                 $forder = 'category';
00151             break;
00152         }
00153         $order = $forder . ', title';
00154         if (!$allVer) {
00155             $where .= ' AND lastversion > 0';
00156         }
00157 
00158         $idx = 0;
00159         $defaultCategories = tx_em_Tools::getDefaultCategory();
00160         foreach ($defaultCategories as $catKey => $tmp) {
00161             $this->catArr[$idx] = $catKey;
00162             $idx++;
00163         }
00164 
00165         $idx = 0;
00166         $states = tx_em_Tools::getStates();
00167         foreach ($states as $state => $tmp) {
00168             $this->stateArr[$idx] = $state;
00169             $idx++;
00170         }
00171 
00172         // Fetch count
00173         $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('*', 'cache_extensions', $where);
00174         $this->matchingCount = $count;
00175 
00176         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'cache_extensions', $where, '', $order, $offset . ',' . $limit);
00177         $this->extensionsXML = array();
00178         while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00179             $row['category'] = $this->catArr[$row['category']];
00180             $row['state'] = $this->stateArr[$row['state']];
00181 
00182             if (!is_array($this->extensionsXML[$row['extkey']])) {
00183                 $this->extensionsXML[$row['extkey']] = array();
00184                 $this->extensionsXML[$row['extkey']]['downloadcounter'] = $row['alldownloadcounter'];
00185             }
00186             if (!is_array($this->extensionsXML[$row['extkey']]['versions'])) {
00187                 $this->extensionsXML[$row['extkey']]['versions'] = array();
00188             }
00189             $row['dependencies'] = unserialize($row['dependencies']);
00190             $this->extensionsXML[$row['extkey']]['versions'][$row['version']] = $row;
00191         }
00192         $GLOBALS['TYPO3_DB']->sql_free_result($res);
00193     }
00194 
00195     /**
00196      * Reduces the entries in $this->extensionsXML to the latest version per extension and removes entries not matching the search parameter
00197      * The extension key has to be a valid one as search is done for exact matches only.
00198      *
00199      * @param   string      $search The list of extensions is reduced to entries with exactely this extension key. If empty, the full list is returned.
00200      * @param   string      $owner  If set only extensions of that user are fetched
00201      * @param   string      $order  A field to order the result by
00202      * @param   boolean     $allExt If set also unreviewed and obsolete extensions are shown
00203      * @param   boolean     $allVer If set returns all version of an extension, otherwise only the last
00204      * @param   integer     $offset Offset to return result from (goes into LIMIT clause)
00205      * @param   integer     $limit  Maximum number of entries to return (goes into LIMIT clause)
00206      * @return  void
00207      */
00208     function searchExtensionsXMLExact($search, $owner = '', $order = '', $allExt = FALSE, $allVer = FALSE, $offset = 0, $limit = 500) {
00209         $this->searchExtensionsXML($search, $owner, $order, $allExt, $allVer, $offset, $limit, TRUE);
00210     }
00211 
00212     function countExtensions() {
00213         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('extkey', 'cache_extensions', '1=1', 'extkey');
00214         $cnt = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
00215         $GLOBALS['TYPO3_DB']->sql_free_result($res);
00216         return $cnt;
00217     }
00218 
00219     /**
00220      * Loads the pre-parsed extension list
00221      *
00222      * @return  boolean     TRUE on success, FALSE on error
00223      */
00224     function loadExtensionsXML() {
00225         $this->searchExtensionsXML('', '', '', TRUE);
00226     }
00227 
00228     /**
00229      * Frees the pre-parsed extension list
00230      *
00231      * @return  void
00232      */
00233     function freeExtensionsXML() {
00234         unset($this->extensionsXML);
00235         $this->extensionsXML = array();
00236     }
00237 
00238     /**
00239      * Removes all extension with a certain state from the list
00240      *
00241      * @param   array       &$extensions    The "versions" subpart of the extension list
00242      * @return  void
00243      */
00244     function removeObsolete(&$extensions) {
00245         if ($this->useObsolete) {
00246             return;
00247         }
00248 
00249         foreach ($extensions as $version => $data) {
00250             if ($data['state'] == 'obsolete') {
00251                 unset($extensions[$version]);
00252             }
00253         }
00254     }
00255 
00256     /**
00257      * Returns the reviewstate of a specific extension-key/version
00258      *
00259      * @param   string      $extKey
00260      * @param   string      $version: ...
00261      * @return  integer     Review state, if none is set 0 is returned as default.
00262      */
00263     function getReviewState($extKey, $version) {
00264         $where = 'extkey=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($extKey, 'cache_extensions') . ' AND version=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($version, 'cache_extensions');
00265         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('reviewstate', 'cache_extensions', $where);
00266         $reviewState = 0;
00267         if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00268             $reviewState = $row['reviewstate'];
00269         }
00270         $GLOBALS['TYPO3_DB']->sql_free_result($res);
00271 
00272         return $reviewState;
00273     }
00274 
00275 
00276     /**
00277      * ***************PARSING METHODS***********************
00278      */
00279     /**
00280      * Enter description here...
00281      *
00282      * @param   unknown_type        $parser
00283      * @param   unknown_type        $name
00284      * @param   unknown_type        $attrs
00285      * @return  [type]      ...
00286      */
00287     function startElement($parser, $name, $attrs) {
00288         switch ($name) {
00289             case 'extensions':
00290                 break;
00291             case 'extension':
00292                 $this->currentExt = $attrs['extensionkey'];
00293                 break;
00294             case 'version':
00295                 $this->currentVersion = $attrs['version'];
00296                 $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion] = array();
00297                 break;
00298             default:
00299                 $this->currentTag = $name;
00300         }
00301     }
00302 
00303     /**
00304      * Enter description here...
00305      *
00306      * @param   unknown_type        $parser
00307      * @param   unknown_type        $name
00308      * @return  [type]      ...
00309      */
00310     function endElement($parser, $name) {
00311         switch ($name) {
00312             case 'extension':
00313                 unset($this->currentExt);
00314                 break;
00315             case 'version':
00316                 unset($this->currentVersion);
00317                 break;
00318             default:
00319                 unset($this->currentTag);
00320         }
00321     }
00322 
00323     /**
00324      * Enter description here...
00325      *
00326      * @param   unknown_type        $parser
00327      * @param   unknown_type        $data
00328      * @return  [type]      ...
00329      */
00330     function characterData($parser, $data) {
00331         if (isset($this->currentTag)) {
00332             if (!isset($this->currentVersion) && $this->currentTag == 'downloadcounter') {
00333                 $this->extXMLResult[$this->currentExt]['downloadcounter'] = trim($data);
00334             } elseif ($this->currentTag == 'dependencies') {
00335                 $data = @unserialize($data);
00336                 if (is_array($data)) {
00337                     $dep = array();
00338                     foreach ($data as $v) {
00339                         $dep[$v['kind']][$v['extensionKey']] = $v['versionRange'];
00340                     }
00341                     $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['dependencies'] = $dep;
00342                 }
00343             } elseif ($this->currentTag == 'reviewstate') {
00344                 $this->reviewStates[$this->currentExt][$this->currentVersion] = (int) trim($data);
00345                 $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['reviewstate'] = (int) trim($data);
00346             } else {
00347                 $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion][$this->currentTag] .= trim($data);
00348             }
00349         }
00350     }
00351 
00352     /**
00353      * Parses content of mirrors.xml into a suitable array
00354      *
00355      * @param   string      XML data file to parse
00356      * @return  string      HTLML output informing about result
00357      */
00358     function parseExtensionsXML($filename) {
00359 
00360         $parser = xml_parser_create();
00361         xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
00362         xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
00363         xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'utf-8');
00364         xml_set_element_handler($parser, array(&$this, 'startElement'), array(&$this, 'endElement'));
00365         xml_set_character_data_handler($parser, array(&$this, 'characterData'));
00366 
00367         $fp = gzopen($filename, 'rb');
00368         if (!$fp) {
00369             return 'Error opening XML extension file "' . $filename . '"';
00370         }
00371         $string = gzread($fp, 0xffff); // Read 64KB
00372 
00373 
00374         $idx = 0;
00375         $defaultCategories = tx_em_Tools::getDefaultCategory();
00376         foreach ($defaultCategories as $catKey => $tmp) {
00377             $this->revCatArr[$catKey] = $idx++;
00378         }
00379 
00380 
00381         $idx = 0;
00382         $states = tx_em_Tools::getStates();
00383         foreach ($states as $state => $tmp) {
00384             $this->revStateArr[$state] = $idx++;
00385         }
00386 
00387         $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery('cache_extensions');
00388 
00389         $extcount = 0;
00390         $content = '';
00391         @ini_set('pcre.backtrack_limit', 500000);
00392         do {
00393             if (preg_match('/.*(<extension\s+extensionkey="[^"]+">.*<\/extension>)/suU', $string, $match)) {
00394                 // Parse content:
00395                 if (!xml_parse($parser, $match[0], 0)) {
00396                     $content .= 'Error in XML parser while decoding extensions XML file. Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser));
00397                     $error = TRUE;
00398                     break;
00399                 }
00400                 $this->storeXMLResult();
00401                 $this->extXMLResult = array();
00402                 $extcount++;
00403                 $string = substr($string, strlen($match[0]));
00404             } elseif (function_exists('preg_last_error') && preg_last_error()) {
00405                 $errorcodes = array(
00406                     0 => 'PREG_NO_ERROR',
00407                     1 => 'PREG_INTERNAL_ERROR',
00408                     2 => 'PREG_BACKTRACK_LIMIT_ERROR',
00409                     3 => 'PREG_RECURSION_LIMIT_ERROR',
00410                     4 => 'PREG_BAD_UTF8_ERROR'
00411                 );
00412                 $content .= 'Error in regular expression matching, code: ' . $errorcodes[preg_last_error()] . '<br />See <a href="http://www.php.net/manual/en/function.preg-last-error.php" target="_blank">http://www.php.net/manual/en/function.preg-last-error.php</a>';
00413                 $error = TRUE;
00414                 break;
00415             } else {
00416                 if (gzeof($fp)) {
00417                     break;
00418                 } // Nothing more can be read
00419                 $string .= gzread($fp, 0xffff); // Read another 64KB
00420             }
00421         } while (TRUE);
00422 
00423         xml_parser_free($parser);
00424         gzclose($fp);
00425 
00426         if (!$error) {
00427             /** @var $flashMessage t3lib_FlashMessage */
00428             $flashMessage = t3lib_div::makeInstance(
00429                 't3lib_FlashMessage',
00430                 sprintf($GLOBALS['LANG']->getLL('ext_import_extlist_updated'), $extcount),
00431                 $GLOBALS['LANG']->getLL('ext_import_extlist_updated_header')
00432             );
00433             $content .= $flashMessage->render();
00434         }
00435 
00436         return $content;
00437     }
00438 
00439     function storeXMLResult() {
00440         foreach ($this->extXMLResult as $extkey => $extArr) {
00441             $max = -1;
00442             $maxrev = -1;
00443             $last = '';
00444             $lastrev = '';
00445             $usecat = '';
00446             $usetitle = '';
00447             $usestate = '';
00448             $useauthorcompany = '';
00449             $useauthorname = '';
00450             $verArr = array();
00451             foreach ($extArr['versions'] as $version => $vArr) {
00452                 $iv = tx_em_Tools::makeVersion($version, 'int');
00453                 if ($vArr['title'] && !$usetitle) {
00454                     $usetitle = $vArr['title'];
00455                 }
00456                 if ($vArr['state'] && !$usestate) {
00457                     $usestate = $vArr['state'];
00458                 }
00459                 if ($vArr['authorcompany'] && !$useauthorcompany) {
00460                     $useauthorcompany = $vArr['authorcompany'];
00461                 }
00462                 if ($vArr['authorname'] && !$useauthorname) {
00463                     $useauthorname = $vArr['authorname'];
00464                 }
00465                 $verArr[$version] = $iv;
00466                 if ($iv > $max) {
00467                     $max = $iv;
00468                     $last = $version;
00469                     if ($vArr['title']) {
00470                         $usetitle = $vArr['title'];
00471                     }
00472                     if ($vArr['state']) {
00473                         $usestate = $vArr['state'];
00474                     }
00475                     if ($vArr['authorcompany']) {
00476                         $useauthorcompany = $vArr['authorcompany'];
00477                     }
00478                     if ($vArr['authorname']) {
00479                         $useauthorname = $vArr['authorname'];
00480                     }
00481                     $usecat = $vArr['category'];
00482                 }
00483                 if ($vArr['reviewstate'] && ($iv > $maxrev)) {
00484                     $maxrev = $iv;
00485                     $lastrev = $version;
00486                 }
00487             }
00488             if (!strlen($usecat)) {
00489                 $usecat = 4; // Extensions without a category end up in "misc"
00490             } else {
00491                 if (isset($this->revCatArr[$usecat])) {
00492                     $usecat = $this->revCatArr[$usecat];
00493                 } else {
00494                     $usecat = 4; // Extensions without a category end up in "misc"
00495                 }
00496             }
00497             if (isset($this->revStateArr[$usestate])) {
00498                 $usestate = $this->revCatArr[$usestate];
00499             } else {
00500                 $usestate = 999; // Extensions without a category end up in "misc"
00501             }
00502             foreach ($extArr['versions'] as $version => $vArr) {
00503                 $vArr['version'] = $version;
00504                 $vArr['intversion'] = $verArr[$version];
00505                 $vArr['extkey'] = $extkey;
00506                 $vArr['alldownloadcounter'] = $extArr['downloadcounter'];
00507                 $vArr['dependencies'] = serialize($vArr['dependencies']);
00508                 $vArr['category'] = $usecat;
00509                 $vArr['title'] = $usetitle;
00510                 if ($version == $last) {
00511                     $vArr['lastversion'] = 1;
00512                 }
00513                 if ($version == $lastrev) {
00514                     $vArr['lastreviewedversion'] = 1;
00515                 }
00516                 $vArr['state'] = isset($this->revStateArr[$vArr['state']]) ? $this->revStateArr[$vArr['state']] : $usestate; // 999 = not set category
00517                 $GLOBALS['TYPO3_DB']->exec_INSERTquery('cache_extensions', $vArr);
00518             }
00519         }
00520     }
00521 
00522     /**
00523      * Parses content of mirrors.xml into a suitable array
00524      *
00525      * @param   string      $string: XML data to parse
00526      * @return  string      HTLML output informing about result
00527      */
00528     function parseMirrorsXML($string) {
00529         global $TYPO3_CONF_VARS;
00530 
00531         // Create parser:
00532         $parser = xml_parser_create();
00533         $vals = array();
00534         $index = array();
00535 
00536         xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
00537         xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
00538 
00539         $preg_result = array();
00540         preg_match('/^[[:space:]]*<\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/', substr($string, 0, 200), $preg_result);
00541         $theCharset = $preg_result[1] ? $preg_result[1] : ($TYPO3_CONF_VARS['BE']['forceCharset'] ? $TYPO3_CONF_VARS['BE']['forceCharset'] : 'iso-8859-1');
00542         xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset); // us-ascii / utf-8 / iso-8859-1
00543 
00544         // Parse content:
00545         xml_parse_into_struct($parser, $string, $vals, $index);
00546 
00547         // If error, return error message:
00548         if (xml_get_error_code($parser)) {
00549             $line = xml_get_current_line_number($parser);
00550             $error = xml_error_string(xml_get_error_code($parser));
00551             xml_parser_free($parser);
00552             return 'Error in XML parser while decoding mirrors XML file. Line ' . $line . ': ' . $error;
00553         } else {
00554             // Init vars:
00555             $stack = array(array());
00556             $stacktop = 0;
00557             $mirrornumber = 0;
00558             $current = array();
00559             $tagName = '';
00560             $documentTag = '';
00561 
00562             // Traverse the parsed XML structure:
00563             foreach ($vals as $val) {
00564 
00565                 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
00566                 $tagName = ($val['tag'] == 'mirror' && $val['type'] == 'open') ? '__plh' : $val['tag'];
00567                 if (!$documentTag) {
00568                     $documentTag = $tagName;
00569                 }
00570 
00571                 // Setting tag-values, manage stack:
00572                 switch ($val['type']) {
00573                     case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
00574                         $current[$tagName] = array(); // Setting blank place holder
00575                         $stack[$stacktop++] = $current;
00576                         $current = array();
00577                         break;
00578                     case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
00579                         $oldCurrent = $current;
00580                         $current = $stack[--$stacktop];
00581                         end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next:
00582                         if ($tagName == 'mirror') {
00583                             unset($current['__plh']);
00584                             $current[$oldCurrent['host']] = $oldCurrent;
00585                         } else {
00586                             $current[key($current)] = $oldCurrent;
00587                         }
00588                         unset($oldCurrent);
00589                         break;
00590                     case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
00591                         $current[$tagName] = (string) $val['value']; // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
00592                         break;
00593                 }
00594             }
00595             return $current[$tagName];
00596         }
00597     }
00598 
00599     /**
00600      * Parses content of *-l10n.xml into a suitable array
00601      *
00602      * @param   string      $string: XML data to parse
00603      * @return  array       Array representation of XML data
00604      */
00605     function parseL10nXML($string) {
00606         // Create parser:
00607         $parser = xml_parser_create();
00608         $vals = array();
00609         $index = array();
00610 
00611         xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
00612         xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
00613 
00614         // Parse content:
00615         xml_parse_into_struct($parser, $string, $vals, $index);
00616 
00617         // If error, return error message:
00618         if (xml_get_error_code($parser)) {
00619             $line = xml_get_current_line_number($parser);
00620             $error = xml_error_string(xml_get_error_code($parser));
00621             xml_parser_free($parser);
00622             return 'Error in XML parser while decoding l10n XML file. Line ' . $line . ': ' . $error;
00623         } else {
00624             // Init vars:
00625             $stack = array(array());
00626             $stacktop = 0;
00627             $mirrornumber = 0;
00628             $current = array();
00629             $tagName = '';
00630             $documentTag = '';
00631 
00632             // Traverse the parsed XML structure:
00633             foreach ($vals as $val) {
00634 
00635                 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
00636                 $tagName = ($val['tag'] == 'languagepack' && $val['type'] == 'open') ? $val['attributes']['language'] : $val['tag'];
00637                 if (!$documentTag) {
00638                     $documentTag = $tagName;
00639                 }
00640 
00641                 // Setting tag-values, manage stack:
00642                 switch ($val['type']) {
00643                     case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
00644                         $current[$tagName] = array(); // Setting blank place holder
00645                         $stack[$stacktop++] = $current;
00646                         $current = array();
00647                         break;
00648                     case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
00649                         $oldCurrent = $current;
00650                         $current = $stack[--$stacktop];
00651                         end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next:
00652                         $current[key($current)] = $oldCurrent;
00653                         unset($oldCurrent);
00654                         break;
00655                     case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
00656                         $current[$tagName] = (string) $val['value']; // Had to cast it as a string - otherwise it would be evaluate FALSE if tested with isset()!!
00657                         break;
00658                 }
00659             }
00660             return $current[$tagName];
00661         }
00662     }
00663 }
00664 
00665 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['typo3/sysext/em/classes/tools/class.tx_em_tools_smlhandler.php'])) {
00666     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['typo3/sysext/em/classes/tools/class.tx_em_tools_xmlhandler.php']);
00667 }
00668 
00669 ?>