Source for file Common.php
Documentation is available at Common.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | 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 '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().
$GLOBALS['_PEAR_Common_tempfiles'] = array ();
$GLOBALS['_PEAR_Common_maintainer_roles'] = array ('lead','developer','contributor','helper');
$GLOBALS['_PEAR_Common_release_states'] = array ('alpha','beta','stable','snapshot','devel');
$GLOBALS['_PEAR_Common_dependency_types'] = array ('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi');
* Valid dependency relations
$GLOBALS['_PEAR_Common_dependency_relations'] = array ('has','eq','lt','le','gt','ge','not', 'ne');
$GLOBALS['_PEAR_Common_file_roles'] = array ('php','ext','test','doc','data','src','script');
* Valid replacement types
$GLOBALS['_PEAR_Common_replacement_types'] = array ('php-const', 'pear-config', 'package-info');
$GLOBALS['_PEAR_Common_provide_types'] = array ('ext', 'prog', 'class', 'function', 'feature', 'api');
$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
/** stack of elements, gives some sort of XML context */
var $element_stack = array ();
/** name of currently parsed XML element */
/** array of attributes of the currently parsed XML element */
var $current_attributes = array ();
/** assoc with information about a package */
* User Interface object (PEAR_Frontend_* class). If null,
* the log() method uses print.
* Configuration object (PEAR_Config).
var $current_path = null;
* PEAR_SourceAnalyzer instance
var $source_analyzer = null;
* Flag variable used to mark a valid package file
* PEAR_Common constructor
$this->config = &PEAR_Config ::singleton ();
$this->debug = $this->config->get ('verbose');
// doesn't work due to bug #14744
//$tempfiles = $this->_tempfiles;
$tempfiles = & $GLOBALS['_PEAR_Common_tempfiles'];
* Register a temporary file or directory. When the destructor is
* executed, all registered temporary files and directories are
* @param string $file name of file or directory
function addTempFile ($file)
$GLOBALS['_PEAR_Common_tempfiles'][] = $file;
* 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
$this->log (2 , " + create dir $dir" );
* @param int $level log level (0 is quiet, higher is noisier)
* @param string $msg message to write to the log
function log ($level, $msg, $append_crlf = true )
if ($this->debug >= $level) {
$this->ui->log ($msg, $append_crlf);
* 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
function mkTempDir ($tmpdir = '')
$topt = array ('-t', $tmpdir);
$this->addTempFile ($tmpdir);
// {{{ setFrontendObject()
* Set object that represents the frontend to be used.
* @param object Reference of the frontend object
function setFrontendObject (&$ui)
* Unindent given string (?)
* @param string $str The string that has to be unindented.
// remove leading newlines
// find whitespace at the beginning of the first line
$indent_len = strspn($str, " \t");
$indent = substr($str, 0 , $indent_len);
// 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";
* 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
function _element_start ($xp, $name, $attribs)
$this->current_element = $name;
$spos = sizeof($this->element_stack) - 2;
$this->prev_element = ($spos >= 0 ) ? $this->element_stack[$spos] : '';
$this->current_attributes = $attribs;
$this->_validPackageFile = true;
if (isset ($attribs['version'])) {
$vs = preg_replace('/[^0-9a-z]/', '_', $attribs['version']);
$elem_start = '_element_start_'. $vs;
$elem_end = '_element_end_'. $vs;
$cdata = '_pkginfo_cdata_'. $vs;
$this->raiseError (" No handlers for package.xml version $attribs[version]" );
* 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
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
* @param resource $xp XML parser resource
* @param string $name name of ending element
function _element_start_1_0 ($xp, $name, $attribs)
$this->current_element = $name;
$spos = sizeof($this->element_stack) - 2;
$this->prev_element = ($spos >= 0 ) ? $this->element_stack[$spos] : '';
$this->current_attributes = $attribs;
if ($this->in_changelog) {
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'];
if ($this->in_changelog) {
if (isset ($attribs['name'])) {
if (count($this->dir_names)) {
foreach ($this->dir_names as $dir) {
$path .= $dir . DIRECTORY_SEPARATOR;
$path .= $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;
if (!isset ($this->filelist[$path]['role']) && isset ($this->dir_role)) {
$this->filelist[$path]['role'] = $this->dir_role;
if (!$this->in_changelog) {
$this->filelist[$this->current_path]['replacements'][] = $attribs;
$this->pkginfo['maintainers'] = array ();
$this->m_i = 0; // maintainers array index
if (!isset ($this->pkginfo['maintainers'])) {
$this->pkginfo['maintainers'] = array ();
$this->pkginfo['maintainers'][$this->m_i] = array ();
$this->current_maintainer = & $this->pkginfo['maintainers'][$this->m_i];
$this->pkginfo['changelog'] = array ();
$this->c_i = 0; // changelog array index
$this->in_changelog = true;
if ($this->in_changelog) {
$this->pkginfo['changelog'][$this->c_i] = array ();
$this->current_release = &$this->pkginfo['changelog'][$this->c_i];
$this->current_release = &$this->pkginfo;
if (!$this->in_changelog) {
$this->pkginfo['release_deps'] = array ();
// dependencies array index
if (!$this->in_changelog) {
$this->pkginfo['release_deps'][$this->d_i] = $attribs;
if (!$this->in_changelog) {
$this->pkginfo['configure_options'] = array ();
if (!$this->in_changelog) {
$this->pkginfo['configure_options'][] = $attribs;
if (empty ($attribs['type']) || empty ($attribs['name'])) {
$attribs['explicit'] = true;
$this->pkginfo['provides'][" $attribs[type];$attribs[name]" ] = $attribs;
// {{{ _element_end_1_0()
* XML parser callback for ending elements. Used for version 1.0
* @param resource $xp XML parser resource
* @param string $name name of ending element
function _element_end_1_0 ($xp, $name)
$data = trim($this->cdata);
switch ($this->prev_element) {
// XXX should we check the package name here?
$this->pkginfo['package'] = ereg_replace('[^a-zA-Z0-9._]', '_', $data);
$this->current_maintainer['name'] = $data;
$this->pkginfo['summary'] = $data;
$data = $this->_unIndent ($this->cdata);
$this->pkginfo['description'] = $data;
$this->current_maintainer['handle'] = $data;
$this->current_maintainer['email'] = $data;
$this->current_maintainer['role'] = $data;
if ($this->in_changelog) {
$this->current_release['version'] = $data;
$this->pkginfo['version'] = $data;
if ($this->in_changelog) {
$this->current_release['release_date'] = $data;
$this->pkginfo['release_date'] = $data;
// 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;
$this->pkginfo['release_notes'] = $data;
if ($this->in_changelog) {
$this->current_release['release_warnings'] = $data;
$this->pkginfo['release_warnings'] = $data;
if ($this->in_changelog) {
$this->current_release['release_state'] = $data;
$this->pkginfo['release_state'] = $data;
if ($this->in_changelog) {
$this->current_release['release_license'] = $data;
$this->pkginfo['release_license'] = $data;
if ($data && !$this->in_changelog) {
$this->pkginfo['release_deps'][$this->d_i]['name'] = $data;
if ($this->in_changelog) {
if ($this->in_changelog) {
if (count($this->dir_names)) {
foreach ($this->dir_names as $dir) {
$path .= $dir . DIRECTORY_SEPARATOR;
$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;
if (!isset ($this->filelist[$path]['role']) && isset ($this->dir_role)) {
$this->filelist[$path]['role'] = $this->dir_role;
if (empty ($this->pkginfo['maintainers'][$this->m_i]['role'])) {
$this->pkginfo['maintainers'][$this->m_i]['role'] = 'lead';
if ($this->in_changelog) {
$this->in_changelog = false;
$spos = sizeof($this->element_stack) - 1;
$this->current_element = ($spos > 0 ) ? $this->element_stack[$spos] : '';
// {{{ _pkginfo_cdata_1_0()
* XML parser callback for character data. Used for version 1.0
* @param resource $xp XML parser resource
* @param string $name character data
function _pkginfo_cdata_1_0 ($xp, $data)
if (isset ($this->cdata)) {
* 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
function infoFromTgzFile ($file)
return $this->raiseError (" could not open file \"$file\"" );
$tar = new Archive_Tar ($file);
$content = $tar->listContent ();
$tar->popErrorHandling ();
return $this->raiseError (" Could not get contents of package \"$file\"".
foreach ($content as $file) {
$name = $file['filename'];
if ($name == 'package.xml') {
} elseif (ereg('package.xml$', $name, $match)) {
$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
function infoFromDescriptionFile ($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
return $this->infoFromString ($data);
* 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
function infoFromString ($data)
require_once('PEAR/Dependency.php');
if (PEAR_Dependency ::checkExtension ($error, 'xml')) {
return $this->raiseError ($error);
return $this->raiseError ('Unable to create XML parser');
$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->_validPackageFile = false;
$msg = sprintf("XML error: %s at line %d",
return $this->raiseError ($msg, $code);
if (!$this->_validPackageFile) {
return $this->raiseError ('Invalid Package File, no <package> tag');
foreach ($this->pkginfo as $k => $v) {
$this->pkginfo[$k] = trim($v);
* 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.
* @param string Filename of the source ('package.xml', '<package>.tgz')
function infoFromAny ($info)
$info = $this->infoFromDescriptionFile ($info);
} elseif ($tmp == '.tar' || $tmp == '.tgz') {
$info = $this->infoFromTgzFile ($info);
$info = $this->infoFromDescriptionFile ($info);
$info = $this->infoFromTgzFile ($info);
if (PEAR ::isError ($info)) {
return $this->raiseError ($info);
* 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
function xmlFromInfo ($pkginfo)
static $maint_map = array (
$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>
foreach ($pkginfo['maintainers'] as $maint) {
$ret .= " <maintainer>\n";
foreach ($maint_map as $idx => $elm) {
$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";
* 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
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";
$ret .= " $indent </deps>\n";
if (isset ($pkginfo['configure_options'])) {
$ret .= " $indent <configureoptions>\n";
foreach ($pkginfo['configure_options'] as $c) {
$ret .= " $indent <configureoption name=\"".
if (isset ($c['default'])) {
$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]\" ";
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="' .
if (isset ($fa['md5sum'])) {
$ret .= " md5sum=\"$fa[md5sum]\"";
if (isset ($fa['platform'])) {
$ret .= " platform=\"$fa[platform]\"";
if (!empty ($fa['install-as'])) {
$ret .= ' install-as="' .
if (empty ($fa['replacements'])) {
foreach ($fa['replacements'] as $r) {
$ret .= " $indent <replace";
foreach ($r as $k => $v) {
@$ret .= " $indent </file>\n";
$ret .= " $indent </filelist>\n";
$ret .= " $indent </release>\n";
// {{{ 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
function validatePackageInfo ($info, &$errors, &$warnings, $dir_prefix = '')
if (PEAR ::isError ($info = $this->infoFromAny ($info))) {
return $this->raiseError ($info);
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)';
foreach ($info['maintainers'] as $m) {
if (empty ($m['handle'])) {
$errors[] = " maintainer $i: missing handle";
$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 ());
$errors[] = " maintainer $i: missing name";
if (empty ($m['email'])) {
$errors[] = " maintainer $i: missing email";
if (!empty ($info['release_deps'])) {
foreach ($info['release_deps'] as $d) {
$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 ());
$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";
if (($d['rel'] == 'not' || $d['rel'] == 'ne') && $d['optional'] == 'yes') {
$errors[] = " dependency $i: 'not' and 'ne' dependencies cannot be " .
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";
if (!empty ($info['configure_options'])) {
foreach ($info['configure_options'] as $c) {
$errors[] = " configure option $i: missing name";
if (empty ($c['prompt'])) {
$errors[] = " configure option $i: missing prompt";
if (empty ($info['filelist'])) {
foreach ($info['filelist'] as $file => $fa) {
if (empty ($fa['role'])) {
$errors[] = " file $file: missing role";
} 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);
$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'];
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
$warnings[] = " in $file: class \"$name\" not prefixed with package name \"$pn\"";
} elseif ($type == 'function') {
$warnings[] = " in $file: function \"$name\" not prefixed with package name \"$pn\"";
// {{{ buildProvidesArray()
* Build a "provides" array from data returned by
* analyzeSourceCode(). The format of the built array is like
* 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
* @param array $srcinfo array with information about a source file
* as returned by the analyzeSourceCode() method.
function buildProvidesArray ($srcinfo)
$file = basename($srcinfo['source_file']);
if (isset ($this->_packageName)) {
$pn = $this->_packageName;
foreach ($srcinfo['declared_classes'] as $class) {
if (isset ($this->pkginfo['provides'][$key])) {
$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])) {
$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])) {
$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
function analyzeSourceCode ($file)
define('T_DOC_COMMENT', T_COMMENT );
if (!$fp = @fopen($file, "r")) {
for ($i = 0; $i < sizeof($tokens); $i++) {
@list($token, $data) = $tokens[$i];
print token_name($token) . ' ';
$current_class_level = -1;
$current_function_level = -1;
$declared_classes = array ();
$declared_interfaces = array ();
$declared_functions = array ();
$declared_methods = array ();
$used_functions = array ();
for ($i = 0; $i < sizeof($tokens); $i++ ) {
list ($token, $data) = $tokens[$i];
$current_function_level = -1;
case T_DOLLAR_OPEN_CURLY_BRACES:
case '{': $brace_level++; continue 2;
if ($current_class_level == $brace_level) {
$current_class_level = -1;
if ($current_function_level == $brace_level) {
$current_function_level = -1;
case '[': $bracket_level++; continue 2;
case ']': $bracket_level--; continue 2;
case '(': $paren_level++; continue 2;
case ')': $paren_level--; continue 2;
if (($current_class_level != -1 ) || ($current_function_level != -1 )) {
PEAR ::raiseError (" Parser error: Invalid PHP file $file" ,
array ('public', 'private', 'protected', 'abstract',
'interface', 'implements', 'clone', 'throw')
PEAR ::raiseError ('Error: PHP5 packages must be packaged by php 5 PEAR');
if ($look_for == T_CLASS ) {
$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 ) {
$current_function = " $current_class::$data";
$declared_methods[$current_class][] = $data;
} elseif ($current_interface) {
$current_function = " $current_interface::$data";
$declared_methods[$current_interface][] = $data;
$current_function = $data;
$declared_functions[] = $current_function;
$current_function_level = $brace_level;
} elseif ($look_for == T_NEW ) {
$used_classes[$data] = true;
if (!($tokens[$i - 1 ][0 ] == T_WHITESPACE || $tokens[$i - 1 ][0 ] == T_STRING )) {
PEAR ::raiseError (" Parser error: Invalid PHP file $file" ,
$class = $tokens[$i - 1 ][1 ];
$used_classes[$class] = true;
"declared_classes" => $declared_classes,
"declared_interfaces" => $declared_interfaces,
"declared_methods" => $declared_methods,
"declared_functions" => $declared_functions,
"inheritance" => $extends,
"implements" => $implements,
* 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|arrayFalse 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);
// {{{ detectDependencies()
function detectDependencies ($any, $status_callback = null )
if (PEAR ::isError ($info = $this->infoFromAny ($any))) {
return $this->raiseError ($info);
$used_c = $decl_c = $decl_f = $decl_m = array ();
foreach ($info['filelist'] as $file => $fa) {
$tmp = $this->analyzeSourceCode ($file);
$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']);
return array ('used_classes' => $used_c,
'declared_classes' => $decl_c,
'declared_methods' => $decl_m,
'declared_functions' => $decl_f,
'undeclared_classes' => $undecl_c,
'inheritance' => $inheri,
* Get the valid roles for a PEAR package maintainer
return $GLOBALS['_PEAR_Common_maintainer_roles'];
// {{{ getReleaseStates()
* Get the valid package release states of packages
function getReleaseStates ()
return $GLOBALS['_PEAR_Common_release_states'];
// {{{ getDependencyTypes()
* Get the implemented dependency types (php, ext, pkg etc.)
function getDependencyTypes ()
return $GLOBALS['_PEAR_Common_dependency_types'];
// {{{ getDependencyRelations()
* Get the implemented dependency relations (has, lt, ge etc.)
function getDependencyRelations ()
return $GLOBALS['_PEAR_Common_dependency_relations'];
* Get the implemented file roles
return $GLOBALS['_PEAR_Common_file_roles'];
// {{{ getReplacementTypes()
* Get the implemented file replacement types in
function getReplacementTypes ()
return $GLOBALS['_PEAR_Common_replacement_types'];
* Get the implemented file replacement types in
function getProvideTypes ()
return $GLOBALS['_PEAR_Common_provide_types'];
* Get the implemented file replacement types in
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
function validPackageName ($name)
// {{{ validPackageVersion()
* Test whether a string contains a valid package version.
* @param string $ver the package version to test
function validPackageVersion ($ver)
* 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
* '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
* '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
* @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
function downloadHttp ($url, &$ui, $save_dir = '.', $callback = null )
if (preg_match('!^http://([^/:?#]*)(:(\d+))?(/.*)!', $url, $matches)) {
list (,$host,,$port,$path) = $matches;
$config = &$this->config;
$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'];
$fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
call_user_func($callback, 'connfailed', array ($proxy_host, $proxy_port,
return PEAR ::raiseError (" Connection to `$proxy_host:$proxy_port' failed: $errstr" , $errno);
$request = " GET $url HTTP/1.0\r\n";
$fp = @fsockopen($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 ' .
if (preg_match('/^([^:]+):\s+(.*)\s*$/', $line, $matches)) {
} 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)) {
$dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
if (!$wp = @fopen($dest_file, 'wb')) {
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'];
while ($data = @fread($fp, 1024 )) {
call_user_func($callback, 'writefailed', array ($dest_file, $php_errormsg));
return PEAR ::raiseError (" $dest_file: write failed ($php_errormsg)" );
* Sort a list of arrays of array(downloaded packagefilename) by dependency.
* It also removes duplicate dependencies
* @param boolean Sort packages in reverse order if true
* @return array array of array(packagefilename, package.xml contents)
function sortPkgDeps (&$packages, $uninstall = false )
foreach($packages as $packageinfo) {
$ret[] = array ('info' => $packageinfo);
foreach($packages as $packagefile) {
$ret[] = array ('file' => $packagefile,
'info' => $a = $this->infoFromAny ($packagefile),
foreach($ret as $i => $p) {
if (!isset ($checkdupes[$p['info']['package']])) {
$checkdupes[$p['info']['package']][] = $i;
$this->_packageSortTree = $this->_getPkgDepTree ($newret);
$func = $uninstall ? '_sortPkgDepsRev' : '_sortPkgDeps';
usort($newret, array (&$this, $func));
$this->_packageSortTree = null;
* 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)
function _sortPkgDeps ($p1, $p2)
$p1name = $p1['info']['package'];
$p2name = $p2['info']['package'];
$p1deps = $this->_getPkgDeps ($p1);
$p2deps = $this->_getPkgDeps ($p2);
return 0; // order makes no difference
return -1; // package 2 has dependencies, package 1 doesn't
return 1; // package 1 has dependencies, package 2 doesn't
// both have dependencies
return -1; // put package 1 first: package 2 depends on package 1
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
* 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)
function _sortPkgDepsRev ($p1, $p2)
$p1name = $p1['info']['package'];
$p2name = $p2['info']['package'];
$p1deps = $this->_getRevPkgDeps ($p1);
$p2deps = $this->_getRevPkgDeps ($p2);
return 0; // order makes no difference
return 1; // package 2 has dependencies, package 1 doesn't
return -1; // package 2 has dependencies, package 1 doesn't
// both have dependencies
return 1; // put package 1 last
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
* get an array of package dependency names
if (!isset ($p['info']['releases'])) {
return $this->_getRevPkgDeps ($p);
if (!isset ($rel['deps'])) {
foreach($rel['deps'] as $dep) {
if ($dep['type'] == 'pkg') {
* get an array representation of the package dependency tree
function _getPkgDepTree ($packages)
foreach ($packages as $p) {
$package = $p['info']['package'];
$deps = $this->_getPkgDeps ($p);
// {{{ _removedDependency($p1, $p2)
* get an array of package dependency names for uninstall
* @param string package 1 name
* @param string package 2 name
function _removedDependency ($p1, $p2)
if (empty ($this->_packageSortTree[$p2])) {
if (!in_array($p1, $this->_packageSortTree[$p2])) {
foreach ($this->_packageSortTree[$p2] as $potential) {
if ($this->_removedDependency ($p1, $potential)) {
* get an array of package dependency names for uninstall
function _getRevPkgDeps ($p)
if (!isset ($p['info']['release_deps'])) {
foreach($p['info']['release_deps'] as $dep) {
if ($dep['type'] == 'pkg') {
Documentation generated on Mon, 11 Mar 2019 14:23:55 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|