2095 lines
70 KiB
PHP
2095 lines
70 KiB
PHP
|
<?php
|
||
|
//
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | PHP Version 4 |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | Copyright (c) 1997-2003 The PHP Group |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | This source file is subject to version 3.0 of the PHP license, |
|
||
|
// | that is bundled with this package in the file LICENSE, and is |
|
||
|
// | available through the world-wide-web at the following url: |
|
||
|
// | http://www.php.net/license/3_0.txt. |
|
||
|
// | If you did not receive a copy of the PHP license and are unable to |
|
||
|
// | obtain it through the world-wide-web, please send a note to |
|
||
|
// | license@php.net so we can mail you a copy immediately. |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | Authors: Stig Bakken <ssb@php.net> |
|
||
|
// | Tomas V.V.Cox <cox@idecnet.com> |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
//
|
||
|
// $Id: Common.php,v 1.126.2.2 2004/12/27 07:04:19 cellog Exp $
|
||
|
|
||
|
require_once 'PEAR.php';
|
||
|
require_once 'Archive/Tar.php';
|
||
|
require_once 'System.php';
|
||
|
require_once 'PEAR/Config.php';
|
||
|
|
||
|
// {{{ constants and globals
|
||
|
|
||
|
/**
|
||
|
* PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode()
|
||
|
*/
|
||
|
define('PEAR_COMMON_ERROR_INVALIDPHP', 1);
|
||
|
define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+');
|
||
|
define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '$/');
|
||
|
|
||
|
// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1
|
||
|
define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?');
|
||
|
define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '$/i');
|
||
|
|
||
|
// XXX far from perfect :-)
|
||
|
define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^(' . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?$/');
|
||
|
|
||
|
/**
|
||
|
* List of temporary files and directories registered by
|
||
|
* PEAR_Common::addTempFile().
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_tempfiles'] = array();
|
||
|
|
||
|
/**
|
||
|
* Valid maintainer roles
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper');
|
||
|
|
||
|
/**
|
||
|
* Valid release states
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel');
|
||
|
|
||
|
/**
|
||
|
* Valid dependency types
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi');
|
||
|
|
||
|
/**
|
||
|
* Valid dependency relations
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne');
|
||
|
|
||
|
/**
|
||
|
* Valid file roles
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script');
|
||
|
|
||
|
/**
|
||
|
* Valid replacement types
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info');
|
||
|
|
||
|
/**
|
||
|
* Valid "provide" types
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api');
|
||
|
|
||
|
/**
|
||
|
* Valid "provide" types
|
||
|
* @var array
|
||
|
*/
|
||
|
$GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup');
|
||
|
|
||
|
// }}}
|
||
|
|
||
|
/**
|
||
|
* Class providing common functionality for PEAR administration classes.
|
||
|
* @deprecated This class will disappear, and its components will be spread
|
||
|
* into smaller classes, like the AT&T breakup
|
||
|
*/
|
||
|
class PEAR_Common extends PEAR
|
||
|
{
|
||
|
// {{{ properties
|
||
|
|
||
|
/** stack of elements, gives some sort of XML context */
|
||
|
var $element_stack = array();
|
||
|
|
||
|
/** name of currently parsed XML element */
|
||
|
var $current_element;
|
||
|
|
||
|
/** array of attributes of the currently parsed XML element */
|
||
|
var $current_attributes = array();
|
||
|
|
||
|
/** assoc with information about a package */
|
||
|
var $pkginfo = array();
|
||
|
|
||
|
/**
|
||
|
* User Interface object (PEAR_Frontend_* class). If null,
|
||
|
* the log() method uses print.
|
||
|
* @var object
|
||
|
*/
|
||
|
var $ui = null;
|
||
|
|
||
|
/**
|
||
|
* Configuration object (PEAR_Config).
|
||
|
* @var object
|
||
|
*/
|
||
|
var $config = null;
|
||
|
|
||
|
var $current_path = null;
|
||
|
|
||
|
/**
|
||
|
* PEAR_SourceAnalyzer instance
|
||
|
* @var object
|
||
|
*/
|
||
|
var $source_analyzer = null;
|
||
|
/**
|
||
|
* Flag variable used to mark a valid package file
|
||
|
* @var boolean
|
||
|
* @access private
|
||
|
*/
|
||
|
var $_validPackageFile;
|
||
|
|
||
|
// }}}
|
||
|
|
||
|
// {{{ constructor
|
||
|
|
||
|
/**
|
||
|
* PEAR_Common constructor
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function PEAR_Common()
|
||
|
{
|
||
|
parent::PEAR();
|
||
|
$this->config = &PEAR_Config::singleton();
|
||
|
$this->debug = $this->config->get('verbose');
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ destructor
|
||
|
|
||
|
/**
|
||
|
* PEAR_Common destructor
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _PEAR_Common()
|
||
|
{
|
||
|
// doesn't work due to bug #14744
|
||
|
//$tempfiles = $this->_tempfiles;
|
||
|
$tempfiles =& $GLOBALS['_PEAR_Common_tempfiles'];
|
||
|
while ($file = array_shift($tempfiles)) {
|
||
|
if (@is_dir($file)) {
|
||
|
System::rm(array('-rf', $file));
|
||
|
} elseif (file_exists($file)) {
|
||
|
unlink($file);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ addTempFile()
|
||
|
|
||
|
/**
|
||
|
* 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
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function addTempFile($file)
|
||
|
{
|
||
|
$GLOBALS['_PEAR_Common_tempfiles'][] = $file;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ mkDirHier()
|
||
|
|
||
|
/**
|
||
|
* Wrapper to System::mkDir(), creates a directory as well as
|
||
|
* any necessary parent directories.
|
||
|
*
|
||
|
* @param string $dir directory name
|
||
|
*
|
||
|
* @return bool TRUE on success, or a PEAR error
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function mkDirHier($dir)
|
||
|
{
|
||
|
$this->log(2, "+ create dir $dir");
|
||
|
return System::mkDir(array('-p', $dir));
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ log()
|
||
|
|
||
|
/**
|
||
|
* Logging method.
|
||
|
*
|
||
|
* @param int $level log level (0 is quiet, higher is noisier)
|
||
|
* @param string $msg message to write to the log
|
||
|
*
|
||
|
* @return void
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function log($level, $msg, $append_crlf = true)
|
||
|
{
|
||
|
if ($this->debug >= $level) {
|
||
|
if (is_object($this->ui)) {
|
||
|
$this->ui->log($msg, $append_crlf);
|
||
|
} else {
|
||
|
print "$msg\n";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ mkTempDir()
|
||
|
|
||
|
/**
|
||
|
* Create and register a temporary directory.
|
||
|
*
|
||
|
* @param string $tmpdir (optional) Directory to use as tmpdir.
|
||
|
* Will use system defaults (for example
|
||
|
* /tmp or c:\windows\temp) if not specified
|
||
|
*
|
||
|
* @return string name of created directory
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function mkTempDir($tmpdir = '')
|
||
|
{
|
||
|
if ($tmpdir) {
|
||
|
$topt = array('-t', $tmpdir);
|
||
|
} else {
|
||
|
$topt = array();
|
||
|
}
|
||
|
$topt = array_merge($topt, array('-d', 'pear'));
|
||
|
if (!$tmpdir = System::mktemp($topt)) {
|
||
|
return false;
|
||
|
}
|
||
|
$this->addTempFile($tmpdir);
|
||
|
return $tmpdir;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ setFrontendObject()
|
||
|
|
||
|
/**
|
||
|
* Set object that represents the frontend to be used.
|
||
|
*
|
||
|
* @param object Reference of the frontend object
|
||
|
* @return void
|
||
|
* @access public
|
||
|
*/
|
||
|
function setFrontendObject(&$ui)
|
||
|
{
|
||
|
$this->ui = &$ui;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
|
||
|
// {{{ _unIndent()
|
||
|
|
||
|
/**
|
||
|
* Unindent given string (?)
|
||
|
*
|
||
|
* @param string $str The string that has to be unindented.
|
||
|
* @return string
|
||
|
* @access private
|
||
|
*/
|
||
|
function _unIndent($str)
|
||
|
{
|
||
|
// remove leading newlines
|
||
|
$str = preg_replace('/^[\r\n]+/', '', $str);
|
||
|
// find whitespace at the beginning of the first line
|
||
|
$indent_len = strspn($str, " \t");
|
||
|
$indent = substr($str, 0, $indent_len);
|
||
|
$data = '';
|
||
|
// remove the same amount of whitespace from following lines
|
||
|
foreach (explode("\n", $str) as $line) {
|
||
|
if (substr($line, 0, $indent_len) == $indent) {
|
||
|
$data .= substr($line, $indent_len) . "\n";
|
||
|
}
|
||
|
}
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _element_start()
|
||
|
|
||
|
/**
|
||
|
* XML parser callback for starting elements. Used while package
|
||
|
* format version is not yet known.
|
||
|
*
|
||
|
* @param resource $xp XML parser resource
|
||
|
* @param string $name name of starting element
|
||
|
* @param array $attribs element attributes, name => value
|
||
|
*
|
||
|
* @return void
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _element_start($xp, $name, $attribs)
|
||
|
{
|
||
|
array_push($this->element_stack, $name);
|
||
|
$this->current_element = $name;
|
||
|
$spos = sizeof($this->element_stack) - 2;
|
||
|
$this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
|
||
|
$this->current_attributes = $attribs;
|
||
|
switch ($name) {
|
||
|
case 'package': {
|
||
|
$this->_validPackageFile = true;
|
||
|
if (isset($attribs['version'])) {
|
||
|
$vs = preg_replace('/[^0-9a-z]/', '_', $attribs['version']);
|
||
|
} else {
|
||
|
$vs = '1_0';
|
||
|
}
|
||
|
$elem_start = '_element_start_'. $vs;
|
||
|
$elem_end = '_element_end_'. $vs;
|
||
|
$cdata = '_pkginfo_cdata_'. $vs;
|
||
|
if (!method_exists($this, $elem_start) ||
|
||
|
!method_exists($this, $elem_end) ||
|
||
|
!method_exists($this, $cdata)) {
|
||
|
$this->raiseError("No handlers for package.xml version $attribs[version]");
|
||
|
return;
|
||
|
}
|
||
|
xml_set_element_handler($xp, $elem_start, $elem_end);
|
||
|
xml_set_character_data_handler($xp, $cdata);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _element_end()
|
||
|
|
||
|
/**
|
||
|
* XML parser callback for ending elements. Used while package
|
||
|
* format version is not yet known.
|
||
|
*
|
||
|
* @param resource $xp XML parser resource
|
||
|
* @param string $name name of ending element
|
||
|
*
|
||
|
* @return void
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _element_end($xp, $name)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
|
||
|
// Support for package DTD v1.0:
|
||
|
// {{{ _element_start_1_0()
|
||
|
|
||
|
/**
|
||
|
* XML parser callback for ending elements. Used for version 1.0
|
||
|
* packages.
|
||
|
*
|
||
|
* @param resource $xp XML parser resource
|
||
|
* @param string $name name of ending element
|
||
|
*
|
||
|
* @return void
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _element_start_1_0($xp, $name, $attribs)
|
||
|
{
|
||
|
array_push($this->element_stack, $name);
|
||
|
$this->current_element = $name;
|
||
|
$spos = sizeof($this->element_stack) - 2;
|
||
|
$this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
|
||
|
$this->current_attributes = $attribs;
|
||
|
$this->cdata = '';
|
||
|
switch ($name) {
|
||
|
case 'dir':
|
||
|
if ($this->in_changelog) {
|
||
|
break;
|
||
|
}
|
||
|
if ($attribs['name'] != '/') {
|
||
|
$this->dir_names[] = $attribs['name'];
|
||
|
}
|
||
|
if (isset($attribs['baseinstalldir'])) {
|
||
|
$this->dir_install = $attribs['baseinstalldir'];
|
||
|
}
|
||
|
if (isset($attribs['role'])) {
|
||
|
$this->dir_role = $attribs['role'];
|
||
|
}
|
||
|
break;
|
||
|
case 'file':
|
||
|
if ($this->in_changelog) {
|
||
|
break;
|
||
|
}
|
||
|
if (isset($attribs['name'])) {
|
||
|
$path = '';
|
||
|
if (count($this->dir_names)) {
|
||
|
foreach ($this->dir_names as $dir) {
|
||
|
$path .= $dir . DIRECTORY_SEPARATOR;
|
||
|
}
|
||
|
}
|
||
|
$path .= $attribs['name'];
|
||
|
unset($attribs['name']);
|
||
|
$this->current_path = $path;
|
||
|
$this->filelist[$path] = $attribs;
|
||
|
// Set the baseinstalldir only if the file don't have this attrib
|
||
|
if (!isset($this->filelist[$path]['baseinstalldir']) &&
|
||
|
isset($this->dir_install))
|
||
|
{
|
||
|
$this->filelist[$path]['baseinstalldir'] = $this->dir_install;
|
||
|
}
|
||
|
// Set the Role
|
||
|
if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
|
||
|
$this->filelist[$path]['role'] = $this->dir_role;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'replace':
|
||
|
if (!$this->in_changelog) {
|
||
|
$this->filelist[$this->current_path]['replacements'][] = $attribs;
|
||
|
}
|
||
|
break;
|
||
|
case 'maintainers':
|
||
|
$this->pkginfo['maintainers'] = array();
|
||
|
$this->m_i = 0; // maintainers array index
|
||
|
break;
|
||
|
case 'maintainer':
|
||
|
// compatibility check
|
||
|
if (!isset($this->pkginfo['maintainers'])) {
|
||
|
$this->pkginfo['maintainers'] = array();
|
||
|
$this->m_i = 0;
|
||
|
}
|
||
|
$this->pkginfo['maintainers'][$this->m_i] = array();
|
||
|
$this->current_maintainer =& $this->pkginfo['maintainers'][$this->m_i];
|
||
|
break;
|
||
|
case 'changelog':
|
||
|
$this->pkginfo['changelog'] = array();
|
||
|
$this->c_i = 0; // changelog array index
|
||
|
$this->in_changelog = true;
|
||
|
break;
|
||
|
case 'release':
|
||
|
if ($this->in_changelog) {
|
||
|
$this->pkginfo['changelog'][$this->c_i] = array();
|
||
|
$this->current_release = &$this->pkginfo['changelog'][$this->c_i];
|
||
|
} else {
|
||
|
$this->current_release = &$this->pkginfo;
|
||
|
}
|
||
|
break;
|
||
|
case 'deps':
|
||
|
if (!$this->in_changelog) {
|
||
|
$this->pkginfo['release_deps'] = array();
|
||
|
}
|
||
|
break;
|
||
|
case 'dep':
|
||
|
// dependencies array index
|
||
|
if (!$this->in_changelog) {
|
||
|
$this->d_i++;
|
||
|
$this->pkginfo['release_deps'][$this->d_i] = $attribs;
|
||
|
}
|
||
|
break;
|
||
|
case 'configureoptions':
|
||
|
if (!$this->in_changelog) {
|
||
|
$this->pkginfo['configure_options'] = array();
|
||
|
}
|
||
|
break;
|
||
|
case 'configureoption':
|
||
|
if (!$this->in_changelog) {
|
||
|
$this->pkginfo['configure_options'][] = $attribs;
|
||
|
}
|
||
|
break;
|
||
|
case 'provides':
|
||
|
if (empty($attribs['type']) || empty($attribs['name'])) {
|
||
|
break;
|
||
|
}
|
||
|
$attribs['explicit'] = true;
|
||
|
$this->pkginfo['provides']["$attribs[type];$attribs[name]"] = $attribs;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _element_end_1_0()
|
||
|
|
||
|
/**
|
||
|
* XML parser callback for ending elements. Used for version 1.0
|
||
|
* packages.
|
||
|
*
|
||
|
* @param resource $xp XML parser resource
|
||
|
* @param string $name name of ending element
|
||
|
*
|
||
|
* @return void
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _element_end_1_0($xp, $name)
|
||
|
{
|
||
|
$data = trim($this->cdata);
|
||
|
switch ($name) {
|
||
|
case 'name':
|
||
|
switch ($this->prev_element) {
|
||
|
case 'package':
|
||
|
// XXX should we check the package name here?
|
||
|
$this->pkginfo['package'] = ereg_replace('[^a-zA-Z0-9._]', '_', $data);
|
||
|
break;
|
||
|
case 'maintainer':
|
||
|
$this->current_maintainer['name'] = $data;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case 'summary':
|
||
|
$this->pkginfo['summary'] = $data;
|
||
|
break;
|
||
|
case 'description':
|
||
|
$data = $this->_unIndent($this->cdata);
|
||
|
$this->pkginfo['description'] = $data;
|
||
|
break;
|
||
|
case 'user':
|
||
|
$this->current_maintainer['handle'] = $data;
|
||
|
break;
|
||
|
case 'email':
|
||
|
$this->current_maintainer['email'] = $data;
|
||
|
break;
|
||
|
case 'role':
|
||
|
$this->current_maintainer['role'] = $data;
|
||
|
break;
|
||
|
case 'version':
|
||
|
$data = ereg_replace ('[^a-zA-Z0-9._\-]', '_', $data);
|
||
|
if ($this->in_changelog) {
|
||
|
$this->current_release['version'] = $data;
|
||
|
} else {
|
||
|
$this->pkginfo['version'] = $data;
|
||
|
}
|
||
|
break;
|
||
|
case 'date':
|
||
|
if ($this->in_changelog) {
|
||
|
$this->current_release['release_date'] = $data;
|
||
|
} else {
|
||
|
$this->pkginfo['release_date'] = $data;
|
||
|
}
|
||
|
break;
|
||
|
case 'notes':
|
||
|
// try to "de-indent" release notes in case someone
|
||
|
// has been over-indenting their xml ;-)
|
||
|
$data = $this->_unIndent($this->cdata);
|
||
|
if ($this->in_changelog) {
|
||
|
$this->current_release['release_notes'] = $data;
|
||
|
} else {
|
||
|
$this->pkginfo['release_notes'] = $data;
|
||
|
}
|
||
|
break;
|
||
|
case 'warnings':
|
||
|
if ($this->in_changelog) {
|
||
|
$this->current_release['release_warnings'] = $data;
|
||
|
} else {
|
||
|
$this->pkginfo['release_warnings'] = $data;
|
||
|
}
|
||
|
break;
|
||
|
case 'state':
|
||
|
if ($this->in_changelog) {
|
||
|
$this->current_release['release_state'] = $data;
|
||
|
} else {
|
||
|
$this->pkginfo['release_state'] = $data;
|
||
|
}
|
||
|
break;
|
||
|
case 'license':
|
||
|
if ($this->in_changelog) {
|
||
|
$this->current_release['release_license'] = $data;
|
||
|
} else {
|
||
|
$this->pkginfo['release_license'] = $data;
|
||
|
}
|
||
|
break;
|
||
|
case 'dep':
|
||
|
if ($data && !$this->in_changelog) {
|
||
|
$this->pkginfo['release_deps'][$this->d_i]['name'] = $data;
|
||
|
}
|
||
|
break;
|
||
|
case 'dir':
|
||
|
if ($this->in_changelog) {
|
||
|
break;
|
||
|
}
|
||
|
array_pop($this->dir_names);
|
||
|
break;
|
||
|
case 'file':
|
||
|
if ($this->in_changelog) {
|
||
|
break;
|
||
|
}
|
||
|
if ($data) {
|
||
|
$path = '';
|
||
|
if (count($this->dir_names)) {
|
||
|
foreach ($this->dir_names as $dir) {
|
||
|
$path .= $dir . DIRECTORY_SEPARATOR;
|
||
|
}
|
||
|
}
|
||
|
$path .= $data;
|
||
|
$this->filelist[$path] = $this->current_attributes;
|
||
|
// Set the baseinstalldir only if the file don't have this attrib
|
||
|
if (!isset($this->filelist[$path]['baseinstalldir']) &&
|
||
|
isset($this->dir_install))
|
||
|
{
|
||
|
$this->filelist[$path]['baseinstalldir'] = $this->dir_install;
|
||
|
}
|
||
|
// Set the Role
|
||
|
if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
|
||
|
$this->filelist[$path]['role'] = $this->dir_role;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'maintainer':
|
||
|
if (empty($this->pkginfo['maintainers'][$this->m_i]['role'])) {
|
||
|
$this->pkginfo['maintainers'][$this->m_i]['role'] = 'lead';
|
||
|
}
|
||
|
$this->m_i++;
|
||
|
break;
|
||
|
case 'release':
|
||
|
if ($this->in_changelog) {
|
||
|
$this->c_i++;
|
||
|
}
|
||
|
break;
|
||
|
case 'changelog':
|
||
|
$this->in_changelog = false;
|
||
|
break;
|
||
|
}
|
||
|
array_pop($this->element_stack);
|
||
|
$spos = sizeof($this->element_stack) - 1;
|
||
|
$this->current_element = ($spos > 0) ? $this->element_stack[$spos] : '';
|
||
|
$this->cdata = '';
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _pkginfo_cdata_1_0()
|
||
|
|
||
|
/**
|
||
|
* XML parser callback for character data. Used for version 1.0
|
||
|
* packages.
|
||
|
*
|
||
|
* @param resource $xp XML parser resource
|
||
|
* @param string $name character data
|
||
|
*
|
||
|
* @return void
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _pkginfo_cdata_1_0($xp, $data)
|
||
|
{
|
||
|
if (isset($this->cdata)) {
|
||
|
$this->cdata .= $data;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
|
||
|
// {{{ infoFromTgzFile()
|
||
|
|
||
|
/**
|
||
|
* Returns information about a package file. Expects the name of
|
||
|
* a gzipped tar file as input.
|
||
|
*
|
||
|
* @param string $file name of .tgz file
|
||
|
*
|
||
|
* @return array array with package information
|
||
|
*
|
||
|
* @access public
|
||
|
*
|
||
|
*/
|
||
|
function infoFromTgzFile($file)
|
||
|
{
|
||
|
if (!@is_file($file)) {
|
||
|
return $this->raiseError("could not open file \"$file\"");
|
||
|
}
|
||
|
$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)) {
|
||
|
$file = realpath($file);
|
||
|
return $this->raiseError("Could not get contents of package \"$file\"".
|
||
|
'. Invalid tgz file.');
|
||
|
}
|
||
|
$xml = null;
|
||
|
foreach ($content as $file) {
|
||
|
$name = $file['filename'];
|
||
|
if ($name == 'package.xml') {
|
||
|
$xml = $name;
|
||
|
break;
|
||
|
} elseif (ereg('package.xml$', $name, $match)) {
|
||
|
$xml = $match[0];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
$tmpdir = System::mkTemp(array('-d', 'pear'));
|
||
|
$this->addTempFile($tmpdir);
|
||
|
if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
|
||
|
return $this->raiseError('could not extract the package.xml file');
|
||
|
}
|
||
|
return $this->infoFromDescriptionFile("$tmpdir/$xml");
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ infoFromDescriptionFile()
|
||
|
|
||
|
/**
|
||
|
* Returns information about a package file. Expects the name of
|
||
|
* a package xml file as input.
|
||
|
*
|
||
|
* @param string $descfile name of package xml file
|
||
|
*
|
||
|
* @return array array with package information
|
||
|
*
|
||
|
* @access public
|
||
|
*
|
||
|
*/
|
||
|
function infoFromDescriptionFile($descfile)
|
||
|
{
|
||
|
if (!@is_file($descfile) || !is_readable($descfile) ||
|
||
|
(!$fp = @fopen($descfile, 'r'))) {
|
||
|
return $this->raiseError("Unable to open $descfile");
|
||
|
}
|
||
|
|
||
|
// read the whole thing so we only get one cdata callback
|
||
|
// for each block of cdata
|
||
|
$data = fread($fp, filesize($descfile));
|
||
|
return $this->infoFromString($data);
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ infoFromString()
|
||
|
|
||
|
/**
|
||
|
* Returns information about a package file. Expects the contents
|
||
|
* of a package xml file as input.
|
||
|
*
|
||
|
* @param string $data name of package xml file
|
||
|
*
|
||
|
* @return array array with package information
|
||
|
*
|
||
|
* @access public
|
||
|
*
|
||
|
*/
|
||
|
function infoFromString($data)
|
||
|
{
|
||
|
require_once('PEAR/Dependency.php');
|
||
|
if (PEAR_Dependency::checkExtension($error, 'xml')) {
|
||
|
return $this->raiseError($error);
|
||
|
}
|
||
|
$xp = @xml_parser_create();
|
||
|
if (!$xp) {
|
||
|
return $this->raiseError('Unable to create XML parser');
|
||
|
}
|
||
|
xml_set_object($xp, $this);
|
||
|
xml_set_element_handler($xp, '_element_start', '_element_end');
|
||
|
xml_set_character_data_handler($xp, '_pkginfo_cdata');
|
||
|
xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);
|
||
|
|
||
|
$this->element_stack = array();
|
||
|
$this->pkginfo = array('provides' => array());
|
||
|
$this->current_element = false;
|
||
|
unset($this->dir_install);
|
||
|
$this->pkginfo['filelist'] = array();
|
||
|
$this->filelist =& $this->pkginfo['filelist'];
|
||
|
$this->dir_names = array();
|
||
|
$this->in_changelog = false;
|
||
|
$this->d_i = 0;
|
||
|
$this->cdata = '';
|
||
|
$this->_validPackageFile = false;
|
||
|
|
||
|
if (!xml_parse($xp, $data, 1)) {
|
||
|
$code = xml_get_error_code($xp);
|
||
|
$msg = sprintf("XML error: %s at line %d",
|
||
|
xml_error_string($code),
|
||
|
xml_get_current_line_number($xp));
|
||
|
xml_parser_free($xp);
|
||
|
return $this->raiseError($msg, $code);
|
||
|
}
|
||
|
|
||
|
xml_parser_free($xp);
|
||
|
|
||
|
if (!$this->_validPackageFile) {
|
||
|
return $this->raiseError('Invalid Package File, no <package> tag');
|
||
|
}
|
||
|
foreach ($this->pkginfo as $k => $v) {
|
||
|
if (!is_array($v)) {
|
||
|
$this->pkginfo[$k] = trim($v);
|
||
|
}
|
||
|
}
|
||
|
return $this->pkginfo;
|
||
|
}
|
||
|
// }}}
|
||
|
// {{{ infoFromAny()
|
||
|
|
||
|
/**
|
||
|
* Returns package information from different sources
|
||
|
*
|
||
|
* 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 Filename of the source ('package.xml', '<package>.tgz')
|
||
|
* @return string
|
||
|
*/
|
||
|
function infoFromAny($info)
|
||
|
{
|
||
|
if (is_string($info) && file_exists($info)) {
|
||
|
$tmp = substr($info, -4);
|
||
|
if ($tmp == '.xml') {
|
||
|
$info = $this->infoFromDescriptionFile($info);
|
||
|
} elseif ($tmp == '.tar' || $tmp == '.tgz') {
|
||
|
$info = $this->infoFromTgzFile($info);
|
||
|
} else {
|
||
|
$fp = fopen($info, "r");
|
||
|
$test = fread($fp, 5);
|
||
|
fclose($fp);
|
||
|
if ($test == "<?xml") {
|
||
|
$info = $this->infoFromDescriptionFile($info);
|
||
|
} else {
|
||
|
$info = $this->infoFromTgzFile($info);
|
||
|
}
|
||
|
}
|
||
|
if (PEAR::isError($info)) {
|
||
|
return $this->raiseError($info);
|
||
|
}
|
||
|
}
|
||
|
return $info;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ xmlFromInfo()
|
||
|
|
||
|
/**
|
||
|
* Return an XML document based on the package info (as returned
|
||
|
* by the PEAR_Common::infoFrom* methods).
|
||
|
*
|
||
|
* @param array $pkginfo package info
|
||
|
*
|
||
|
* @return string XML data
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function xmlFromInfo($pkginfo)
|
||
|
{
|
||
|
static $maint_map = array(
|
||
|
"handle" => "user",
|
||
|
"name" => "name",
|
||
|
"email" => "email",
|
||
|
"role" => "role",
|
||
|
);
|
||
|
$ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
|
||
|
$ret .= "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n";
|
||
|
$ret .= "<package version=\"1.0\">
|
||
|
<name>$pkginfo[package]</name>
|
||
|
<summary>".htmlspecialchars($pkginfo['summary'])."</summary>
|
||
|
<description>".htmlspecialchars($pkginfo['description'])."</description>
|
||
|
<maintainers>
|
||
|
";
|
||
|
foreach ($pkginfo['maintainers'] as $maint) {
|
||
|
$ret .= " <maintainer>\n";
|
||
|
foreach ($maint_map as $idx => $elm) {
|
||
|
$ret .= " <$elm>";
|
||
|
$ret .= htmlspecialchars($maint[$idx]);
|
||
|
$ret .= "</$elm>\n";
|
||
|
}
|
||
|
$ret .= " </maintainer>\n";
|
||
|
}
|
||
|
$ret .= " </maintainers>\n";
|
||
|
$ret .= $this->_makeReleaseXml($pkginfo);
|
||
|
if (@sizeof($pkginfo['changelog']) > 0) {
|
||
|
$ret .= " <changelog>\n";
|
||
|
foreach ($pkginfo['changelog'] as $oldrelease) {
|
||
|
$ret .= $this->_makeReleaseXml($oldrelease, true);
|
||
|
}
|
||
|
$ret .= " </changelog>\n";
|
||
|
}
|
||
|
$ret .= "</package>\n";
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _makeReleaseXml()
|
||
|
|
||
|
/**
|
||
|
* Generate part of an XML description with release information.
|
||
|
*
|
||
|
* @param array $pkginfo array with release information
|
||
|
* @param bool $changelog whether the result will be in a changelog element
|
||
|
*
|
||
|
* @return string XML data
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _makeReleaseXml($pkginfo, $changelog = false)
|
||
|
{
|
||
|
// XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!!
|
||
|
$indent = $changelog ? " " : "";
|
||
|
$ret = "$indent <release>\n";
|
||
|
if (!empty($pkginfo['version'])) {
|
||
|
$ret .= "$indent <version>$pkginfo[version]</version>\n";
|
||
|
}
|
||
|
if (!empty($pkginfo['release_date'])) {
|
||
|
$ret .= "$indent <date>$pkginfo[release_date]</date>\n";
|
||
|
}
|
||
|
if (!empty($pkginfo['release_license'])) {
|
||
|
$ret .= "$indent <license>$pkginfo[release_license]</license>\n";
|
||
|
}
|
||
|
if (!empty($pkginfo['release_state'])) {
|
||
|
$ret .= "$indent <state>$pkginfo[release_state]</state>\n";
|
||
|
}
|
||
|
if (!empty($pkginfo['release_notes'])) {
|
||
|
$ret .= "$indent <notes>".htmlspecialchars($pkginfo['release_notes'])."</notes>\n";
|
||
|
}
|
||
|
if (!empty($pkginfo['release_warnings'])) {
|
||
|
$ret .= "$indent <warnings>".htmlspecialchars($pkginfo['release_warnings'])."</warnings>\n";
|
||
|
}
|
||
|
if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) {
|
||
|
$ret .= "$indent <deps>\n";
|
||
|
foreach ($pkginfo['release_deps'] as $dep) {
|
||
|
$ret .= "$indent <dep type=\"$dep[type]\" rel=\"$dep[rel]\"";
|
||
|
if (isset($dep['version'])) {
|
||
|
$ret .= " version=\"$dep[version]\"";
|
||
|
}
|
||
|
if (isset($dep['optional'])) {
|
||
|
$ret .= " optional=\"$dep[optional]\"";
|
||
|
}
|
||
|
if (isset($dep['name'])) {
|
||
|
$ret .= ">$dep[name]</dep>\n";
|
||
|
} else {
|
||
|
$ret .= "/>\n";
|
||
|
}
|
||
|
}
|
||
|
$ret .= "$indent </deps>\n";
|
||
|
}
|
||
|
if (isset($pkginfo['configure_options'])) {
|
||
|
$ret .= "$indent <configureoptions>\n";
|
||
|
foreach ($pkginfo['configure_options'] as $c) {
|
||
|
$ret .= "$indent <configureoption name=\"".
|
||
|
htmlspecialchars($c['name']) . "\"";
|
||
|
if (isset($c['default'])) {
|
||
|
$ret .= " default=\"" . htmlspecialchars($c['default']) . "\"";
|
||
|
}
|
||
|
$ret .= " prompt=\"" . htmlspecialchars($c['prompt']) . "\"";
|
||
|
$ret .= "/>\n";
|
||
|
}
|
||
|
$ret .= "$indent </configureoptions>\n";
|
||
|
}
|
||
|
if (isset($pkginfo['provides'])) {
|
||
|
foreach ($pkginfo['provides'] as $key => $what) {
|
||
|
$ret .= "$indent <provides type=\"$what[type]\" ";
|
||
|
$ret .= "name=\"$what[name]\" ";
|
||
|
if (isset($what['extends'])) {
|
||
|
$ret .= "extends=\"$what[extends]\" ";
|
||
|
}
|
||
|
$ret .= "/>\n";
|
||
|
}
|
||
|
}
|
||
|
if (isset($pkginfo['filelist'])) {
|
||
|
$ret .= "$indent <filelist>\n";
|
||
|
foreach ($pkginfo['filelist'] as $file => $fa) {
|
||
|
@$ret .= "$indent <file role=\"$fa[role]\"";
|
||
|
if (isset($fa['baseinstalldir'])) {
|
||
|
$ret .= ' baseinstalldir="' .
|
||
|
htmlspecialchars($fa['baseinstalldir']) . '"';
|
||
|
}
|
||
|
if (isset($fa['md5sum'])) {
|
||
|
$ret .= " md5sum=\"$fa[md5sum]\"";
|
||
|
}
|
||
|
if (isset($fa['platform'])) {
|
||
|
$ret .= " platform=\"$fa[platform]\"";
|
||
|
}
|
||
|
if (!empty($fa['install-as'])) {
|
||
|
$ret .= ' install-as="' .
|
||
|
htmlspecialchars($fa['install-as']) . '"';
|
||
|
}
|
||
|
$ret .= ' name="' . htmlspecialchars($file) . '"';
|
||
|
if (empty($fa['replacements'])) {
|
||
|
$ret .= "/>\n";
|
||
|
} else {
|
||
|
$ret .= ">\n";
|
||
|
foreach ($fa['replacements'] as $r) {
|
||
|
$ret .= "$indent <replace";
|
||
|
foreach ($r as $k => $v) {
|
||
|
$ret .= " $k=\"" . htmlspecialchars($v) .'"';
|
||
|
}
|
||
|
$ret .= "/>\n";
|
||
|
}
|
||
|
@$ret .= "$indent </file>\n";
|
||
|
}
|
||
|
}
|
||
|
$ret .= "$indent </filelist>\n";
|
||
|
}
|
||
|
$ret .= "$indent </release>\n";
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ validatePackageInfo()
|
||
|
|
||
|
/**
|
||
|
* Validate XML package definition file.
|
||
|
*
|
||
|
* @param string $info Filename of the package archive or of the
|
||
|
* package definition file
|
||
|
* @param array $errors Array that will contain the errors
|
||
|
* @param array $warnings Array that will contain the warnings
|
||
|
* @param string $dir_prefix (optional) directory where source files
|
||
|
* may be found, or empty if they are not available
|
||
|
* @access public
|
||
|
* @return boolean
|
||
|
*/
|
||
|
function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '')
|
||
|
{
|
||
|
if (PEAR::isError($info = $this->infoFromAny($info))) {
|
||
|
return $this->raiseError($info);
|
||
|
}
|
||
|
if (!is_array($info)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$errors = array();
|
||
|
$warnings = array();
|
||
|
if (!isset($info['package'])) {
|
||
|
$errors[] = 'missing package name';
|
||
|
} elseif (!$this->validPackageName($info['package'])) {
|
||
|
$errors[] = 'invalid package name';
|
||
|
}
|
||
|
$this->_packageName = $pn = $info['package'];
|
||
|
|
||
|
if (empty($info['summary'])) {
|
||
|
$errors[] = 'missing summary';
|
||
|
} elseif (strpos(trim($info['summary']), "\n") !== false) {
|
||
|
$warnings[] = 'summary should be on a single line';
|
||
|
}
|
||
|
if (empty($info['description'])) {
|
||
|
$errors[] = 'missing description';
|
||
|
}
|
||
|
if (empty($info['release_license'])) {
|
||
|
$errors[] = 'missing license';
|
||
|
}
|
||
|
if (!isset($info['version'])) {
|
||
|
$errors[] = 'missing version';
|
||
|
} elseif (!$this->validPackageVersion($info['version'])) {
|
||
|
$errors[] = 'invalid package release version';
|
||
|
}
|
||
|
if (empty($info['release_state'])) {
|
||
|
$errors[] = 'missing release state';
|
||
|
} elseif (!in_array($info['release_state'], PEAR_Common::getReleaseStates())) {
|
||
|
$errors[] = "invalid release state `$info[release_state]', should be one of: "
|
||
|
. implode(' ', PEAR_Common::getReleaseStates());
|
||
|
}
|
||
|
if (empty($info['release_date'])) {
|
||
|
$errors[] = 'missing release date';
|
||
|
} elseif (!preg_match('/^\d{4}-\d\d-\d\d$/', $info['release_date'])) {
|
||
|
$errors[] = "invalid release date `$info[release_date]', format is YYYY-MM-DD";
|
||
|
}
|
||
|
if (empty($info['release_notes'])) {
|
||
|
$errors[] = "missing release notes";
|
||
|
}
|
||
|
if (empty($info['maintainers'])) {
|
||
|
$errors[] = 'no maintainer(s)';
|
||
|
} else {
|
||
|
$i = 1;
|
||
|
foreach ($info['maintainers'] as $m) {
|
||
|
if (empty($m['handle'])) {
|
||
|
$errors[] = "maintainer $i: missing handle";
|
||
|
}
|
||
|
if (empty($m['role'])) {
|
||
|
$errors[] = "maintainer $i: missing role";
|
||
|
} elseif (!in_array($m['role'], PEAR_Common::getUserRoles())) {
|
||
|
$errors[] = "maintainer $i: invalid role `$m[role]', should be one of: "
|
||
|
. implode(' ', PEAR_Common::getUserRoles());
|
||
|
}
|
||
|
if (empty($m['name'])) {
|
||
|
$errors[] = "maintainer $i: missing name";
|
||
|
}
|
||
|
if (empty($m['email'])) {
|
||
|
$errors[] = "maintainer $i: missing email";
|
||
|
}
|
||
|
$i++;
|
||
|
}
|
||
|
}
|
||
|
if (!empty($info['release_deps'])) {
|
||
|
$i = 1;
|
||
|
foreach ($info['release_deps'] as $d) {
|
||
|
if (empty($d['type'])) {
|
||
|
$errors[] = "dependency $i: missing type";
|
||
|
} elseif (!in_array($d['type'], PEAR_Common::getDependencyTypes())) {
|
||
|
$errors[] = "dependency $i: invalid type '$d[type]', should be one of: " .
|
||
|
implode(' ', PEAR_Common::getDependencyTypes());
|
||
|
}
|
||
|
if (empty($d['rel'])) {
|
||
|
$errors[] = "dependency $i: missing relation";
|
||
|
} elseif (!in_array($d['rel'], PEAR_Common::getDependencyRelations())) {
|
||
|
$errors[] = "dependency $i: invalid relation '$d[rel]', should be one of: "
|
||
|
. implode(' ', PEAR_Common::getDependencyRelations());
|
||
|
}
|
||
|
if (!empty($d['optional'])) {
|
||
|
if (!in_array($d['optional'], array('yes', 'no'))) {
|
||
|
$errors[] = "dependency $i: invalid relation optional attribute '$d[optional]', should be one of: yes no";
|
||
|
} else {
|
||
|
if (($d['rel'] == 'not' || $d['rel'] == 'ne') && $d['optional'] == 'yes') {
|
||
|
$errors[] = "dependency $i: 'not' and 'ne' dependencies cannot be " .
|
||
|
"optional";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ($d['rel'] != 'not' && $d['rel'] != 'has' && empty($d['version'])) {
|
||
|
$warnings[] = "dependency $i: missing version";
|
||
|
} elseif (($d['rel'] == 'not' || $d['rel'] == 'has') && !empty($d['version'])) {
|
||
|
$warnings[] = "dependency $i: version ignored for `$d[rel]' dependencies";
|
||
|
}
|
||
|
if ($d['rel'] == 'not' && !empty($d['version'])) {
|
||
|
$warnings[] = "dependency $i: 'not' defines a total conflict, to exclude " .
|
||
|
"specific versions, use 'ne'";
|
||
|
}
|
||
|
if ($d['type'] == 'php' && !empty($d['name'])) {
|
||
|
$warnings[] = "dependency $i: name ignored for php type dependencies";
|
||
|
} elseif ($d['type'] != 'php' && empty($d['name'])) {
|
||
|
$errors[] = "dependency $i: missing name";
|
||
|
}
|
||
|
if ($d['type'] == 'php' && $d['rel'] == 'not') {
|
||
|
$errors[] = "dependency $i: PHP dependencies cannot use 'not' " .
|
||
|
"rel, use 'ne' to exclude versions";
|
||
|
}
|
||
|
$i++;
|
||
|
}
|
||
|
}
|
||
|
if (!empty($info['configure_options'])) {
|
||
|
$i = 1;
|
||
|
foreach ($info['configure_options'] as $c) {
|
||
|
if (empty($c['name'])) {
|
||
|
$errors[] = "configure option $i: missing name";
|
||
|
}
|
||
|
if (empty($c['prompt'])) {
|
||
|
$errors[] = "configure option $i: missing prompt";
|
||
|
}
|
||
|
$i++;
|
||
|
}
|
||
|
}
|
||
|
if (empty($info['filelist'])) {
|
||
|
$errors[] = 'no files';
|
||
|
} else {
|
||
|
foreach ($info['filelist'] as $file => $fa) {
|
||
|
if (empty($fa['role'])) {
|
||
|
$errors[] = "file $file: missing role";
|
||
|
continue;
|
||
|
} elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) {
|
||
|
$errors[] = "file $file: invalid role, should be one of: "
|
||
|
. implode(' ', PEAR_Common::getFileRoles());
|
||
|
}
|
||
|
if ($fa['role'] == 'php' && $dir_prefix) {
|
||
|
$this->log(1, "Analyzing $file");
|
||
|
$srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
|
||
|
if ($srcinfo) {
|
||
|
$this->buildProvidesArray($srcinfo);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// (ssb) Any checks we can do for baseinstalldir?
|
||
|
// (cox) Perhaps checks that either the target dir and
|
||
|
// baseInstall doesn't cointain "../../"
|
||
|
}
|
||
|
}
|
||
|
$this->_packageName = $pn = $info['package'];
|
||
|
$pnl = strlen($pn);
|
||
|
foreach ((array)$this->pkginfo['provides'] as $key => $what) {
|
||
|
if (isset($what['explicit'])) {
|
||
|
// skip conformance checks if the provides entry is
|
||
|
// specified in the package.xml file
|
||
|
continue;
|
||
|
}
|
||
|
extract($what);
|
||
|
if ($type == 'class') {
|
||
|
if (!strncasecmp($name, $pn, $pnl)) {
|
||
|
continue;
|
||
|
}
|
||
|
$warnings[] = "in $file: class \"$name\" not prefixed with package name \"$pn\"";
|
||
|
} elseif ($type == 'function') {
|
||
|
if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
|
||
|
continue;
|
||
|
}
|
||
|
$warnings[] = "in $file: function \"$name\" not prefixed with package name \"$pn\"";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ buildProvidesArray()
|
||
|
|
||
|
/**
|
||
|
* Build a "provides" array from data returned by
|
||
|
* analyzeSourceCode(). The format of the built array is like
|
||
|
* this:
|
||
|
*
|
||
|
* array(
|
||
|
* 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
|
||
|
* ...
|
||
|
* )
|
||
|
*
|
||
|
*
|
||
|
* @param array $srcinfo array with information about a source file
|
||
|
* as returned by the analyzeSourceCode() method.
|
||
|
*
|
||
|
* @return void
|
||
|
*
|
||
|
* @access public
|
||
|
*
|
||
|
*/
|
||
|
function buildProvidesArray($srcinfo)
|
||
|
{
|
||
|
$file = basename($srcinfo['source_file']);
|
||
|
$pn = '';
|
||
|
if (isset($this->_packageName)) {
|
||
|
$pn = $this->_packageName;
|
||
|
}
|
||
|
$pnl = strlen($pn);
|
||
|
foreach ($srcinfo['declared_classes'] as $class) {
|
||
|
$key = "class;$class";
|
||
|
if (isset($this->pkginfo['provides'][$key])) {
|
||
|
continue;
|
||
|
}
|
||
|
$this->pkginfo['provides'][$key] =
|
||
|
array('file'=> $file, 'type' => 'class', 'name' => $class);
|
||
|
if (isset($srcinfo['inheritance'][$class])) {
|
||
|
$this->pkginfo['provides'][$key]['extends'] =
|
||
|
$srcinfo['inheritance'][$class];
|
||
|
}
|
||
|
}
|
||
|
foreach ($srcinfo['declared_methods'] as $class => $methods) {
|
||
|
foreach ($methods as $method) {
|
||
|
$function = "$class::$method";
|
||
|
$key = "function;$function";
|
||
|
if ($method{0} == '_' || !strcasecmp($method, $class) ||
|
||
|
isset($this->pkginfo['provides'][$key])) {
|
||
|
continue;
|
||
|
}
|
||
|
$this->pkginfo['provides'][$key] =
|
||
|
array('file'=> $file, 'type' => 'function', 'name' => $function);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach ($srcinfo['declared_functions'] as $function) {
|
||
|
$key = "function;$function";
|
||
|
if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) {
|
||
|
continue;
|
||
|
}
|
||
|
if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
|
||
|
$warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
|
||
|
}
|
||
|
$this->pkginfo['provides'][$key] =
|
||
|
array('file'=> $file, 'type' => 'function', 'name' => $function);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ analyzeSourceCode()
|
||
|
|
||
|
/**
|
||
|
* Analyze the source code of the given PHP file
|
||
|
*
|
||
|
* @param string Filename of the PHP file
|
||
|
* @return mixed
|
||
|
* @access public
|
||
|
*/
|
||
|
function analyzeSourceCode($file)
|
||
|
{
|
||
|
if (!function_exists("token_get_all")) {
|
||
|
return false;
|
||
|
}
|
||
|
if (!defined('T_DOC_COMMENT')) {
|
||
|
define('T_DOC_COMMENT', T_COMMENT);
|
||
|
}
|
||
|
if (!defined('T_INTERFACE')) {
|
||
|
define('T_INTERFACE', -1);
|
||
|
}
|
||
|
if (!defined('T_IMPLEMENTS')) {
|
||
|
define('T_IMPLEMENTS', -1);
|
||
|
}
|
||
|
if (!$fp = @fopen($file, "r")) {
|
||
|
return false;
|
||
|
}
|
||
|
$contents = fread($fp, filesize($file));
|
||
|
$tokens = token_get_all($contents);
|
||
|
/*
|
||
|
for ($i = 0; $i < sizeof($tokens); $i++) {
|
||
|
@list($token, $data) = $tokens[$i];
|
||
|
if (is_string($token)) {
|
||
|
var_dump($token);
|
||
|
} else {
|
||
|
print token_name($token) . ' ';
|
||
|
var_dump(rtrim($data));
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
$look_for = 0;
|
||
|
$paren_level = 0;
|
||
|
$bracket_level = 0;
|
||
|
$brace_level = 0;
|
||
|
$lastphpdoc = '';
|
||
|
$current_class = '';
|
||
|
$current_interface = '';
|
||
|
$current_class_level = -1;
|
||
|
$current_function = '';
|
||
|
$current_function_level = -1;
|
||
|
$declared_classes = array();
|
||
|
$declared_interfaces = array();
|
||
|
$declared_functions = array();
|
||
|
$declared_methods = array();
|
||
|
$used_classes = array();
|
||
|
$used_functions = array();
|
||
|
$extends = array();
|
||
|
$implements = array();
|
||
|
$nodeps = array();
|
||
|
$inquote = false;
|
||
|
$interface = false;
|
||
|
for ($i = 0; $i < sizeof($tokens); $i++) {
|
||
|
if (is_array($tokens[$i])) {
|
||
|
list($token, $data) = $tokens[$i];
|
||
|
} else {
|
||
|
$token = $tokens[$i];
|
||
|
$data = '';
|
||
|
}
|
||
|
if ($inquote) {
|
||
|
if ($token != '"') {
|
||
|
continue;
|
||
|
} else {
|
||
|
$inquote = false;
|
||
|
}
|
||
|
}
|
||
|
switch ($token) {
|
||
|
case T_WHITESPACE:
|
||
|
continue;
|
||
|
case ';':
|
||
|
if ($interface) {
|
||
|
$current_function = '';
|
||
|
$current_function_level = -1;
|
||
|
}
|
||
|
break;
|
||
|
case '"':
|
||
|
$inquote = true;
|
||
|
break;
|
||
|
case T_CURLY_OPEN:
|
||
|
case T_DOLLAR_OPEN_CURLY_BRACES:
|
||
|
case '{': $brace_level++; continue 2;
|
||
|
case '}':
|
||
|
$brace_level--;
|
||
|
if ($current_class_level == $brace_level) {
|
||
|
$current_class = '';
|
||
|
$current_class_level = -1;
|
||
|
}
|
||
|
if ($current_function_level == $brace_level) {
|
||
|
$current_function = '';
|
||
|
$current_function_level = -1;
|
||
|
}
|
||
|
continue 2;
|
||
|
case '[': $bracket_level++; continue 2;
|
||
|
case ']': $bracket_level--; continue 2;
|
||
|
case '(': $paren_level++; continue 2;
|
||
|
case ')': $paren_level--; continue 2;
|
||
|
case T_INTERFACE:
|
||
|
$interface = true;
|
||
|
case T_CLASS:
|
||
|
if (($current_class_level != -1) || ($current_function_level != -1)) {
|
||
|
PEAR::raiseError("Parser error: Invalid PHP file $file",
|
||
|
PEAR_COMMON_ERROR_INVALIDPHP);
|
||
|
return false;
|
||
|
}
|
||
|
case T_FUNCTION:
|
||
|
case T_NEW:
|
||
|
case T_EXTENDS:
|
||
|
case T_IMPLEMENTS:
|
||
|
$look_for = $token;
|
||
|
continue 2;
|
||
|
case T_STRING:
|
||
|
if (version_compare(zend_version(), '2.0', '<')) {
|
||
|
if (in_array(strtolower($data),
|
||
|
array('public', 'private', 'protected', 'abstract',
|
||
|
'interface', 'implements', 'clone', 'throw')
|
||
|
)) {
|
||
|
PEAR::raiseError('Error: PHP5 packages must be packaged by php 5 PEAR');
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
if ($look_for == T_CLASS) {
|
||
|
$current_class = $data;
|
||
|
$current_class_level = $brace_level;
|
||
|
$declared_classes[] = $current_class;
|
||
|
} elseif ($look_for == T_INTERFACE) {
|
||
|
$current_interface = $data;
|
||
|
$current_class_level = $brace_level;
|
||
|
$declared_interfaces[] = $current_interface;
|
||
|
} elseif ($look_for == T_IMPLEMENTS) {
|
||
|
$implements[$current_class] = $data;
|
||
|
} elseif ($look_for == T_EXTENDS) {
|
||
|
$extends[$current_class] = $data;
|
||
|
} elseif ($look_for == T_FUNCTION) {
|
||
|
if ($current_class) {
|
||
|
$current_function = "$current_class::$data";
|
||
|
$declared_methods[$current_class][] = $data;
|
||
|
} elseif ($current_interface) {
|
||
|
$current_function = "$current_interface::$data";
|
||
|
$declared_methods[$current_interface][] = $data;
|
||
|
} else {
|
||
|
$current_function = $data;
|
||
|
$declared_functions[] = $current_function;
|
||
|
}
|
||
|
$current_function_level = $brace_level;
|
||
|
$m = array();
|
||
|
} elseif ($look_for == T_NEW) {
|
||
|
$used_classes[$data] = true;
|
||
|
}
|
||
|
$look_for = 0;
|
||
|
continue 2;
|
||
|
case T_VARIABLE:
|
||
|
$look_for = 0;
|
||
|
continue 2;
|
||
|
case T_DOC_COMMENT:
|
||
|
case T_COMMENT:
|
||
|
if (preg_match('!^/\*\*\s!', $data)) {
|
||
|
$lastphpdoc = $data;
|
||
|
if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
|
||
|
$nodeps = array_merge($nodeps, $m[1]);
|
||
|
}
|
||
|
}
|
||
|
continue 2;
|
||
|
case T_DOUBLE_COLON:
|
||
|
if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) {
|
||
|
PEAR::raiseError("Parser error: Invalid PHP file $file",
|
||
|
PEAR_COMMON_ERROR_INVALIDPHP);
|
||
|
return false;
|
||
|
}
|
||
|
$class = $tokens[$i - 1][1];
|
||
|
if (strtolower($class) != 'parent') {
|
||
|
$used_classes[$class] = true;
|
||
|
}
|
||
|
continue 2;
|
||
|
}
|
||
|
}
|
||
|
return array(
|
||
|
"source_file" => $file,
|
||
|
"declared_classes" => $declared_classes,
|
||
|
"declared_interfaces" => $declared_interfaces,
|
||
|
"declared_methods" => $declared_methods,
|
||
|
"declared_functions" => $declared_functions,
|
||
|
"used_classes" => array_diff(array_keys($used_classes), $nodeps),
|
||
|
"inheritance" => $extends,
|
||
|
"implements" => $implements,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ betterStates()
|
||
|
|
||
|
/**
|
||
|
* Return an array containing all of the states that are more stable than
|
||
|
* or equal to the passed in state
|
||
|
*
|
||
|
* @param string Release state
|
||
|
* @param boolean Determines whether to include $state in the list
|
||
|
* @return false|array False if $state is not a valid release state
|
||
|
*/
|
||
|
function betterStates($state, $include = false)
|
||
|
{
|
||
|
static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
|
||
|
$i = array_search($state, $states);
|
||
|
if ($i === false) {
|
||
|
return false;
|
||
|
}
|
||
|
if ($include) {
|
||
|
$i--;
|
||
|
}
|
||
|
return array_slice($states, $i + 1);
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ detectDependencies()
|
||
|
|
||
|
function detectDependencies($any, $status_callback = null)
|
||
|
{
|
||
|
if (!function_exists("token_get_all")) {
|
||
|
return false;
|
||
|
}
|
||
|
if (PEAR::isError($info = $this->infoFromAny($any))) {
|
||
|
return $this->raiseError($info);
|
||
|
}
|
||
|
if (!is_array($info)) {
|
||
|
return false;
|
||
|
}
|
||
|
$deps = array();
|
||
|
$used_c = $decl_c = $decl_f = $decl_m = array();
|
||
|
foreach ($info['filelist'] as $file => $fa) {
|
||
|
$tmp = $this->analyzeSourceCode($file);
|
||
|
$used_c = @array_merge($used_c, $tmp['used_classes']);
|
||
|
$decl_c = @array_merge($decl_c, $tmp['declared_classes']);
|
||
|
$decl_f = @array_merge($decl_f, $tmp['declared_functions']);
|
||
|
$decl_m = @array_merge($decl_m, $tmp['declared_methods']);
|
||
|
$inheri = @array_merge($inheri, $tmp['inheritance']);
|
||
|
}
|
||
|
$used_c = array_unique($used_c);
|
||
|
$decl_c = array_unique($decl_c);
|
||
|
$undecl_c = array_diff($used_c, $decl_c);
|
||
|
return array('used_classes' => $used_c,
|
||
|
'declared_classes' => $decl_c,
|
||
|
'declared_methods' => $decl_m,
|
||
|
'declared_functions' => $decl_f,
|
||
|
'undeclared_classes' => $undecl_c,
|
||
|
'inheritance' => $inheri,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getUserRoles()
|
||
|
|
||
|
/**
|
||
|
* Get the valid roles for a PEAR package maintainer
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getUserRoles()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_maintainer_roles'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getReleaseStates()
|
||
|
|
||
|
/**
|
||
|
* Get the valid package release states of packages
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getReleaseStates()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_release_states'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getDependencyTypes()
|
||
|
|
||
|
/**
|
||
|
* Get the implemented dependency types (php, ext, pkg etc.)
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getDependencyTypes()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_dependency_types'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getDependencyRelations()
|
||
|
|
||
|
/**
|
||
|
* Get the implemented dependency relations (has, lt, ge etc.)
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getDependencyRelations()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_dependency_relations'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getFileRoles()
|
||
|
|
||
|
/**
|
||
|
* Get the implemented file roles
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getFileRoles()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_file_roles'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getReplacementTypes()
|
||
|
|
||
|
/**
|
||
|
* Get the implemented file replacement types in
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getReplacementTypes()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_replacement_types'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getProvideTypes()
|
||
|
|
||
|
/**
|
||
|
* Get the implemented file replacement types in
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getProvideTypes()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_provide_types'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ getScriptPhases()
|
||
|
|
||
|
/**
|
||
|
* Get the implemented file replacement types in
|
||
|
*
|
||
|
* @return array
|
||
|
* @static
|
||
|
*/
|
||
|
function getScriptPhases()
|
||
|
{
|
||
|
return $GLOBALS['_PEAR_Common_script_phases'];
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ validPackageName()
|
||
|
|
||
|
/**
|
||
|
* Test whether a string contains a valid package name.
|
||
|
*
|
||
|
* @param string $name the package name to test
|
||
|
*
|
||
|
* @return bool
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function validPackageName($name)
|
||
|
{
|
||
|
return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name);
|
||
|
}
|
||
|
|
||
|
|
||
|
// }}}
|
||
|
// {{{ validPackageVersion()
|
||
|
|
||
|
/**
|
||
|
* Test whether a string contains a valid package version.
|
||
|
*
|
||
|
* @param string $ver the package version to test
|
||
|
*
|
||
|
* @return bool
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function validPackageVersion($ver)
|
||
|
{
|
||
|
return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver);
|
||
|
}
|
||
|
|
||
|
|
||
|
// }}}
|
||
|
|
||
|
// {{{ downloadHttp()
|
||
|
|
||
|
/**
|
||
|
* Download a file through HTTP. Considers suggested file name in
|
||
|
* Content-disposition: header and can run a callback function for
|
||
|
* different events. The callback will be called with two
|
||
|
* parameters: the callback type, and parameters. The implemented
|
||
|
* callback types are:
|
||
|
*
|
||
|
* 'setup' called at the very beginning, parameter is a UI object
|
||
|
* that should be used for all output
|
||
|
* 'message' the parameter is a string with an informational message
|
||
|
* 'saveas' may be used to save with a different file name, the
|
||
|
* parameter is the filename that is about to be used.
|
||
|
* If a 'saveas' callback returns a non-empty string,
|
||
|
* that file name will be used as the filename instead.
|
||
|
* Note that $save_dir will not be affected by this, only
|
||
|
* the basename of the file.
|
||
|
* 'start' download is starting, parameter is number of bytes
|
||
|
* that are expected, or -1 if unknown
|
||
|
* 'bytesread' parameter is the number of bytes read so far
|
||
|
* 'done' download is complete, parameter is the total number
|
||
|
* of bytes read
|
||
|
* 'connfailed' if the TCP connection fails, this callback is called
|
||
|
* with array(host,port,errno,errmsg)
|
||
|
* 'writefailed' if writing to disk fails, this callback is called
|
||
|
* with array(destfile,errmsg)
|
||
|
*
|
||
|
* If an HTTP proxy has been configured (http_proxy PEAR_Config
|
||
|
* setting), the proxy will be used.
|
||
|
*
|
||
|
* @param string $url the URL to download
|
||
|
* @param object $ui PEAR_Frontend_* instance
|
||
|
* @param object $config PEAR_Config instance
|
||
|
* @param string $save_dir (optional) directory to save file in
|
||
|
* @param mixed $callback (optional) function/method to call for status
|
||
|
* updates
|
||
|
*
|
||
|
* @return string Returns the full path of the downloaded file or a PEAR
|
||
|
* error on failure. If the error is caused by
|
||
|
* socket-related errors, the error object will
|
||
|
* have the fsockopen error code available through
|
||
|
* getCode().
|
||
|
*
|
||
|
* @access public
|
||
|
*/
|
||
|
function downloadHttp($url, &$ui, $save_dir = '.', $callback = null)
|
||
|
{
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'setup', array(&$ui));
|
||
|
}
|
||
|
if (preg_match('!^http://([^/:?#]*)(:(\d+))?(/.*)!', $url, $matches)) {
|
||
|
list(,$host,,$port,$path) = $matches;
|
||
|
}
|
||
|
if (isset($this)) {
|
||
|
$config = &$this->config;
|
||
|
} else {
|
||
|
$config = &PEAR_Config::singleton();
|
||
|
}
|
||
|
$proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
|
||
|
if ($proxy = parse_url($config->get('http_proxy'))) {
|
||
|
$proxy_host = @$proxy['host'];
|
||
|
$proxy_port = @$proxy['port'];
|
||
|
$proxy_user = @$proxy['user'];
|
||
|
$proxy_pass = @$proxy['pass'];
|
||
|
|
||
|
if ($proxy_port == '') {
|
||
|
$proxy_port = 8080;
|
||
|
}
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
|
||
|
}
|
||
|
}
|
||
|
if (empty($port)) {
|
||
|
$port = 80;
|
||
|
}
|
||
|
if ($proxy_host != '') {
|
||
|
$fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
|
||
|
if (!$fp) {
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
|
||
|
$errno, $errstr));
|
||
|
}
|
||
|
return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
|
||
|
}
|
||
|
$request = "GET $url HTTP/1.0\r\n";
|
||
|
} else {
|
||
|
$fp = @fsockopen($host, $port, $errno, $errstr);
|
||
|
if (!$fp) {
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'connfailed', array($host, $port,
|
||
|
$errno, $errstr));
|
||
|
}
|
||
|
return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
|
||
|
}
|
||
|
$request = "GET $path HTTP/1.0\r\n";
|
||
|
}
|
||
|
$request .= "Host: $host:$port\r\n".
|
||
|
"User-Agent: PHP/".PHP_VERSION."\r\n";
|
||
|
if ($proxy_host != '' && $proxy_user != '') {
|
||
|
$request .= 'Proxy-Authorization: Basic ' .
|
||
|
base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
|
||
|
}
|
||
|
$request .= "\r\n";
|
||
|
fwrite($fp, $request);
|
||
|
$headers = array();
|
||
|
while (trim($line = fgets($fp, 1024))) {
|
||
|
if (preg_match('/^([^:]+):\s+(.*)\s*$/', $line, $matches)) {
|
||
|
$headers[strtolower($matches[1])] = trim($matches[2]);
|
||
|
} elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
|
||
|
if ($matches[1] != 200) {
|
||
|
return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (isset($headers['content-disposition']) &&
|
||
|
preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|$)/', $headers['content-disposition'], $matches)) {
|
||
|
$save_as = basename($matches[1]);
|
||
|
} else {
|
||
|
$save_as = basename($url);
|
||
|
}
|
||
|
if ($callback) {
|
||
|
$tmp = call_user_func($callback, 'saveas', $save_as);
|
||
|
if ($tmp) {
|
||
|
$save_as = $tmp;
|
||
|
}
|
||
|
}
|
||
|
$dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
|
||
|
if (!$wp = @fopen($dest_file, 'wb')) {
|
||
|
fclose($fp);
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
|
||
|
}
|
||
|
return PEAR::raiseError("could not open $dest_file for writing");
|
||
|
}
|
||
|
if (isset($headers['content-length'])) {
|
||
|
$length = $headers['content-length'];
|
||
|
} else {
|
||
|
$length = -1;
|
||
|
}
|
||
|
$bytes = 0;
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'start', array(basename($dest_file), $length));
|
||
|
}
|
||
|
while ($data = @fread($fp, 1024)) {
|
||
|
$bytes += strlen($data);
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'bytesread', $bytes);
|
||
|
}
|
||
|
if (!@fwrite($wp, $data)) {
|
||
|
fclose($fp);
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
|
||
|
}
|
||
|
return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
|
||
|
}
|
||
|
}
|
||
|
fclose($fp);
|
||
|
fclose($wp);
|
||
|
if ($callback) {
|
||
|
call_user_func($callback, 'done', $bytes);
|
||
|
}
|
||
|
return $dest_file;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ sortPkgDeps()
|
||
|
|
||
|
/**
|
||
|
* Sort a list of arrays of array(downloaded packagefilename) by dependency.
|
||
|
*
|
||
|
* It also removes duplicate dependencies
|
||
|
* @param array
|
||
|
* @param boolean Sort packages in reverse order if true
|
||
|
* @return array array of array(packagefilename, package.xml contents)
|
||
|
*/
|
||
|
function sortPkgDeps(&$packages, $uninstall = false)
|
||
|
{
|
||
|
$ret = array();
|
||
|
if ($uninstall) {
|
||
|
foreach($packages as $packageinfo) {
|
||
|
$ret[] = array('info' => $packageinfo);
|
||
|
}
|
||
|
} else {
|
||
|
foreach($packages as $packagefile) {
|
||
|
if (!is_array($packagefile)) {
|
||
|
$ret[] = array('file' => $packagefile,
|
||
|
'info' => $a = $this->infoFromAny($packagefile),
|
||
|
'pkg' => $a['package']);
|
||
|
} else {
|
||
|
$ret[] = $packagefile;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$checkdupes = array();
|
||
|
$newret = array();
|
||
|
foreach($ret as $i => $p) {
|
||
|
if (!isset($checkdupes[$p['info']['package']])) {
|
||
|
$checkdupes[$p['info']['package']][] = $i;
|
||
|
$newret[] = $p;
|
||
|
}
|
||
|
}
|
||
|
$this->_packageSortTree = $this->_getPkgDepTree($newret);
|
||
|
|
||
|
$func = $uninstall ? '_sortPkgDepsRev' : '_sortPkgDeps';
|
||
|
usort($newret, array(&$this, $func));
|
||
|
$this->_packageSortTree = null;
|
||
|
$packages = $newret;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _sortPkgDeps()
|
||
|
|
||
|
/**
|
||
|
* Compare two package's package.xml, and sort
|
||
|
* so that dependencies are installed first
|
||
|
*
|
||
|
* This is a crude compare, real dependency checking is done on install.
|
||
|
* The only purpose this serves is to make the command-line
|
||
|
* order-independent (you can list a dependent package first, and
|
||
|
* installation occurs in the order required)
|
||
|
* @access private
|
||
|
*/
|
||
|
function _sortPkgDeps($p1, $p2)
|
||
|
{
|
||
|
$p1name = $p1['info']['package'];
|
||
|
$p2name = $p2['info']['package'];
|
||
|
$p1deps = $this->_getPkgDeps($p1);
|
||
|
$p2deps = $this->_getPkgDeps($p2);
|
||
|
if (!count($p1deps) && !count($p2deps)) {
|
||
|
return 0; // order makes no difference
|
||
|
}
|
||
|
if (!count($p1deps)) {
|
||
|
return -1; // package 2 has dependencies, package 1 doesn't
|
||
|
}
|
||
|
if (!count($p2deps)) {
|
||
|
return 1; // package 1 has dependencies, package 2 doesn't
|
||
|
}
|
||
|
// both have dependencies
|
||
|
if (in_array($p1name, $p2deps)) {
|
||
|
return -1; // put package 1 first: package 2 depends on package 1
|
||
|
}
|
||
|
if (in_array($p2name, $p1deps)) {
|
||
|
return 1; // put package 2 first: package 1 depends on package 2
|
||
|
}
|
||
|
if ($this->_removedDependency($p1name, $p2name)) {
|
||
|
return -1; // put package 1 first: package 2 depends on packages that depend on package 1
|
||
|
}
|
||
|
if ($this->_removedDependency($p2name, $p1name)) {
|
||
|
return 1; // put package 2 first: package 1 depends on packages that depend on package 2
|
||
|
}
|
||
|
// doesn't really matter if neither depends on the other
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _sortPkgDepsRev()
|
||
|
|
||
|
/**
|
||
|
* Compare two package's package.xml, and sort
|
||
|
* so that dependencies are uninstalled last
|
||
|
*
|
||
|
* This is a crude compare, real dependency checking is done on uninstall.
|
||
|
* The only purpose this serves is to make the command-line
|
||
|
* order-independent (you can list a dependency first, and
|
||
|
* uninstallation occurs in the order required)
|
||
|
* @access private
|
||
|
*/
|
||
|
function _sortPkgDepsRev($p1, $p2)
|
||
|
{
|
||
|
$p1name = $p1['info']['package'];
|
||
|
$p2name = $p2['info']['package'];
|
||
|
$p1deps = $this->_getRevPkgDeps($p1);
|
||
|
$p2deps = $this->_getRevPkgDeps($p2);
|
||
|
if (!count($p1deps) && !count($p2deps)) {
|
||
|
return 0; // order makes no difference
|
||
|
}
|
||
|
if (!count($p1deps)) {
|
||
|
return 1; // package 2 has dependencies, package 1 doesn't
|
||
|
}
|
||
|
if (!count($p2deps)) {
|
||
|
return -1; // package 2 has dependencies, package 1 doesn't
|
||
|
}
|
||
|
// both have dependencies
|
||
|
if (in_array($p1name, $p2deps)) {
|
||
|
return 1; // put package 1 last
|
||
|
}
|
||
|
if (in_array($p2name, $p1deps)) {
|
||
|
return -1; // put package 2 last
|
||
|
}
|
||
|
if ($this->_removedDependency($p1name, $p2name)) {
|
||
|
return 1; // put package 1 last: package 2 depends on packages that depend on package 1
|
||
|
}
|
||
|
if ($this->_removedDependency($p2name, $p1name)) {
|
||
|
return -1; // put package 2 last: package 1 depends on packages that depend on package 2
|
||
|
}
|
||
|
// doesn't really matter if neither depends on the other
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _getPkgDeps()
|
||
|
|
||
|
/**
|
||
|
* get an array of package dependency names
|
||
|
* @param array
|
||
|
* @return array
|
||
|
* @access private
|
||
|
*/
|
||
|
function _getPkgDeps($p)
|
||
|
{
|
||
|
if (!isset($p['info']['releases'])) {
|
||
|
return $this->_getRevPkgDeps($p);
|
||
|
}
|
||
|
$rel = array_shift($p['info']['releases']);
|
||
|
if (!isset($rel['deps'])) {
|
||
|
return array();
|
||
|
}
|
||
|
$ret = array();
|
||
|
foreach($rel['deps'] as $dep) {
|
||
|
if ($dep['type'] == 'pkg') {
|
||
|
$ret[] = $dep['name'];
|
||
|
}
|
||
|
}
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _getPkgDeps()
|
||
|
|
||
|
/**
|
||
|
* get an array representation of the package dependency tree
|
||
|
* @return array
|
||
|
* @access private
|
||
|
*/
|
||
|
function _getPkgDepTree($packages)
|
||
|
{
|
||
|
$tree = array();
|
||
|
foreach ($packages as $p) {
|
||
|
$package = $p['info']['package'];
|
||
|
$deps = $this->_getPkgDeps($p);
|
||
|
$tree[$package] = $deps;
|
||
|
}
|
||
|
return $tree;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _removedDependency($p1, $p2)
|
||
|
|
||
|
/**
|
||
|
* get an array of package dependency names for uninstall
|
||
|
* @param string package 1 name
|
||
|
* @param string package 2 name
|
||
|
* @return bool
|
||
|
* @access private
|
||
|
*/
|
||
|
function _removedDependency($p1, $p2)
|
||
|
{
|
||
|
if (empty($this->_packageSortTree[$p2])) {
|
||
|
return false;
|
||
|
}
|
||
|
if (!in_array($p1, $this->_packageSortTree[$p2])) {
|
||
|
foreach ($this->_packageSortTree[$p2] as $potential) {
|
||
|
if ($this->_removedDependency($p1, $potential)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
// {{{ _getRevPkgDeps()
|
||
|
|
||
|
/**
|
||
|
* get an array of package dependency names for uninstall
|
||
|
* @param array
|
||
|
* @return array
|
||
|
* @access private
|
||
|
*/
|
||
|
function _getRevPkgDeps($p)
|
||
|
{
|
||
|
if (!isset($p['info']['release_deps'])) {
|
||
|
return array();
|
||
|
}
|
||
|
$ret = array();
|
||
|
foreach($p['info']['release_deps'] as $dep) {
|
||
|
if ($dep['type'] == 'pkg') {
|
||
|
$ret[] = $dep['name'];
|
||
|
}
|
||
|
}
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
// }}}
|
||
|
}
|
||
|
|
||
|
?>
|