* @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ /** * needed for PEAR_VALIDATE_* constants */ require_once 'PEAR/Validate.php'; /** * Error code if the package.xml tag does not contain a valid version */ define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1); /** * Error code if the package.xml tag version is not supported (version 1.0 and 1.1 are the only supported versions, * currently */ define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2); /** * Abstraction for the package.xml package description file * * @category pear * @package PEAR * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License * @version Release: 1.10.15 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ class PEAR_PackageFile { /** * @var PEAR_Config */ var $_config; var $_debug; var $_logger = false; /** * @var boolean */ var $_rawReturn = false; /** * helper for extracting Archive_Tar errors * @var array * @access private */ var $_extractErrors = array(); /** * * @param PEAR_Config $config * @param ? $debug * @param string @tmpdir Optional temporary directory for uncompressing * files */ function __construct(&$config, $debug = false) { $this->_config = $config; $this->_debug = $debug; } /** * Turn off validation - return a parsed package.xml without checking it * * This is used by the package-validate command */ function rawReturn() { $this->_rawReturn = true; } function setLogger(&$l) { $this->_logger = &$l; } /** * Create a PEAR_PackageFile_Parser_v* of a given version. * @param int $version * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1 */ function &parserFactory($version) { if (!in_array($version[0], array('1', '2'))) { $a = false; return $a; } include_once 'PEAR/PackageFile/Parser/v' . $version[0] . '.php'; $version = $version[0]; $class = "PEAR_PackageFile_Parser_v$version"; $a = new $class; return $a; } /** * For simpler unit-testing * @return string */ function getClassPrefix() { return 'PEAR_PackageFile_v'; } /** * Create a PEAR_PackageFile_v* of a given version. * @param int $version * @return PEAR_PackageFile_v1|PEAR_PackageFile_v1 */ function &factory($version) { if (!in_array($version[0], array('1', '2'))) { $a = false; return $a; } include_once 'PEAR/PackageFile/v' . $version[0] . '.php'; $version = $version[0]; $class = $this->getClassPrefix() . $version; $a = new $class; return $a; } /** * Create a PEAR_PackageFile_v* from its toArray() method * * WARNING: no validation is performed, the array is assumed to be valid, * always parse from xml if you want validation. * @param array $arr * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2 * @uses factory() to construct the returned object. */ function &fromArray($arr) { if (isset($arr['xsdversion'])) { $obj = &$this->factory($arr['xsdversion']); if ($this->_logger) { $obj->setLogger($this->_logger); } $obj->setConfig($this->_config); $obj->fromArray($arr); return $obj; } if (isset($arr['package']['attribs']['version'])) { $obj = &$this->factory($arr['package']['attribs']['version']); } else { $obj = &$this->factory('1.0'); } if ($this->_logger) { $obj->setLogger($this->_logger); } $obj->setConfig($this->_config); $obj->fromArray($arr); return $obj; } /** * Create a PEAR_PackageFile_v* from an XML string. * @access public * @param string $data contents of package.xml file * @param int $state package state (one of PEAR_VALIDATE_* constants) * @param string $file full path to the package.xml file (and the files * it references) * @param string $archive optional name of the archive that the XML was * extracted from, if any * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 * @uses parserFactory() to construct a parser to load the package. */ function &fromXmlString($data, $state, $file, $archive = false) { if (preg_match('/]+version=[\'"]([0-9]+\.[0-9]+)[\'"]/', $data, $packageversion)) { if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) { return PEAR::raiseError('package.xml version "' . $packageversion[1] . '" is not supported, only 1.0, 2.0, and 2.1 are supported.'); } $object = &$this->parserFactory($packageversion[1]); if ($this->_logger) { $object->setLogger($this->_logger); } $object->setConfig($this->_config); $pf = $object->parse($data, $file, $archive); if (PEAR::isError($pf)) { return $pf; } if ($this->_rawReturn) { return $pf; } if (!$pf->validate($state)) {; if ($this->_config->get('verbose') > 0 && $this->_logger && $pf->getValidationWarnings(false) ) { foreach ($pf->getValidationWarnings(false) as $warning) { $this->_logger->log(0, 'ERROR: ' . $warning['message']); } } $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', 2, null, null, $pf->getValidationWarnings()); return $a; } if ($this->_logger && $pf->getValidationWarnings(false)) { foreach ($pf->getValidationWarnings() as $warning) { $this->_logger->log(0, 'WARNING: ' . $warning['message']); } } if (method_exists($pf, 'flattenFilelist')) { $pf->flattenFilelist(); // for v2 } return $pf; } elseif (preg_match('/]+version=[\'"]([^"\']+)[\'"]/', $data, $packageversion)) { $a = PEAR::raiseError('package.xml file "' . $file . '" has unsupported package.xml version "' . $packageversion[1] . '"'); return $a; } else { if (!class_exists('PEAR_ErrorStack')) { require_once 'PEAR/ErrorStack.php'; } PEAR_ErrorStack::staticPush('PEAR_PackageFile', PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION, 'warning', array('xml' => $data), 'package.xml "' . $file . '" has no package.xml version'); $object = &$this->parserFactory('1.0'); $object->setConfig($this->_config); $pf = $object->parse($data, $file, $archive); if (PEAR::isError($pf)) { return $pf; } if ($this->_rawReturn) { return $pf; } if (!$pf->validate($state)) { $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', 2, null, null, $pf->getValidationWarnings()); return $a; } if ($this->_logger && $pf->getValidationWarnings(false)) { foreach ($pf->getValidationWarnings() as $warning) { $this->_logger->log(0, 'WARNING: ' . $warning['message']); } } if (method_exists($pf, 'flattenFilelist')) { $pf->flattenFilelist(); // for v2 } return $pf; } } /** * Register a temporary file or directory. When the destructor is * executed, all registered temporary files and directories are * removed. * * @param string $file name of file or directory * @return void */ static function addTempFile($file) { $GLOBALS['_PEAR_Common_tempfiles'][] = $file; } /** * Create a PEAR_PackageFile_v* from a compressed Tar or Tgz file. * @access public * @param string contents of package.xml file * @param int package state (one of PEAR_VALIDATE_* constants) * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 * @using Archive_Tar to extract the files * @using fromPackageFile() to load the package after the package.xml * file is extracted. */ function &fromTgzFile($file, $state) { if (!class_exists('Archive_Tar')) { require_once 'Archive/Tar.php'; } $tar = new Archive_Tar($file); if ($this->_debug <= 1) { $tar->pushErrorHandling(PEAR_ERROR_RETURN); } $content = $tar->listContent(); if ($this->_debug <= 1) { $tar->popErrorHandling(); } if (!is_array($content)) { if (is_string($file) && strlen($file) < 255 && (!file_exists($file) || !@is_file($file))) { $ret = PEAR::raiseError("could not open file \"$file\""); return $ret; } $file = realpath($file); $ret = PEAR::raiseError("Could not get contents of package \"$file\"". '. Invalid tgz file.'); return $ret; } if (!count($content) && !@is_file($file)) { $ret = PEAR::raiseError("could not open file \"$file\""); return $ret; } $xml = null; $origfile = $file; foreach ($content as $file) { $name = $file['filename']; if ($name == 'package2.xml') { // allow a .tgz to distribute both versions $xml = $name; break; } if ($name == 'package.xml') { $xml = $name; break; } elseif (preg_match('/package.xml$/', $name, $match)) { $xml = $name; break; } } $tmpdir = System::mktemp('-t "' . $this->_config->get('temp_dir') . '" -d pear'); if ($tmpdir === false) { $ret = PEAR::raiseError("there was a problem with getting the configured temp directory"); return $ret; } PEAR_PackageFile::addTempFile($tmpdir); $this->_extractErrors(); PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors')); if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { $extra = implode("\n", $this->_extractErrors()); if ($extra) { $extra = ' ' . $extra; } PEAR::staticPopErrorHandling(); $ret = PEAR::raiseError('could not extract the package.xml file from "' . $origfile . '"' . $extra); return $ret; } PEAR::staticPopErrorHandling(); $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile); return $ret; } /** * helper callback for extracting Archive_Tar errors * * @param PEAR_Error|null $err * @return array * @access private */ function _extractErrors($err = null) { static $errors = array(); if ($err === null) { $e = $errors; $errors = array(); return $e; } $errors[] = $err->getMessage(); } /** * Create a PEAR_PackageFile_v* from a package.xml file. * * @access public * @param string $descfile name of package xml file * @param int $state package state (one of PEAR_VALIDATE_* constants) * @param string|false $archive name of the archive this package.xml came * from, if any * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 * @uses PEAR_PackageFile::fromXmlString to create the oject after the * XML is loaded from the package.xml file. */ function &fromPackageFile($descfile, $state, $archive = false) { $fp = false; if (is_string($descfile) && strlen($descfile) < 255 && ( !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) || (!$fp = @fopen($descfile, 'r')) ) ) { $a = PEAR::raiseError("Unable to open $descfile"); return $a; } // read the whole thing so we only get one cdata callback // for each block of cdata fclose($fp); $data = file_get_contents($descfile); $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive); return $ret; } /** * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file. * * This method is able to extract information about a package from a .tgz * archive or from a XML package definition file. * * @access public * @param string $info file name * @param int $state package state (one of PEAR_VALIDATE_* constants) * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 * @uses fromPackageFile() if the file appears to be XML * @uses fromTgzFile() to load all non-XML files */ function &fromAnyFile($info, $state) { if (is_dir($info)) { $dir_name = realpath($info); if (file_exists($dir_name . '/package.xml')) { $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state); } elseif (file_exists($dir_name . '/package2.xml')) { $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state); } else { $info = PEAR::raiseError("No package definition found in '$info' directory"); } return $info; } $fp = false; if (is_string($info) && strlen($info) < 255 && (file_exists($info) || ($fp = @fopen($info, 'r'))) ) { if ($fp) { fclose($fp); } $tmp = substr($info, -4); if ($tmp == '.xml') { $info = &PEAR_PackageFile::fromPackageFile($info, $state); } elseif ($tmp == '.tar' || $tmp == '.tgz') { $info = &PEAR_PackageFile::fromTgzFile($info, $state); } else { $fp = fopen($info, 'r'); $test = fread($fp, 5); fclose($fp); if ($test == '