1<?php 2/** 3 * PEAR_PackageFile, package.xml parsing utility class 4 * 5 * PHP versions 4 and 5 6 * 7 * @category pear 8 * @package PEAR 9 * @author Greg Beaver <cellog@php.net> 10 * @copyright 1997-2009 The Authors 11 * @license http://opensource.org/licenses/bsd-license.php New BSD License 12 * @link http://pear.php.net/package/PEAR 13 * @since File available since Release 1.4.0a1 14 */ 15 16/** 17 * needed for PEAR_VALIDATE_* constants 18 */ 19require_once 'PEAR/Validate.php'; 20/** 21 * Error code if the package.xml <package> tag does not contain a valid version 22 */ 23define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1); 24/** 25 * Error code if the package.xml <package> tag version is not supported (version 1.0 and 1.1 are the only supported versions, 26 * currently 27 */ 28define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2); 29/** 30 * Abstraction for the package.xml package description file 31 * 32 * @category pear 33 * @package PEAR 34 * @author Greg Beaver <cellog@php.net> 35 * @copyright 1997-2009 The Authors 36 * @license http://opensource.org/licenses/bsd-license.php New BSD License 37 * @version Release: @PEAR-VER@ 38 * @link http://pear.php.net/package/PEAR 39 * @since Class available since Release 1.4.0a1 40 */ 41class PEAR_PackageFile 42{ 43 /** 44 * @var PEAR_Config 45 */ 46 var $_config; 47 var $_debug; 48 49 var $_logger = false; 50 /** 51 * @var boolean 52 */ 53 var $_rawReturn = false; 54 55 /** 56 * helper for extracting Archive_Tar errors 57 * @var array 58 * @access private 59 */ 60 var $_extractErrors = array(); 61 62 /** 63 * 64 * @param PEAR_Config $config 65 * @param ? $debug 66 * @param string @tmpdir Optional temporary directory for uncompressing 67 * files 68 */ 69 function __construct(&$config, $debug = false) 70 { 71 $this->_config = $config; 72 $this->_debug = $debug; 73 } 74 75 /** 76 * Turn off validation - return a parsed package.xml without checking it 77 * 78 * This is used by the package-validate command 79 */ 80 function rawReturn() 81 { 82 $this->_rawReturn = true; 83 } 84 85 function setLogger(&$l) 86 { 87 $this->_logger = &$l; 88 } 89 90 /** 91 * Create a PEAR_PackageFile_Parser_v* of a given version. 92 * @param int $version 93 * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1 94 */ 95 function &parserFactory($version) 96 { 97 if (!in_array($version{0}, array('1', '2'))) { 98 $a = false; 99 return $a; 100 } 101 102 include_once 'PEAR/PackageFile/Parser/v' . $version{0} . '.php'; 103 $version = $version{0}; 104 $class = "PEAR_PackageFile_Parser_v$version"; 105 $a = new $class; 106 return $a; 107 } 108 109 /** 110 * For simpler unit-testing 111 * @return string 112 */ 113 function getClassPrefix() 114 { 115 return 'PEAR_PackageFile_v'; 116 } 117 118 /** 119 * Create a PEAR_PackageFile_v* of a given version. 120 * @param int $version 121 * @return PEAR_PackageFile_v1|PEAR_PackageFile_v1 122 */ 123 function &factory($version) 124 { 125 if (!in_array($version{0}, array('1', '2'))) { 126 $a = false; 127 return $a; 128 } 129 130 include_once 'PEAR/PackageFile/v' . $version{0} . '.php'; 131 $version = $version{0}; 132 $class = $this->getClassPrefix() . $version; 133 $a = new $class; 134 return $a; 135 } 136 137 /** 138 * Create a PEAR_PackageFile_v* from its toArray() method 139 * 140 * WARNING: no validation is performed, the array is assumed to be valid, 141 * always parse from xml if you want validation. 142 * @param array $arr 143 * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2 144 * @uses factory() to construct the returned object. 145 */ 146 function &fromArray($arr) 147 { 148 if (isset($arr['xsdversion'])) { 149 $obj = &$this->factory($arr['xsdversion']); 150 if ($this->_logger) { 151 $obj->setLogger($this->_logger); 152 } 153 154 $obj->setConfig($this->_config); 155 $obj->fromArray($arr); 156 return $obj; 157 } 158 159 if (isset($arr['package']['attribs']['version'])) { 160 $obj = &$this->factory($arr['package']['attribs']['version']); 161 } else { 162 $obj = &$this->factory('1.0'); 163 } 164 165 if ($this->_logger) { 166 $obj->setLogger($this->_logger); 167 } 168 169 $obj->setConfig($this->_config); 170 $obj->fromArray($arr); 171 return $obj; 172 } 173 174 /** 175 * Create a PEAR_PackageFile_v* from an XML string. 176 * @access public 177 * @param string $data contents of package.xml file 178 * @param int $state package state (one of PEAR_VALIDATE_* constants) 179 * @param string $file full path to the package.xml file (and the files 180 * it references) 181 * @param string $archive optional name of the archive that the XML was 182 * extracted from, if any 183 * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 184 * @uses parserFactory() to construct a parser to load the package. 185 */ 186 function &fromXmlString($data, $state, $file, $archive = false) 187 { 188 if (preg_match('/<package[^>]+version=[\'"]([0-9]+\.[0-9]+)[\'"]/', $data, $packageversion)) { 189 if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) { 190 return PEAR::raiseError('package.xml version "' . $packageversion[1] . 191 '" is not supported, only 1.0, 2.0, and 2.1 are supported.'); 192 } 193 194 $object = &$this->parserFactory($packageversion[1]); 195 if ($this->_logger) { 196 $object->setLogger($this->_logger); 197 } 198 199 $object->setConfig($this->_config); 200 $pf = $object->parse($data, $file, $archive); 201 if (PEAR::isError($pf)) { 202 return $pf; 203 } 204 205 if ($this->_rawReturn) { 206 return $pf; 207 } 208 209 if (!$pf->validate($state)) {; 210 if ($this->_config->get('verbose') > 0 211 && $this->_logger && $pf->getValidationWarnings(false) 212 ) { 213 foreach ($pf->getValidationWarnings(false) as $warning) { 214 $this->_logger->log(0, 'ERROR: ' . $warning['message']); 215 } 216 } 217 218 $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', 219 2, null, null, $pf->getValidationWarnings()); 220 return $a; 221 } 222 223 if ($this->_logger && $pf->getValidationWarnings(false)) { 224 foreach ($pf->getValidationWarnings() as $warning) { 225 $this->_logger->log(0, 'WARNING: ' . $warning['message']); 226 } 227 } 228 229 if (method_exists($pf, 'flattenFilelist')) { 230 $pf->flattenFilelist(); // for v2 231 } 232 233 return $pf; 234 } elseif (preg_match('/<package[^>]+version=[\'"]([^"\']+)[\'"]/', $data, $packageversion)) { 235 $a = PEAR::raiseError('package.xml file "' . $file . 236 '" has unsupported package.xml <package> version "' . $packageversion[1] . '"'); 237 return $a; 238 } else { 239 if (!class_exists('PEAR_ErrorStack')) { 240 require_once 'PEAR/ErrorStack.php'; 241 } 242 243 PEAR_ErrorStack::staticPush('PEAR_PackageFile', 244 PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION, 245 'warning', array('xml' => $data), 'package.xml "' . $file . 246 '" has no package.xml <package> version'); 247 $object = &$this->parserFactory('1.0'); 248 $object->setConfig($this->_config); 249 $pf = $object->parse($data, $file, $archive); 250 if (PEAR::isError($pf)) { 251 return $pf; 252 } 253 254 if ($this->_rawReturn) { 255 return $pf; 256 } 257 258 if (!$pf->validate($state)) { 259 $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', 260 2, null, null, $pf->getValidationWarnings()); 261 return $a; 262 } 263 264 if ($this->_logger && $pf->getValidationWarnings(false)) { 265 foreach ($pf->getValidationWarnings() as $warning) { 266 $this->_logger->log(0, 'WARNING: ' . $warning['message']); 267 } 268 } 269 270 if (method_exists($pf, 'flattenFilelist')) { 271 $pf->flattenFilelist(); // for v2 272 } 273 274 return $pf; 275 } 276 } 277 278 /** 279 * Register a temporary file or directory. When the destructor is 280 * executed, all registered temporary files and directories are 281 * removed. 282 * 283 * @param string $file name of file or directory 284 * @return void 285 */ 286 static function addTempFile($file) 287 { 288 $GLOBALS['_PEAR_Common_tempfiles'][] = $file; 289 } 290 291 /** 292 * Create a PEAR_PackageFile_v* from a compressed Tar or Tgz file. 293 * @access public 294 * @param string contents of package.xml file 295 * @param int package state (one of PEAR_VALIDATE_* constants) 296 * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 297 * @using Archive_Tar to extract the files 298 * @using fromPackageFile() to load the package after the package.xml 299 * file is extracted. 300 */ 301 function &fromTgzFile($file, $state) 302 { 303 if (!class_exists('Archive_Tar')) { 304 require_once 'Archive/Tar.php'; 305 } 306 307 $tar = new Archive_Tar($file); 308 if ($this->_debug <= 1) { 309 $tar->pushErrorHandling(PEAR_ERROR_RETURN); 310 } 311 312 $content = $tar->listContent(); 313 if ($this->_debug <= 1) { 314 $tar->popErrorHandling(); 315 } 316 317 if (!is_array($content)) { 318 if (is_string($file) && strlen($file) < 255 && 319 (!file_exists($file) || !@is_file($file))) { 320 $ret = PEAR::raiseError("could not open file \"$file\""); 321 return $ret; 322 } 323 324 $file = realpath($file); 325 $ret = PEAR::raiseError("Could not get contents of package \"$file\"". 326 '. Invalid tgz file.'); 327 return $ret; 328 } 329 330 if (!count($content) && !@is_file($file)) { 331 $ret = PEAR::raiseError("could not open file \"$file\""); 332 return $ret; 333 } 334 335 $xml = null; 336 $origfile = $file; 337 foreach ($content as $file) { 338 $name = $file['filename']; 339 if ($name == 'package2.xml') { // allow a .tgz to distribute both versions 340 $xml = $name; 341 break; 342 } 343 344 if ($name == 'package.xml') { 345 $xml = $name; 346 break; 347 } elseif (preg_match('/package.xml$/', $name, $match)) { 348 $xml = $name; 349 break; 350 } 351 } 352 353 $tmpdir = System::mktemp('-t "' . $this->_config->get('temp_dir') . '" -d pear'); 354 if ($tmpdir === false) { 355 $ret = PEAR::raiseError("there was a problem with getting the configured temp directory"); 356 return $ret; 357 } 358 359 PEAR_PackageFile::addTempFile($tmpdir); 360 361 $this->_extractErrors(); 362 PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors')); 363 364 if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { 365 $extra = implode("\n", $this->_extractErrors()); 366 if ($extra) { 367 $extra = ' ' . $extra; 368 } 369 370 PEAR::staticPopErrorHandling(); 371 $ret = PEAR::raiseError('could not extract the package.xml file from "' . 372 $origfile . '"' . $extra); 373 return $ret; 374 } 375 376 PEAR::staticPopErrorHandling(); 377 $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile); 378 return $ret; 379 } 380 381 /** 382 * helper callback for extracting Archive_Tar errors 383 * 384 * @param PEAR_Error|null $err 385 * @return array 386 * @access private 387 */ 388 function _extractErrors($err = null) 389 { 390 static $errors = array(); 391 if ($err === null) { 392 $e = $errors; 393 $errors = array(); 394 return $e; 395 } 396 $errors[] = $err->getMessage(); 397 } 398 399 /** 400 * Create a PEAR_PackageFile_v* from a package.xml file. 401 * 402 * @access public 403 * @param string $descfile name of package xml file 404 * @param int $state package state (one of PEAR_VALIDATE_* constants) 405 * @param string|false $archive name of the archive this package.xml came 406 * from, if any 407 * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 408 * @uses PEAR_PackageFile::fromXmlString to create the oject after the 409 * XML is loaded from the package.xml file. 410 */ 411 function &fromPackageFile($descfile, $state, $archive = false) 412 { 413 $fp = false; 414 if (is_string($descfile) && strlen($descfile) < 255 && 415 ( 416 !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) 417 || (!$fp = @fopen($descfile, 'r')) 418 ) 419 ) { 420 $a = PEAR::raiseError("Unable to open $descfile"); 421 return $a; 422 } 423 424 // read the whole thing so we only get one cdata callback 425 // for each block of cdata 426 fclose($fp); 427 $data = file_get_contents($descfile); 428 $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive); 429 return $ret; 430 } 431 432 /** 433 * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file. 434 * 435 * This method is able to extract information about a package from a .tgz 436 * archive or from a XML package definition file. 437 * 438 * @access public 439 * @param string $info file name 440 * @param int $state package state (one of PEAR_VALIDATE_* constants) 441 * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 442 * @uses fromPackageFile() if the file appears to be XML 443 * @uses fromTgzFile() to load all non-XML files 444 */ 445 function &fromAnyFile($info, $state) 446 { 447 if (is_dir($info)) { 448 $dir_name = realpath($info); 449 if (file_exists($dir_name . '/package.xml')) { 450 $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state); 451 } elseif (file_exists($dir_name . '/package2.xml')) { 452 $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state); 453 } else { 454 $info = PEAR::raiseError("No package definition found in '$info' directory"); 455 } 456 457 return $info; 458 } 459 460 $fp = false; 461 if (is_string($info) && strlen($info) < 255 && 462 (file_exists($info) || ($fp = @fopen($info, 'r'))) 463 ) { 464 465 if ($fp) { 466 fclose($fp); 467 } 468 469 $tmp = substr($info, -4); 470 if ($tmp == '.xml') { 471 $info = &PEAR_PackageFile::fromPackageFile($info, $state); 472 } elseif ($tmp == '.tar' || $tmp == '.tgz') { 473 $info = &PEAR_PackageFile::fromTgzFile($info, $state); 474 } else { 475 $fp = fopen($info, 'r'); 476 $test = fread($fp, 5); 477 fclose($fp); 478 if ($test == '<?xml') { 479 $info = &PEAR_PackageFile::fromPackageFile($info, $state); 480 } else { 481 $info = &PEAR_PackageFile::fromTgzFile($info, $state); 482 } 483 } 484 485 return $info; 486 } 487 488 $info = PEAR::raiseError("Cannot open '$info' for parsing"); 489 return $info; 490 } 491} 492