* All rights reserved * * This script is part of the TYPO3 project. The TYPO3 project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * A copy is found in the textfile GPL.txt and important notices to the license * from the author is found in LICENSE.txt distributed with these scripts. * * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ /** * XML handling class for the TYPO3 Extension Manager. * * It contains methods for handling the XML files involved with the EM, * such as the list of extension mirrors and the list of available extensions. * * @author Karsten Dambekalns * @package TYPO3 * @subpackage EM */ class SC_mod_tools_em_xmlhandler { /** * Holds the parsed XML from extensions.xml.gz * @see parseExtensionsXML() * * @var array */ var $emObj; var $extXMLResult = array(); var $extensionsXML = array(); var $reviewStates = null; var $useUnchecked = false; var $useObsolete = false; /** * Reduces the entries in $this->extensionsXML to the latest version per extension and removes entries not matching the search parameter * * @param string $search The list of extensions is reduced to entries matching this. If empty, the full list is returned. * @param boolean $latest If true, only the latest version is kept in the list * @return [type] ... */ function searchExtensionsXML($search, $owner='') { if(!count($this->extensionsXML)) $this->loadExtensionsXML(); reset($this->extensionsXML); while (list($extkey, $data) = each($this->extensionsXML)) { // Unset extension key in installed keys array (for tracking) if(isset($this->emObj->inst_keys[$extkey])) unset($this->emObj->inst_keys[$extkey]); if(strlen($search) && !stristr($extkey,$search)) { unset($this->extensionsXML[$extkey]); continue; } if(strlen($owner) && !$this->checkOwner($extkey, $owner)) { unset($this->extensionsXML[$extkey]); continue; } if(!strlen($owner)) { $this->checkReviewState($this->extensionsXML[$extkey]['versions']); // if showing only own extensions, never hide unreviewed } $this->removeObsolete($this->extensionsXML[$extkey]['versions']); uksort($data['versions'], array($this->emObj, 'versionDifference')); // needed? or will the extensions always be sorted in the XML anyway? Robert? if(!count($this->extensionsXML[$extkey]['versions'])) { unset($this->extensionsXML[$extkey]); } } } /** * Checks whether at least one of the extension versions is owned by the given username * * @param string $extkey * @param string $owner * @return boolean */ function checkOwner($extkey, $owner) { foreach($this->extensionsXML[$extkey]['versions'] as $ext) { if($ext['ownerusername'] == $owner) return true; } return false; } /** * Loads the pre-parsed extension list * * @return boolean true on success, false on error */ function loadExtensionsXML() { if(is_file(PATH_site.'typo3temp/extensions.bin')) { $this->extensionsXML = unserialize(gzuncompress(t3lib_div::getURL(PATH_site.'typo3temp/extensions.bin'))); return true; } else { $this->extensionsXML = array(); return false; } } /** * Loads the pre-parsed extension list * * @return boolean true on success, false on error */ function loadReviewStates() { if(is_file(PATH_site.'typo3temp/reviewstates.bin')) { $this->reviewStates = unserialize(gzuncompress(t3lib_div::getURL(PATH_site.'typo3temp/reviewstates.bin'))); return true; } else { $this->reviewStates = array(); return false; } } /** * Enter description here... * * @return [type] ... */ function saveExtensionsXML() { t3lib_div::writeFile(PATH_site.'typo3temp/extensions.bin',gzcompress(serialize($this->extXMLResult))); t3lib_div::writeFile(PATH_site.'typo3temp/reviewstates.bin',gzcompress(serialize($this->reviewStates))); } /** * Frees the pre-parsed extension list * * @return void */ function freeExtensionsXML() { unset($this->extensionsXML); $this->extensionsXML = array(); } /** * Removes all extension with a certain state from the list * * @param array &$extensions The "versions" subpart of the extension list * @return void */ function removeObsolete(&$extensions) { if($this->useObsolete) return; reset($extensions); while (list($version, $data) = each($extensions)) { if($data['state']=='obsolete') unset($extensions[$version]); } } /** * Enter description here... * * @param string $extKey * @param string $version: ... * @return integer Review state, if none is set 0 is returned as default. */ function getReviewState($extKey, $version) { if(!is_array($this->reviewStates)) $this->loadReviewStates(); if(isset($this->reviewStates[$extKey])) { return (int)$this->reviewStates[$extKey][$version]; } else { return 0; } } /** * Removes all extension versions from $extensions that have a reviewstate<1, unless explicitly allowed * * @param array &$extensions The "versions" subpart of the extension list * @return void */ function checkReviewState(&$extensions) { if ($this->useUnchecked) return; reset($extensions); while (list($version, $data) = each($extensions)) { if($data['reviewstate']<1) unset($extensions[$version]); } } /** * Removes all extension versions from the list of available extensions that have a reviewstate<1, unless explicitly allowed * * @return void */ function checkReviewStateGlobal() { if($this->useUnchecked) return; reset($this->extensionsXML); while (list($extkey, $data) = each($this->extensionsXML)) { while (list($version, $vdata) = each($data['versions'])) { if($vdata['reviewstate']<1) unset($this->extensionsXML[$extkey]['versions'][$version]); } if(!count($this->extensionsXML[$extkey]['versions'])) unset($this->extensionsXML[$extkey]); } } /** * ***************PARSING METHODS*********************** */ /** * Enter description here... * * @param unknown_type $parser * @param unknown_type $name * @param unknown_type $attrs * @return [type] ... */ function startElement($parser, $name, $attrs) { switch($name) { case 'extensions': break; case 'extension': $this->currentExt = $attrs['extensionkey']; break; case 'version': $this->currentVersion = $attrs['version']; $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion] = array(); break; default: $this->currentTag = $name; } } /** * Enter description here... * * @param unknown_type $parser * @param unknown_type $name * @return [type] ... */ function endElement($parser, $name) { switch($name) { case 'extension': unset($this->currentExt); break; case 'version': unset($this->currentVersion); break; default: unset($this->currentTag); } } /** * Enter description here... * * @param unknown_type $parser * @param unknown_type $data * @return [type] ... */ function characterData($parser, $data) { if(isset($this->currentTag)) { if(!isset($this->currentVersion) && $this->currentTag == 'downloadcounter') { $this->extXMLResult[$this->currentExt]['downloadcounter'] = trim($data); } elseif($this->currentTag == 'dependencies') { $data = @unserialize($data); if(is_array($data)) { $dep = array(); foreach($data as $v) { $dep[$v['kind']][$v['extensionKey']] = $v['versionRange']; } $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['dependencies'] = $dep; } } elseif($this->currentTag == 'reviewstate') { $this->reviewStates[$this->currentExt][$this->currentVersion] = (int)trim($data); $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['reviewstate'] = (int)trim($data); } else { $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion][$this->currentTag] .= trim($data); } } } /** * Parses content of mirrors.xml into a suitable array * * @param string XML data file to parse * @return string HTLML output informing about result */ function parseExtensionsXML($string) { global $TYPO3_CONF_VARS; $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); xml_set_element_handler($parser, array(&$this,'startElement'), array(&$this,'endElement')); xml_set_character_data_handler($parser, array(&$this,'characterData')); if ((double)phpversion()>=5) { $preg_result = array(); preg_match('/^[[:space:]]*<\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/',substr($string,0,200),$preg_result); $theCharset = $preg_result[1] ? $preg_result[1] : ($TYPO3_CONF_VARS['BE']['forceCharset'] ? $TYPO3_CONF_VARS['BE']['forceCharset'] : 'iso-8859-1'); xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset); // us-ascii / utf-8 / iso-8859-1 } // Parse content: if (!xml_parse($parser, $string)) { $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)); $error = true; } xml_parser_free($parser); if(!$error) { $content.= '

The extensions list has been updated and now contains '.count($this->extXMLResult).' extension entries.

'; } return $content; } /** * Parses content of mirrors.xml into a suitable array * * @param string $string: XML data to parse * @return string HTLML output informing about result */ function parseMirrorsXML($string) { global $TYPO3_CONF_VARS; // Create parser: $parser = xml_parser_create(); $vals = array(); $index = array(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); if ((double)phpversion()>=5) { $preg_result = array(); preg_match('/^[[:space:]]*<\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/',substr($string,0,200),$preg_result); $theCharset = $preg_result[1] ? $preg_result[1] : ($TYPO3_CONF_VARS['BE']['forceCharset'] ? $TYPO3_CONF_VARS['BE']['forceCharset'] : 'iso-8859-1'); xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset); // us-ascii / utf-8 / iso-8859-1 } // Parse content: xml_parse_into_struct($parser, $string, $vals, $index); // If error, return error message: if (xml_get_error_code($parser)) { $line = xml_get_current_line_number($parser); $error = xml_error_string(xml_get_error_code($parser)); xml_parser_free($parser); return 'Error in XML parser while decoding mirrors XML file. Line '.$line.': '.$error; } else { // Init vars: $stack = array(array()); $stacktop = 0; $mirrornumber = 0; $current=array(); $tagName = ''; $documentTag = ''; // Traverse the parsed XML structure: foreach($vals as $val) { // First, process the tag-name (which is used in both cases, whether "complete" or "close") $tagName = ($val['tag']=='mirror' && $val['type']=='open') ? '__plh' : $val['tag']; if (!$documentTag) $documentTag = $tagName; // Setting tag-values, manage stack: switch($val['type']) { case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array: $current[$tagName] = array(); // Setting blank place holder $stack[$stacktop++] = $current; $current = array(); break; case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer. $oldCurrent = $current; $current = $stack[--$stacktop]; end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next: if($tagName=='mirror') { unset($current['__plh']); $current[$oldCurrent['host']] = $oldCurrent; } else { $current[key($current)] = $oldCurrent; } unset($oldCurrent); break; case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it. $current[$tagName] = (string)$val['value']; // Had to cast it as a string - otherwise it would be evaluate false if tested with isset()!! break; } } return $current[$tagName]; } } /** * Parses content of *-l10n.xml into a suitable array * * @param string $string: XML data to parse * @return array Array representation of XML data */ function parseL10nXML($string) { // Create parser: $parser = xml_parser_create(); $vals = array(); $index = array(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); // Parse content: xml_parse_into_struct($parser, $string, $vals, $index); // If error, return error message: if (xml_get_error_code($parser)) { $line = xml_get_current_line_number($parser); $error = xml_error_string(xml_get_error_code($parser)); debug($error); xml_parser_free($parser); return 'Error in XML parser while decoding l10n XML file. Line '.$line.': '.$error; } else { // Init vars: $stack = array(array()); $stacktop = 0; $mirrornumber = 0; $current=array(); $tagName = ''; $documentTag = ''; // Traverse the parsed XML structure: foreach($vals as $val) { // First, process the tag-name (which is used in both cases, whether "complete" or "close") $tagName = ($val['tag']=='languagepack' && $val['type']=='open') ? $val['attributes']['language'] : $val['tag']; if (!$documentTag) $documentTag = $tagName; // Setting tag-values, manage stack: switch($val['type']) { case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array: $current[$tagName] = array(); // Setting blank place holder $stack[$stacktop++] = $current; $current = array(); break; case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer. $oldCurrent = $current; $current = $stack[--$stacktop]; end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next: $current[key($current)] = $oldCurrent; unset($oldCurrent); break; case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it. $current[$tagName] = (string)$val['value']; // Had to cast it as a string - otherwise it would be evaluate false if tested with isset()!! break; } } return $current[$tagName]; } } } ?>