Fix to code documentation
[Packages/TYPO3.CMS.git] / typo3 / mod / tools / em / class.em_xmlhandler.php
1 <?php
2 /* **************************************************************
3 * Copyright notice
4 *
5 * (c) 2006 Karsten Dambekalns <karsten@typo3.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * XML handling class for the TYPO3 Extension Manager.
30 *
31 * It contains methods for handling the XML files involved with the EM,
32 * such as the list of extension mirrors and the list of available extensions.
33 *
34 * @author Karsten Dambekalns <karsten@typo3.org>
35 * @package TYPO3
36 * @subpackage EM
37 */
38 class SC_mod_tools_em_xmlhandler {
39 /**
40 * Holds the parsed XML from extensions.xml.gz
41 * @see parseExtensionsXML()
42 *
43 * @var array
44 */
45 var $emObj;
46 var $extXMLResult = array();
47 var $extensionsXML = array();
48 var $reviewStates = null;
49 var $useUnsupported = false;
50 var $useObsolete = false;
51
52 /**
53 * Reduces the entries in $this->extensionsXML to the latest version per extension and removes entries not matching the search parameter
54 *
55 * @param string $search The list of extensions is reduced to entries matching this. If empty, the full list is returned.
56 * @param boolean $latest If true, only the latest version is kept in the list
57 * @return [type] ...
58 */
59 function searchExtensionsXML($search, $owner='') {
60 if(!count($this->extensionsXML)) $this->loadExtensionsXML();
61
62 reset($this->extensionsXML);
63 while (list($extkey, $data) = each($this->extensionsXML)) {
64
65 // Unset extension key in installed keys array (for tracking)
66 if(isset($this->emObj->inst_keys[$extkey])) unset($this->emObj->inst_keys[$extkey]);
67
68 if(strlen($search) && !stristr($extkey,$search)) {
69 unset($this->extensionsXML[$extkey]);
70 continue;
71 }
72
73 if(strlen($owner) && !$this->checkOwner($extkey, $owner)) {
74 unset($this->extensionsXML[$extkey]);
75 continue;
76 }
77
78 if(!strlen($owner)) {
79 $this->checkReviewState($this->extensionsXML[$extkey]['versions']); // if showing only own extensions, never hide unreviewed
80 }
81 $this->removeObsolete($this->extensionsXML[$extkey]['versions']);
82
83 uksort($data['versions'], array($this->emObj, 'versionDifference')); // needed? or will the extensions always be sorted in the XML anyway? Robert?
84
85 if(!count($this->extensionsXML[$extkey]['versions'])) {
86 unset($this->extensionsXML[$extkey]);
87 }
88 }
89 }
90
91 /**
92 * Checks whether at least one of the extension versions is owned by the given username
93 *
94 * @param string $extkey
95 * @param string $owner
96 * @return boolean
97 */
98 function checkOwner($extkey, $owner) {
99 foreach($this->extensionsXML[$extkey]['versions'] as $ext) {
100 if($ext['ownerusername'] == $owner) return true;
101 }
102 return false;
103 }
104
105 /**
106 * Loads the pre-parsed extension list
107 *
108 * @return boolean true on success, false on error
109 */
110 function loadExtensionsXML() {
111 if(is_file(PATH_site.'typo3temp/extensions.bin')) {
112 $this->extensionsXML = unserialize(gzuncompress(t3lib_div::getURL(PATH_site.'typo3temp/extensions.bin')));
113 return true;
114 } else {
115 $this->extensionsXML = array();
116 return false;
117 }
118 }
119
120 /**
121 * Loads the pre-parsed extension list
122 *
123 * @return boolean true on success, false on error
124 */
125 function loadReviewStates() {
126 if(is_file(PATH_site.'typo3temp/reviewstates.bin')) {
127 $this->reviewStates = unserialize(gzuncompress(t3lib_div::getURL(PATH_site.'typo3temp/reviewstates.bin')));
128 return true;
129 } else {
130 $this->reviewStates = array();
131 return false;
132 }
133 }
134
135 /**
136 * Enter description here...
137 *
138 * @return [type] ...
139 */
140 function saveExtensionsXML() {
141 t3lib_div::writeFile(PATH_site.'typo3temp/extensions.bin',gzcompress(serialize($this->extXMLResult)));
142 t3lib_div::writeFile(PATH_site.'typo3temp/reviewstates.bin',gzcompress(serialize($this->reviewStates)));
143 }
144
145 /**
146 * Frees the pre-parsed extension list
147 *
148 * @return void
149 */
150 function freeExtensionsXML() {
151 unset($this->extensionsXML);
152 $this->extensionsXML = array();
153 }
154
155 /**
156 * Removes all extension with a certain state from the list
157 *
158 * @param array &$extensions The "versions" subpart of the extension list
159 * @return void
160 */
161 function removeObsolete(&$extensions) {
162 if($this->useObsolete) return;
163
164 reset($extensions);
165 while (list($version, $data) = each($extensions)) {
166 if($data['state']=='obsolete')
167 unset($extensions[$version]);
168 }
169 }
170
171 /**
172 * Enter description here...
173 *
174 * @param string $extKey
175 * @param string $version: ...
176 * @return integer Review state, if none is set 0 is returned as default.
177 */
178 function getReviewState($extKey, $version) {
179 if(!is_array($this->reviewStates)) $this->loadReviewStates();
180
181 if(isset($this->reviewStates[$extKey])) {
182 return (int)$this->reviewStates[$extKey][$version];
183 } else {
184 return 0;
185 }
186 }
187
188 /**
189 * Removes all extension versions from $extensions that have a reviewstate<1, unless explicitly allowed
190 *
191 * @param array &$extensions The "versions" subpart of the extension list
192 * @return void
193 */
194 function checkReviewState(&$extensions) {
195 if($this->useUnsupported) return;
196
197 reset($extensions);
198 while (list($version, $data) = each($extensions)) {
199 if($data['reviewstate']<1)
200 unset($extensions[$version]);
201 }
202 }
203
204 /**
205 * Removes all extension versions from the list of available extensions that have a reviewstate<1, unless explicitly allowed
206 *
207 * @return void
208 */
209 function checkReviewStateGlobal() {
210 if($this->useUnsupported) return;
211
212 reset($this->extensionsXML);
213 while (list($extkey, $data) = each($this->extensionsXML)) {
214 while (list($version, $vdata) = each($data['versions'])) {
215 if($vdata['reviewstate']<1) unset($this->extensionsXML[$extkey]['versions'][$version]);
216 }
217 if(!count($this->extensionsXML[$extkey]['versions'])) unset($this->extensionsXML[$extkey]);
218 }
219 }
220
221
222 /**
223 * ***************PARSING METHODS***********************
224 */
225 /**
226 * Enter description here...
227 *
228 * @param unknown_type $parser
229 * @param unknown_type $name
230 * @param unknown_type $attrs
231 * @return [type] ...
232 */
233 function startElement($parser, $name, $attrs) {
234 switch($name) {
235 case 'extensions':
236 break;
237 case 'extension':
238 $this->currentExt = $attrs['extensionkey'];
239 break;
240 case 'version':
241 $this->currentVersion = $attrs['version'];
242 $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion] = array();
243 break;
244 default:
245 $this->currentTag = $name;
246 }
247 }
248
249 /**
250 * Enter description here...
251 *
252 * @param unknown_type $parser
253 * @param unknown_type $name
254 * @return [type] ...
255 */
256 function endElement($parser, $name) {
257 switch($name) {
258 case 'extension':
259 unset($this->currentExt);
260 break;
261 case 'version':
262 unset($this->currentVersion);
263 break;
264 default:
265 unset($this->currentTag);
266 }
267 }
268
269 /**
270 * Enter description here...
271 *
272 * @param unknown_type $parser
273 * @param unknown_type $data
274 * @return [type] ...
275 */
276 function characterData($parser, $data) {
277 if(isset($this->currentTag)) {
278 if(!isset($this->currentVersion) && $this->currentTag == 'downloadcounter') {
279 $this->extXMLResult[$this->currentExt]['downloadcounter'] = trim($data);
280 } elseif($this->currentTag == 'dependencies') {
281 $data = @unserialize($data);
282 if(is_array($data)) {
283 $dep = array();
284 foreach($data as $v) {
285 $dep[$v['kind']][$v['extensionKey']] = $v['versionRange'];
286 }
287 $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['dependencies'] = $dep;
288 }
289 } elseif($this->currentTag == 'reviewstate') {
290 $this->reviewStates[$this->currentExt][$this->currentVersion] = (int)trim($data);
291 $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['reviewstate'] = (int)trim($data);
292 } else {
293 $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion][$this->currentTag] .= trim($data);
294 }
295 }
296 }
297
298 /**
299 * Parses content of mirrors.xml into a suitable array
300 *
301 * @param string XML data file to parse
302 * @return string HTLML output informing about result
303 */
304 function parseExtensionsXML($string) {
305 global $TYPO3_CONF_VARS;
306
307 $parser = xml_parser_create();
308 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
309 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
310 xml_set_element_handler($parser, array(&$this,'startElement'), array(&$this,'endElement'));
311 xml_set_character_data_handler($parser, array(&$this,'characterData'));
312
313 if ((double)phpversion()>=5) {
314 $preg_result = array();
315 preg_match('/^[[:space:]]*<\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/',substr($string,0,200),$preg_result);
316 $theCharset = $preg_result[1] ? $preg_result[1] : ($TYPO3_CONF_VARS['BE']['forceCharset'] ? $TYPO3_CONF_VARS['BE']['forceCharset'] : 'iso-8859-1');
317 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset); // us-ascii / utf-8 / iso-8859-1
318 }
319
320 // Parse content:
321 if (!xml_parse($parser, $string)) {
322 $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));
323 $error = true;
324 }
325 xml_parser_free($parser);
326
327 if(!$error) {
328 $content.= '<p>The extensions list has been updated and now contains '.count($this->extXMLResult).' extension entries.</p>';
329 }
330
331 return $content;
332 }
333
334 /**
335 * Parses content of mirrors.xml into a suitable array
336 *
337 * @param string $string: XML data to parse
338 * @return string HTLML output informing about result
339 */
340 function parseMirrorsXML($string) {
341 global $TYPO3_CONF_VARS;
342
343 // Create parser:
344 $parser = xml_parser_create();
345 $vals = array();
346 $index = array();
347
348 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
349 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
350
351 if ((double)phpversion()>=5) {
352 $preg_result = array();
353 preg_match('/^[[:space:]]*<\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/',substr($string,0,200),$preg_result);
354 $theCharset = $preg_result[1] ? $preg_result[1] : ($TYPO3_CONF_VARS['BE']['forceCharset'] ? $TYPO3_CONF_VARS['BE']['forceCharset'] : 'iso-8859-1');
355 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset); // us-ascii / utf-8 / iso-8859-1
356 }
357
358 // Parse content:
359 xml_parse_into_struct($parser, $string, $vals, $index);
360
361 // If error, return error message:
362 if (xml_get_error_code($parser)) {
363 $line = xml_get_current_line_number($parser);
364 $error = xml_error_string(xml_get_error_code($parser));
365 xml_parser_free($parser);
366 return 'Error in XML parser while decoding mirrors XML file. Line '.$line.': '.$error;
367 } else {
368 // Init vars:
369 $stack = array(array());
370 $stacktop = 0;
371 $mirrornumber = 0;
372 $current=array();
373 $tagName = '';
374 $documentTag = '';
375
376 // Traverse the parsed XML structure:
377 foreach($vals as $val) {
378
379 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
380 $tagName = ($val['tag']=='mirror' && $val['type']=='open') ? '__plh' : $val['tag'];
381 if (!$documentTag) $documentTag = $tagName;
382
383 // Setting tag-values, manage stack:
384 switch($val['type']) {
385 case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
386 $current[$tagName] = array(); // Setting blank place holder
387 $stack[$stacktop++] = $current;
388 $current = array();
389 break;
390 case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
391 $oldCurrent = $current;
392 $current = $stack[--$stacktop];
393 end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next:
394 if($tagName=='mirror') {
395 unset($current['__plh']);
396 $current[$oldCurrent['host']] = $oldCurrent;
397 } else {
398 $current[key($current)] = $oldCurrent;
399 }
400 unset($oldCurrent);
401 break;
402 case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
403 $current[$tagName] = (string)$val['value']; // Had to cast it as a string - otherwise it would be evaluate false if tested with isset()!!
404 break;
405 }
406 }
407 return $current[$tagName];
408 }
409 }
410
411 /**
412 * Parses content of *-l10n.xml into a suitable array
413 *
414 * @param string $string: XML data to parse
415 * @return array Array representation of XML data
416 */
417 function parseL10nXML($string) {
418 // Create parser:
419 $parser = xml_parser_create();
420 $vals = array();
421 $index = array();
422
423 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
424 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
425
426 // Parse content:
427 xml_parse_into_struct($parser, $string, $vals, $index);
428
429 // If error, return error message:
430 if (xml_get_error_code($parser)) {
431 $line = xml_get_current_line_number($parser);
432 $error = xml_error_string(xml_get_error_code($parser));
433 debug($error);
434 xml_parser_free($parser);
435 return 'Error in XML parser while decoding l10n XML file. Line '.$line.': '.$error;
436 } else {
437 // Init vars:
438 $stack = array(array());
439 $stacktop = 0;
440 $mirrornumber = 0;
441 $current=array();
442 $tagName = '';
443 $documentTag = '';
444
445 // Traverse the parsed XML structure:
446 foreach($vals as $val) {
447
448 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
449 $tagName = ($val['tag']=='languagepack' && $val['type']=='open') ? $val['attributes']['language'] : $val['tag'];
450 if (!$documentTag) $documentTag = $tagName;
451
452 // Setting tag-values, manage stack:
453 switch($val['type']) {
454 case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array:
455 $current[$tagName] = array(); // Setting blank place holder
456 $stack[$stacktop++] = $current;
457 $current = array();
458 break;
459 case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
460 $oldCurrent = $current;
461 $current = $stack[--$stacktop];
462 end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next:
463 $current[key($current)] = $oldCurrent;
464 unset($oldCurrent);
465 break;
466 case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it.
467 $current[$tagName] = (string)$val['value']; // Had to cast it as a string - otherwise it would be evaluate false if tested with isset()!!
468 break;
469 }
470 }
471 return $current[$tagName];
472 }
473 }
474 }
475 ?>