2154 lines
84 KiB
PHP
2154 lines
84 KiB
PHP
<?php
|
|
/**
|
|
* PEAR_PackageFile_v2, package.xml version 2.0, read/write version
|
|
*
|
|
* PHP versions 4 and 5
|
|
*
|
|
* @category pear
|
|
* @package PEAR
|
|
* @author Greg Beaver <cellog@php.net>
|
|
* @copyright 1997-2009 The Authors
|
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
|
* @version CVS: $Id: Validator.php 313023 2011-07-06 19:17:11Z dufuz $
|
|
* @link http://pear.php.net/package/PEAR
|
|
* @since File available since Release 1.4.0a8
|
|
*/
|
|
/**
|
|
* Private validation class used by PEAR_PackageFile_v2 - do not use directly, its
|
|
* sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller
|
|
* @category pear
|
|
* @package PEAR
|
|
* @author Greg Beaver <cellog@php.net>
|
|
* @copyright 1997-2009 The Authors
|
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
|
* @version Release: 1.9.4
|
|
* @link http://pear.php.net/package/PEAR
|
|
* @since Class available since Release 1.4.0a8
|
|
* @access private
|
|
*/
|
|
class PEAR_PackageFile_v2_Validator
|
|
{
|
|
/**
|
|
* @var array
|
|
*/
|
|
var $_packageInfo;
|
|
/**
|
|
* @var PEAR_PackageFile_v2
|
|
*/
|
|
var $_pf;
|
|
/**
|
|
* @var PEAR_ErrorStack
|
|
*/
|
|
var $_stack;
|
|
/**
|
|
* @var int
|
|
*/
|
|
var $_isValid = 0;
|
|
/**
|
|
* @var int
|
|
*/
|
|
var $_filesValid = 0;
|
|
/**
|
|
* @var int
|
|
*/
|
|
var $_curState = 0;
|
|
/**
|
|
* @param PEAR_PackageFile_v2
|
|
* @param int
|
|
*/
|
|
function validate(&$pf, $state = PEAR_VALIDATE_NORMAL)
|
|
{
|
|
$this->_pf = &$pf;
|
|
$this->_curState = $state;
|
|
$this->_packageInfo = $this->_pf->getArray();
|
|
$this->_isValid = $this->_pf->_isValid;
|
|
$this->_filesValid = $this->_pf->_filesValid;
|
|
$this->_stack = &$pf->_stack;
|
|
$this->_stack->getErrors(true);
|
|
if (($this->_isValid & $state) == $state) {
|
|
return true;
|
|
}
|
|
if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
|
|
return false;
|
|
}
|
|
if (!isset($this->_packageInfo['attribs']['version']) ||
|
|
($this->_packageInfo['attribs']['version'] != '2.0' &&
|
|
$this->_packageInfo['attribs']['version'] != '2.1')
|
|
) {
|
|
$this->_noPackageVersion();
|
|
}
|
|
$structure =
|
|
array(
|
|
'name',
|
|
'channel|uri',
|
|
'*extends', // can't be multiple, but this works fine
|
|
'summary',
|
|
'description',
|
|
'+lead', // these all need content checks
|
|
'*developer',
|
|
'*contributor',
|
|
'*helper',
|
|
'date',
|
|
'*time',
|
|
'version',
|
|
'stability',
|
|
'license->?uri->?filesource',
|
|
'notes',
|
|
'contents', //special validation needed
|
|
'*compatible',
|
|
'dependencies', //special validation needed
|
|
'*usesrole',
|
|
'*usestask', // reserve these for 1.4.0a1 to implement
|
|
// this will allow a package.xml to gracefully say it
|
|
// needs a certain package installed in order to implement a role or task
|
|
'*providesextension',
|
|
'*srcpackage|*srcuri',
|
|
'+phprelease|+extsrcrelease|+extbinrelease|' .
|
|
'+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed
|
|
'*changelog',
|
|
);
|
|
$test = $this->_packageInfo;
|
|
if (isset($test['dependencies']) &&
|
|
isset($test['dependencies']['required']) &&
|
|
isset($test['dependencies']['required']['pearinstaller']) &&
|
|
isset($test['dependencies']['required']['pearinstaller']['min']) &&
|
|
version_compare('1.9.4',
|
|
$test['dependencies']['required']['pearinstaller']['min'], '<')
|
|
) {
|
|
$this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']);
|
|
return false;
|
|
}
|
|
// ignore post-installation array fields
|
|
if (array_key_exists('filelist', $test)) {
|
|
unset($test['filelist']);
|
|
}
|
|
if (array_key_exists('_lastmodified', $test)) {
|
|
unset($test['_lastmodified']);
|
|
}
|
|
if (array_key_exists('#binarypackage', $test)) {
|
|
unset($test['#binarypackage']);
|
|
}
|
|
if (array_key_exists('old', $test)) {
|
|
unset($test['old']);
|
|
}
|
|
if (array_key_exists('_lastversion', $test)) {
|
|
unset($test['_lastversion']);
|
|
}
|
|
if (!$this->_stupidSchemaValidate($structure, $test, '<package>')) {
|
|
return false;
|
|
}
|
|
if (empty($this->_packageInfo['name'])) {
|
|
$this->_tagCannotBeEmpty('name');
|
|
}
|
|
$test = isset($this->_packageInfo['uri']) ? 'uri' :'channel';
|
|
if (empty($this->_packageInfo[$test])) {
|
|
$this->_tagCannotBeEmpty($test);
|
|
}
|
|
if (is_array($this->_packageInfo['license']) &&
|
|
(!isset($this->_packageInfo['license']['_content']) ||
|
|
empty($this->_packageInfo['license']['_content']))) {
|
|
$this->_tagCannotBeEmpty('license');
|
|
} elseif (empty($this->_packageInfo['license'])) {
|
|
$this->_tagCannotBeEmpty('license');
|
|
}
|
|
if (empty($this->_packageInfo['summary'])) {
|
|
$this->_tagCannotBeEmpty('summary');
|
|
}
|
|
if (empty($this->_packageInfo['description'])) {
|
|
$this->_tagCannotBeEmpty('description');
|
|
}
|
|
if (empty($this->_packageInfo['date'])) {
|
|
$this->_tagCannotBeEmpty('date');
|
|
}
|
|
if (empty($this->_packageInfo['notes'])) {
|
|
$this->_tagCannotBeEmpty('notes');
|
|
}
|
|
if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) {
|
|
$this->_tagCannotBeEmpty('time');
|
|
}
|
|
if (isset($this->_packageInfo['dependencies'])) {
|
|
$this->_validateDependencies();
|
|
}
|
|
if (isset($this->_packageInfo['compatible'])) {
|
|
$this->_validateCompatible();
|
|
}
|
|
if (!isset($this->_packageInfo['bundle'])) {
|
|
if (empty($this->_packageInfo['contents'])) {
|
|
$this->_tagCannotBeEmpty('contents');
|
|
}
|
|
if (!isset($this->_packageInfo['contents']['dir'])) {
|
|
$this->_filelistMustContainDir('contents');
|
|
return false;
|
|
}
|
|
if (isset($this->_packageInfo['contents']['file'])) {
|
|
$this->_filelistCannotContainFile('contents');
|
|
return false;
|
|
}
|
|
}
|
|
$this->_validateMaintainers();
|
|
$this->_validateStabilityVersion();
|
|
$fail = false;
|
|
if (array_key_exists('usesrole', $this->_packageInfo)) {
|
|
$roles = $this->_packageInfo['usesrole'];
|
|
if (!is_array($roles) || !isset($roles[0])) {
|
|
$roles = array($roles);
|
|
}
|
|
foreach ($roles as $role) {
|
|
if (!isset($role['role'])) {
|
|
$this->_usesroletaskMustHaveRoleTask('usesrole', 'role');
|
|
$fail = true;
|
|
} else {
|
|
if (!isset($role['channel'])) {
|
|
if (!isset($role['uri'])) {
|
|
$this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole');
|
|
$fail = true;
|
|
}
|
|
} elseif (!isset($role['package'])) {
|
|
$this->_usesroletaskMustHavePackage($role['role'], 'usesrole');
|
|
$fail = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (array_key_exists('usestask', $this->_packageInfo)) {
|
|
$roles = $this->_packageInfo['usestask'];
|
|
if (!is_array($roles) || !isset($roles[0])) {
|
|
$roles = array($roles);
|
|
}
|
|
foreach ($roles as $role) {
|
|
if (!isset($role['task'])) {
|
|
$this->_usesroletaskMustHaveRoleTask('usestask', 'task');
|
|
$fail = true;
|
|
} else {
|
|
if (!isset($role['channel'])) {
|
|
if (!isset($role['uri'])) {
|
|
$this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask');
|
|
$fail = true;
|
|
}
|
|
} elseif (!isset($role['package'])) {
|
|
$this->_usesroletaskMustHavePackage($role['task'], 'usestask');
|
|
$fail = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($fail) {
|
|
return false;
|
|
}
|
|
|
|
$list = $this->_packageInfo['contents'];
|
|
if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) {
|
|
$this->_multipleToplevelDirNotAllowed();
|
|
return $this->_isValid = 0;
|
|
}
|
|
|
|
$this->_validateFilelist();
|
|
$this->_validateRelease();
|
|
if (!$this->_stack->hasErrors()) {
|
|
$chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true);
|
|
if (PEAR::isError($chan)) {
|
|
$this->_unknownChannel($this->_pf->getChannel());
|
|
} else {
|
|
$valpack = $chan->getValidationPackage();
|
|
// for channel validator packages, always use the default PEAR validator.
|
|
// otherwise, they can't be installed or packaged
|
|
$validator = $chan->getValidationObject($this->_pf->getPackage());
|
|
if (!$validator) {
|
|
$this->_stack->push(__FUNCTION__, 'error',
|
|
array('channel' => $chan->getName(),
|
|
'package' => $this->_pf->getPackage(),
|
|
'name' => $valpack['_content'],
|
|
'version' => $valpack['attribs']['version']),
|
|
'package "%channel%/%package%" cannot be properly validated without ' .
|
|
'validation package "%channel%/%name%-%version%"');
|
|
return $this->_isValid = 0;
|
|
}
|
|
$validator->setPackageFile($this->_pf);
|
|
$validator->validate($state);
|
|
$failures = $validator->getFailures();
|
|
foreach ($failures['errors'] as $error) {
|
|
$this->_stack->push(__FUNCTION__, 'error', $error,
|
|
'Channel validator error: field "%field%" - %reason%');
|
|
}
|
|
foreach ($failures['warnings'] as $warning) {
|
|
$this->_stack->push(__FUNCTION__, 'warning', $warning,
|
|
'Channel validator warning: field "%field%" - %reason%');
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error');
|
|
if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) {
|
|
if ($this->_pf->getPackageType() == 'bundle') {
|
|
if ($this->_analyzeBundledPackages()) {
|
|
$this->_filesValid = $this->_pf->_filesValid = true;
|
|
} else {
|
|
$this->_pf->_isValid = $this->_isValid = 0;
|
|
}
|
|
} else {
|
|
if (!$this->_analyzePhpFiles()) {
|
|
$this->_pf->_isValid = $this->_isValid = 0;
|
|
} else {
|
|
$this->_filesValid = $this->_pf->_filesValid = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this->_isValid) {
|
|
return $this->_pf->_isValid = $this->_isValid = $state;
|
|
}
|
|
|
|
return $this->_pf->_isValid = $this->_isValid = 0;
|
|
}
|
|
|
|
function _stupidSchemaValidate($structure, $xml, $root)
|
|
{
|
|
if (!is_array($xml)) {
|
|
$xml = array();
|
|
}
|
|
$keys = array_keys($xml);
|
|
reset($keys);
|
|
$key = current($keys);
|
|
while ($key == 'attribs' || $key == '_contents') {
|
|
$key = next($keys);
|
|
}
|
|
$unfoundtags = $optionaltags = array();
|
|
$ret = true;
|
|
$mismatch = false;
|
|
foreach ($structure as $struc) {
|
|
if ($key) {
|
|
$tag = $xml[$key];
|
|
}
|
|
$test = $this->_processStructure($struc);
|
|
if (isset($test['choices'])) {
|
|
$loose = true;
|
|
foreach ($test['choices'] as $choice) {
|
|
if ($key == $choice['tag']) {
|
|
$key = next($keys);
|
|
while ($key == 'attribs' || $key == '_contents') {
|
|
$key = next($keys);
|
|
}
|
|
$unfoundtags = $optionaltags = array();
|
|
$mismatch = false;
|
|
if ($key && $key != $choice['tag'] && isset($choice['multiple'])) {
|
|
$unfoundtags[] = $choice['tag'];
|
|
$optionaltags[] = $choice['tag'];
|
|
if ($key) {
|
|
$mismatch = true;
|
|
}
|
|
}
|
|
$ret &= $this->_processAttribs($choice, $tag, $root);
|
|
continue 2;
|
|
} else {
|
|
$unfoundtags[] = $choice['tag'];
|
|
$mismatch = true;
|
|
}
|
|
if (!isset($choice['multiple']) || $choice['multiple'] != '*') {
|
|
$loose = false;
|
|
} else {
|
|
$optionaltags[] = $choice['tag'];
|
|
}
|
|
}
|
|
if (!$loose) {
|
|
$this->_invalidTagOrder($unfoundtags, $key, $root);
|
|
return false;
|
|
}
|
|
} else {
|
|
if ($key != $test['tag']) {
|
|
if (isset($test['multiple']) && $test['multiple'] != '*') {
|
|
$unfoundtags[] = $test['tag'];
|
|
$this->_invalidTagOrder($unfoundtags, $key, $root);
|
|
return false;
|
|
} else {
|
|
if ($key) {
|
|
$mismatch = true;
|
|
}
|
|
$unfoundtags[] = $test['tag'];
|
|
$optionaltags[] = $test['tag'];
|
|
}
|
|
if (!isset($test['multiple'])) {
|
|
$this->_invalidTagOrder($unfoundtags, $key, $root);
|
|
return false;
|
|
}
|
|
continue;
|
|
} else {
|
|
$unfoundtags = $optionaltags = array();
|
|
$mismatch = false;
|
|
}
|
|
$key = next($keys);
|
|
while ($key == 'attribs' || $key == '_contents') {
|
|
$key = next($keys);
|
|
}
|
|
if ($key && $key != $test['tag'] && isset($test['multiple'])) {
|
|
$unfoundtags[] = $test['tag'];
|
|
$optionaltags[] = $test['tag'];
|
|
$mismatch = true;
|
|
}
|
|
$ret &= $this->_processAttribs($test, $tag, $root);
|
|
continue;
|
|
}
|
|
}
|
|
if (!$mismatch && count($optionaltags)) {
|
|
// don't error out on any optional tags
|
|
$unfoundtags = array_diff($unfoundtags, $optionaltags);
|
|
}
|
|
if (count($unfoundtags)) {
|
|
$this->_invalidTagOrder($unfoundtags, $key, $root);
|
|
} elseif ($key) {
|
|
// unknown tags
|
|
$this->_invalidTagOrder('*no tags allowed here*', $key, $root);
|
|
while ($key = next($keys)) {
|
|
$this->_invalidTagOrder('*no tags allowed here*', $key, $root);
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
function _processAttribs($choice, $tag, $context)
|
|
{
|
|
if (isset($choice['attribs'])) {
|
|
if (!is_array($tag)) {
|
|
$tag = array($tag);
|
|
}
|
|
$tags = $tag;
|
|
if (!isset($tags[0])) {
|
|
$tags = array($tags);
|
|
}
|
|
$ret = true;
|
|
foreach ($tags as $i => $tag) {
|
|
if (!is_array($tag) || !isset($tag['attribs'])) {
|
|
foreach ($choice['attribs'] as $attrib) {
|
|
if ($attrib{0} != '?') {
|
|
$ret &= $this->_tagHasNoAttribs($choice['tag'],
|
|
$context);
|
|
continue 2;
|
|
}
|
|
}
|
|
}
|
|
foreach ($choice['attribs'] as $attrib) {
|
|
if ($attrib{0} != '?') {
|
|
if (!isset($tag['attribs'][$attrib])) {
|
|
$ret &= $this->_tagMissingAttribute($choice['tag'],
|
|
$attrib, $context);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function _processStructure($key)
|
|
{
|
|
$ret = array();
|
|
if (count($pieces = explode('|', $key)) > 1) {
|
|
$ret['choices'] = array();
|
|
foreach ($pieces as $piece) {
|
|
$ret['choices'][] = $this->_processStructure($piece);
|
|
}
|
|
return $ret;
|
|
}
|
|
$multi = $key{0};
|
|
if ($multi == '+' || $multi == '*') {
|
|
$ret['multiple'] = $key{0};
|
|
$key = substr($key, 1);
|
|
}
|
|
if (count($attrs = explode('->', $key)) > 1) {
|
|
$ret['tag'] = array_shift($attrs);
|
|
$ret['attribs'] = $attrs;
|
|
} else {
|
|
$ret['tag'] = $key;
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
function _validateStabilityVersion()
|
|
{
|
|
$structure = array('release', 'api');
|
|
$a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>');
|
|
$a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>');
|
|
if ($a) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$this->_packageInfo['version']['release'])) {
|
|
$this->_invalidVersion('release', $this->_packageInfo['version']['release']);
|
|
}
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$this->_packageInfo['version']['api'])) {
|
|
$this->_invalidVersion('api', $this->_packageInfo['version']['api']);
|
|
}
|
|
if (!in_array($this->_packageInfo['stability']['release'],
|
|
array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) {
|
|
$this->_invalidState('release', $this->_packageInfo['stability']['release']);
|
|
}
|
|
if (!in_array($this->_packageInfo['stability']['api'],
|
|
array('devel', 'alpha', 'beta', 'stable'))) {
|
|
$this->_invalidState('api', $this->_packageInfo['stability']['api']);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateMaintainers()
|
|
{
|
|
$structure =
|
|
array(
|
|
'name',
|
|
'user',
|
|
'email',
|
|
'active',
|
|
);
|
|
foreach (array('lead', 'developer', 'contributor', 'helper') as $type) {
|
|
if (!isset($this->_packageInfo[$type])) {
|
|
continue;
|
|
}
|
|
if (isset($this->_packageInfo[$type][0])) {
|
|
foreach ($this->_packageInfo[$type] as $lead) {
|
|
$this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>');
|
|
}
|
|
} else {
|
|
$this->_stupidSchemaValidate($structure, $this->_packageInfo[$type],
|
|
'<' . $type . '>');
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validatePhpDep($dep, $installcondition = false)
|
|
{
|
|
$structure = array(
|
|
'min',
|
|
'*max',
|
|
'*exclude',
|
|
);
|
|
$type = $installcondition ? '<installcondition><php>' : '<dependencies><required><php>';
|
|
$this->_stupidSchemaValidate($structure, $dep, $type);
|
|
if (isset($dep['min'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
|
|
$dep['min'])) {
|
|
$this->_invalidVersion($type . '<min>', $dep['min']);
|
|
}
|
|
}
|
|
if (isset($dep['max'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
|
|
$dep['max'])) {
|
|
$this->_invalidVersion($type . '<max>', $dep['max']);
|
|
}
|
|
}
|
|
if (isset($dep['exclude'])) {
|
|
if (!is_array($dep['exclude'])) {
|
|
$dep['exclude'] = array($dep['exclude']);
|
|
}
|
|
foreach ($dep['exclude'] as $exclude) {
|
|
if (!preg_match(
|
|
'/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
|
|
$exclude)) {
|
|
$this->_invalidVersion($type . '<exclude>', $exclude);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validatePearinstallerDep($dep)
|
|
{
|
|
$structure = array(
|
|
'min',
|
|
'*max',
|
|
'*recommended',
|
|
'*exclude',
|
|
);
|
|
$this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
|
|
if (isset($dep['min'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['min'])) {
|
|
$this->_invalidVersion('<dependencies><required><pearinstaller><min>',
|
|
$dep['min']);
|
|
}
|
|
}
|
|
if (isset($dep['max'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['max'])) {
|
|
$this->_invalidVersion('<dependencies><required><pearinstaller><max>',
|
|
$dep['max']);
|
|
}
|
|
}
|
|
if (isset($dep['recommended'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['recommended'])) {
|
|
$this->_invalidVersion('<dependencies><required><pearinstaller><recommended>',
|
|
$dep['recommended']);
|
|
}
|
|
}
|
|
if (isset($dep['exclude'])) {
|
|
if (!is_array($dep['exclude'])) {
|
|
$dep['exclude'] = array($dep['exclude']);
|
|
}
|
|
foreach ($dep['exclude'] as $exclude) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$exclude)) {
|
|
$this->_invalidVersion('<dependencies><required><pearinstaller><exclude>',
|
|
$exclude);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validatePackageDep($dep, $group, $type = '<package>')
|
|
{
|
|
if (isset($dep['uri'])) {
|
|
if (isset($dep['conflicts'])) {
|
|
$structure = array(
|
|
'name',
|
|
'uri',
|
|
'conflicts',
|
|
'*providesextension',
|
|
);
|
|
} else {
|
|
$structure = array(
|
|
'name',
|
|
'uri',
|
|
'*providesextension',
|
|
);
|
|
}
|
|
} else {
|
|
if (isset($dep['conflicts'])) {
|
|
$structure = array(
|
|
'name',
|
|
'channel',
|
|
'*min',
|
|
'*max',
|
|
'*exclude',
|
|
'conflicts',
|
|
'*providesextension',
|
|
);
|
|
} else {
|
|
$structure = array(
|
|
'name',
|
|
'channel',
|
|
'*min',
|
|
'*max',
|
|
'*recommended',
|
|
'*exclude',
|
|
'*nodefault',
|
|
'*providesextension',
|
|
);
|
|
}
|
|
}
|
|
if (isset($dep['name'])) {
|
|
$type .= '<name>' . $dep['name'] . '</name>';
|
|
}
|
|
$this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type);
|
|
if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) ||
|
|
isset($dep['recommended']) || isset($dep['exclude']))) {
|
|
$this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type);
|
|
}
|
|
if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
|
|
$this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
|
|
}
|
|
if (isset($dep['min'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['min'])) {
|
|
$this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
|
|
}
|
|
}
|
|
if (isset($dep['max'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['max'])) {
|
|
$this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
|
|
}
|
|
}
|
|
if (isset($dep['recommended'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['recommended'])) {
|
|
$this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>',
|
|
$dep['recommended']);
|
|
}
|
|
}
|
|
if (isset($dep['exclude'])) {
|
|
if (!is_array($dep['exclude'])) {
|
|
$dep['exclude'] = array($dep['exclude']);
|
|
}
|
|
foreach ($dep['exclude'] as $exclude) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$exclude)) {
|
|
$this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>',
|
|
$exclude);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateSubpackageDep($dep, $group)
|
|
{
|
|
$this->_validatePackageDep($dep, $group, '<subpackage>');
|
|
if (isset($dep['providesextension'])) {
|
|
$this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : '');
|
|
}
|
|
if (isset($dep['conflicts'])) {
|
|
$this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : '');
|
|
}
|
|
}
|
|
|
|
function _validateExtensionDep($dep, $group = false, $installcondition = false)
|
|
{
|
|
if (isset($dep['conflicts'])) {
|
|
$structure = array(
|
|
'name',
|
|
'*min',
|
|
'*max',
|
|
'*exclude',
|
|
'conflicts',
|
|
);
|
|
} else {
|
|
$structure = array(
|
|
'name',
|
|
'*min',
|
|
'*max',
|
|
'*recommended',
|
|
'*exclude',
|
|
);
|
|
}
|
|
if ($installcondition) {
|
|
$type = '<installcondition><extension>';
|
|
} else {
|
|
$type = '<dependencies>' . $group . '<extension>';
|
|
}
|
|
if (isset($dep['name'])) {
|
|
$type .= '<name>' . $dep['name'] . '</name>';
|
|
}
|
|
$this->_stupidSchemaValidate($structure, $dep, $type);
|
|
if (isset($dep['min'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['min'])) {
|
|
$this->_invalidVersion(substr($type, 1) . '<min', $dep['min']);
|
|
}
|
|
}
|
|
if (isset($dep['max'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['max'])) {
|
|
$this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
|
|
}
|
|
}
|
|
if (isset($dep['recommended'])) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$dep['recommended'])) {
|
|
$this->_invalidVersion(substr($type, 1) . '<recommended', $dep['recommended']);
|
|
}
|
|
}
|
|
if (isset($dep['exclude'])) {
|
|
if (!is_array($dep['exclude'])) {
|
|
$dep['exclude'] = array($dep['exclude']);
|
|
}
|
|
foreach ($dep['exclude'] as $exclude) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$exclude)) {
|
|
$this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateOsDep($dep, $installcondition = false)
|
|
{
|
|
$structure = array(
|
|
'name',
|
|
'*conflicts',
|
|
);
|
|
$type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>';
|
|
if ($this->_stupidSchemaValidate($structure, $dep, $type)) {
|
|
if ($dep['name'] == '*') {
|
|
if (array_key_exists('conflicts', $dep)) {
|
|
$this->_cannotConflictWithAllOs($type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateArchDep($dep, $installcondition = false)
|
|
{
|
|
$structure = array(
|
|
'pattern',
|
|
'*conflicts',
|
|
);
|
|
$type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>';
|
|
$this->_stupidSchemaValidate($structure, $dep, $type);
|
|
}
|
|
|
|
function _validateInstallConditions($cond, $release)
|
|
{
|
|
$structure = array(
|
|
'*php',
|
|
'*extension',
|
|
'*os',
|
|
'*arch',
|
|
);
|
|
if (!$this->_stupidSchemaValidate($structure,
|
|
$cond, $release)) {
|
|
return false;
|
|
}
|
|
foreach (array('php', 'extension', 'os', 'arch') as $type) {
|
|
if (isset($cond[$type])) {
|
|
$iter = $cond[$type];
|
|
if (!is_array($iter) || !isset($iter[0])) {
|
|
$iter = array($iter);
|
|
}
|
|
foreach ($iter as $package) {
|
|
if ($type == 'extension') {
|
|
$this->{"_validate{$type}Dep"}($package, false, true);
|
|
} else {
|
|
$this->{"_validate{$type}Dep"}($package, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateDependencies()
|
|
{
|
|
$structure = array(
|
|
'required',
|
|
'*optional',
|
|
'*group->name->hint'
|
|
);
|
|
if (!$this->_stupidSchemaValidate($structure,
|
|
$this->_packageInfo['dependencies'], '<dependencies>')) {
|
|
return false;
|
|
}
|
|
foreach (array('required', 'optional') as $simpledep) {
|
|
if (isset($this->_packageInfo['dependencies'][$simpledep])) {
|
|
if ($simpledep == 'optional') {
|
|
$structure = array(
|
|
'*package',
|
|
'*subpackage',
|
|
'*extension',
|
|
);
|
|
} else {
|
|
$structure = array(
|
|
'php',
|
|
'pearinstaller',
|
|
'*package',
|
|
'*subpackage',
|
|
'*extension',
|
|
'*os',
|
|
'*arch',
|
|
);
|
|
}
|
|
if ($this->_stupidSchemaValidate($structure,
|
|
$this->_packageInfo['dependencies'][$simpledep],
|
|
"<dependencies><$simpledep>")) {
|
|
foreach (array('package', 'subpackage', 'extension') as $type) {
|
|
if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
|
|
$iter = $this->_packageInfo['dependencies'][$simpledep][$type];
|
|
if (!isset($iter[0])) {
|
|
$iter = array($iter);
|
|
}
|
|
foreach ($iter as $package) {
|
|
if ($type != 'extension') {
|
|
if (isset($package['uri'])) {
|
|
if (isset($package['channel'])) {
|
|
$this->_UrlOrChannel($type,
|
|
$package['name']);
|
|
}
|
|
} else {
|
|
if (!isset($package['channel'])) {
|
|
$this->_NoChannel($type, $package['name']);
|
|
}
|
|
}
|
|
}
|
|
$this->{"_validate{$type}Dep"}($package, "<$simpledep>");
|
|
}
|
|
}
|
|
}
|
|
if ($simpledep == 'optional') {
|
|
continue;
|
|
}
|
|
foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) {
|
|
if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
|
|
$iter = $this->_packageInfo['dependencies'][$simpledep][$type];
|
|
if (!isset($iter[0])) {
|
|
$iter = array($iter);
|
|
}
|
|
foreach ($iter as $package) {
|
|
$this->{"_validate{$type}Dep"}($package);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (isset($this->_packageInfo['dependencies']['group'])) {
|
|
$groups = $this->_packageInfo['dependencies']['group'];
|
|
if (!isset($groups[0])) {
|
|
$groups = array($groups);
|
|
}
|
|
$structure = array(
|
|
'*package',
|
|
'*subpackage',
|
|
'*extension',
|
|
);
|
|
foreach ($groups as $group) {
|
|
if ($this->_stupidSchemaValidate($structure, $group, '<group>')) {
|
|
if (!PEAR_Validate::validGroupName($group['attribs']['name'])) {
|
|
$this->_invalidDepGroupName($group['attribs']['name']);
|
|
}
|
|
foreach (array('package', 'subpackage', 'extension') as $type) {
|
|
if (isset($group[$type])) {
|
|
$iter = $group[$type];
|
|
if (!isset($iter[0])) {
|
|
$iter = array($iter);
|
|
}
|
|
foreach ($iter as $package) {
|
|
if ($type != 'extension') {
|
|
if (isset($package['uri'])) {
|
|
if (isset($package['channel'])) {
|
|
$this->_UrlOrChannelGroup($type,
|
|
$package['name'],
|
|
$group['name']);
|
|
}
|
|
} else {
|
|
if (!isset($package['channel'])) {
|
|
$this->_NoChannelGroup($type,
|
|
$package['name'],
|
|
$group['name']);
|
|
}
|
|
}
|
|
}
|
|
$this->{"_validate{$type}Dep"}($package, '<group name="' .
|
|
$group['attribs']['name'] . '">');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateCompatible()
|
|
{
|
|
$compat = $this->_packageInfo['compatible'];
|
|
if (!isset($compat[0])) {
|
|
$compat = array($compat);
|
|
}
|
|
$required = array('name', 'channel', 'min', 'max', '*exclude');
|
|
foreach ($compat as $package) {
|
|
$type = '<compatible>';
|
|
if (is_array($package) && array_key_exists('name', $package)) {
|
|
$type .= '<name>' . $package['name'] . '</name>';
|
|
}
|
|
$this->_stupidSchemaValidate($required, $package, $type);
|
|
if (is_array($package) && array_key_exists('min', $package)) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$package['min'])) {
|
|
$this->_invalidVersion(substr($type, 1) . '<min', $package['min']);
|
|
}
|
|
}
|
|
if (is_array($package) && array_key_exists('max', $package)) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$package['max'])) {
|
|
$this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
|
|
}
|
|
}
|
|
if (is_array($package) && array_key_exists('exclude', $package)) {
|
|
if (!is_array($package['exclude'])) {
|
|
$package['exclude'] = array($package['exclude']);
|
|
}
|
|
foreach ($package['exclude'] as $exclude) {
|
|
if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
|
|
$exclude)) {
|
|
$this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateBundle($list)
|
|
{
|
|
if (!is_array($list) || !isset($list['bundledpackage'])) {
|
|
return $this->_NoBundledPackages();
|
|
}
|
|
if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) {
|
|
return $this->_AtLeast2BundledPackages();
|
|
}
|
|
foreach ($list['bundledpackage'] as $package) {
|
|
if (!is_string($package)) {
|
|
$this->_bundledPackagesMustBeFilename();
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateFilelist($list = false, $allowignore = false, $dirs = '')
|
|
{
|
|
$iscontents = false;
|
|
if (!$list) {
|
|
$iscontents = true;
|
|
$list = $this->_packageInfo['contents'];
|
|
if (isset($this->_packageInfo['bundle'])) {
|
|
return $this->_validateBundle($list);
|
|
}
|
|
}
|
|
if ($allowignore) {
|
|
$struc = array(
|
|
'*install->name->as',
|
|
'*ignore->name'
|
|
);
|
|
} else {
|
|
$struc = array(
|
|
'*dir->name->?baseinstalldir',
|
|
'*file->name->role->?baseinstalldir->?md5sum'
|
|
);
|
|
if (isset($list['dir']) && isset($list['file'])) {
|
|
// stave off validation errors without requiring a set order.
|
|
$_old = $list;
|
|
if (isset($list['attribs'])) {
|
|
$list = array('attribs' => $_old['attribs']);
|
|
}
|
|
$list['dir'] = $_old['dir'];
|
|
$list['file'] = $_old['file'];
|
|
}
|
|
}
|
|
if (!isset($list['attribs']) || !isset($list['attribs']['name'])) {
|
|
$unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">';
|
|
$dirname = $iscontents ? '<contents>' : $unknown;
|
|
} else {
|
|
$dirname = '<dir name="' . $list['attribs']['name'] . '">';
|
|
if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
|
|
str_replace('\\', '/', $list['attribs']['name']))) {
|
|
// file contains .. parent directory or . cur directory
|
|
$this->_invalidDirName($list['attribs']['name']);
|
|
}
|
|
}
|
|
$res = $this->_stupidSchemaValidate($struc, $list, $dirname);
|
|
if ($allowignore && $res) {
|
|
$ignored_or_installed = array();
|
|
$this->_pf->getFilelist();
|
|
$fcontents = $this->_pf->getContents();
|
|
$filelist = array();
|
|
if (!isset($fcontents['dir']['file'][0])) {
|
|
$fcontents['dir']['file'] = array($fcontents['dir']['file']);
|
|
}
|
|
foreach ($fcontents['dir']['file'] as $file) {
|
|
$filelist[$file['attribs']['name']] = true;
|
|
}
|
|
if (isset($list['install'])) {
|
|
if (!isset($list['install'][0])) {
|
|
$list['install'] = array($list['install']);
|
|
}
|
|
foreach ($list['install'] as $file) {
|
|
if (!isset($filelist[$file['attribs']['name']])) {
|
|
$this->_notInContents($file['attribs']['name'], 'install');
|
|
continue;
|
|
}
|
|
if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
|
|
$this->_multipleInstallAs($file['attribs']['name']);
|
|
}
|
|
if (!isset($ignored_or_installed[$file['attribs']['name']])) {
|
|
$ignored_or_installed[$file['attribs']['name']] = array();
|
|
}
|
|
$ignored_or_installed[$file['attribs']['name']][] = 1;
|
|
if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
|
|
str_replace('\\', '/', $file['attribs']['as']))) {
|
|
// file contains .. parent directory or . cur directory references
|
|
$this->_invalidFileInstallAs($file['attribs']['name'],
|
|
$file['attribs']['as']);
|
|
}
|
|
}
|
|
}
|
|
if (isset($list['ignore'])) {
|
|
if (!isset($list['ignore'][0])) {
|
|
$list['ignore'] = array($list['ignore']);
|
|
}
|
|
foreach ($list['ignore'] as $file) {
|
|
if (!isset($filelist[$file['attribs']['name']])) {
|
|
$this->_notInContents($file['attribs']['name'], 'ignore');
|
|
continue;
|
|
}
|
|
if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
|
|
$this->_ignoreAndInstallAs($file['attribs']['name']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!$allowignore && isset($list['file'])) {
|
|
if (is_string($list['file'])) {
|
|
$this->_oldStyleFileNotAllowed();
|
|
return false;
|
|
}
|
|
if (!isset($list['file'][0])) {
|
|
// single file
|
|
$list['file'] = array($list['file']);
|
|
}
|
|
foreach ($list['file'] as $i => $file)
|
|
{
|
|
if (isset($file['attribs']) && isset($file['attribs']['name'])) {
|
|
if ($file['attribs']['name']{0} == '.' &&
|
|
$file['attribs']['name']{1} == '/') {
|
|
// name is something like "./doc/whatever.txt"
|
|
$this->_invalidFileName($file['attribs']['name'], $dirname);
|
|
}
|
|
if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
|
|
str_replace('\\', '/', $file['attribs']['name']))) {
|
|
// file contains .. parent directory or . cur directory
|
|
$this->_invalidFileName($file['attribs']['name'], $dirname);
|
|
}
|
|
}
|
|
if (isset($file['attribs']) && isset($file['attribs']['role'])) {
|
|
if (!$this->_validateRole($file['attribs']['role'])) {
|
|
if (isset($this->_packageInfo['usesrole'])) {
|
|
$roles = $this->_packageInfo['usesrole'];
|
|
if (!isset($roles[0])) {
|
|
$roles = array($roles);
|
|
}
|
|
foreach ($roles as $role) {
|
|
if ($role['role'] = $file['attribs']['role']) {
|
|
$msg = 'This package contains role "%role%" and requires ' .
|
|
'package "%package%" to be used';
|
|
if (isset($role['uri'])) {
|
|
$params = array('role' => $role['role'],
|
|
'package' => $role['uri']);
|
|
} else {
|
|
$params = array('role' => $role['role'],
|
|
'package' => $this->_pf->_registry->
|
|
parsedPackageNameToString(array('package' =>
|
|
$role['package'], 'channel' => $role['channel']),
|
|
true));
|
|
}
|
|
$this->_stack->push('_mustInstallRole', 'error', $params, $msg);
|
|
}
|
|
}
|
|
}
|
|
$this->_invalidFileRole($file['attribs']['name'],
|
|
$dirname, $file['attribs']['role']);
|
|
}
|
|
}
|
|
if (!isset($file['attribs'])) {
|
|
continue;
|
|
}
|
|
$save = $file['attribs'];
|
|
if ($dirs) {
|
|
$save['name'] = $dirs . '/' . $save['name'];
|
|
}
|
|
unset($file['attribs']);
|
|
if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks
|
|
foreach ($file as $task => $value) {
|
|
if ($tagClass = $this->_pf->getTask($task)) {
|
|
if (!is_array($value) || !isset($value[0])) {
|
|
$value = array($value);
|
|
}
|
|
foreach ($value as $v) {
|
|
$ret = call_user_func(array($tagClass, 'validateXml'),
|
|
$this->_pf, $v, $this->_pf->_config, $save);
|
|
if (is_array($ret)) {
|
|
$this->_invalidTask($task, $ret, isset($save['name']) ?
|
|
$save['name'] : '');
|
|
}
|
|
}
|
|
} else {
|
|
if (isset($this->_packageInfo['usestask'])) {
|
|
$roles = $this->_packageInfo['usestask'];
|
|
if (!isset($roles[0])) {
|
|
$roles = array($roles);
|
|
}
|
|
foreach ($roles as $role) {
|
|
if ($role['task'] = $task) {
|
|
$msg = 'This package contains task "%task%" and requires ' .
|
|
'package "%package%" to be used';
|
|
if (isset($role['uri'])) {
|
|
$params = array('task' => $role['task'],
|
|
'package' => $role['uri']);
|
|
} else {
|
|
$params = array('task' => $role['task'],
|
|
'package' => $this->_pf->_registry->
|
|
parsedPackageNameToString(array('package' =>
|
|
$role['package'], 'channel' => $role['channel']),
|
|
true));
|
|
}
|
|
$this->_stack->push('_mustInstallTask', 'error',
|
|
$params, $msg);
|
|
}
|
|
}
|
|
}
|
|
$this->_unknownTask($task, $save['name']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (isset($list['ignore'])) {
|
|
if (!$allowignore) {
|
|
$this->_ignoreNotAllowed('ignore');
|
|
}
|
|
}
|
|
if (isset($list['install'])) {
|
|
if (!$allowignore) {
|
|
$this->_ignoreNotAllowed('install');
|
|
}
|
|
}
|
|
if (isset($list['file'])) {
|
|
if ($allowignore) {
|
|
$this->_fileNotAllowed('file');
|
|
}
|
|
}
|
|
if (isset($list['dir'])) {
|
|
if ($allowignore) {
|
|
$this->_fileNotAllowed('dir');
|
|
} else {
|
|
if (!isset($list['dir'][0])) {
|
|
$list['dir'] = array($list['dir']);
|
|
}
|
|
foreach ($list['dir'] as $dir) {
|
|
if (isset($dir['attribs']) && isset($dir['attribs']['name'])) {
|
|
if ($dir['attribs']['name'] == '/' ||
|
|
!isset($this->_packageInfo['contents']['dir']['dir'])) {
|
|
// always use nothing if the filelist has already been flattened
|
|
$newdirs = '';
|
|
} elseif ($dirs == '') {
|
|
$newdirs = $dir['attribs']['name'];
|
|
} else {
|
|
$newdirs = $dirs . '/' . $dir['attribs']['name'];
|
|
}
|
|
} else {
|
|
$newdirs = $dirs;
|
|
}
|
|
$this->_validateFilelist($dir, $allowignore, $newdirs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _validateRelease()
|
|
{
|
|
if (isset($this->_packageInfo['phprelease'])) {
|
|
$release = 'phprelease';
|
|
if (isset($this->_packageInfo['providesextension'])) {
|
|
$this->_cannotProvideExtension($release);
|
|
}
|
|
if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
|
|
$this->_cannotHaveSrcpackage($release);
|
|
}
|
|
$releases = $this->_packageInfo['phprelease'];
|
|
if (!is_array($releases)) {
|
|
return true;
|
|
}
|
|
if (!isset($releases[0])) {
|
|
$releases = array($releases);
|
|
}
|
|
foreach ($releases as $rel) {
|
|
$this->_stupidSchemaValidate(array(
|
|
'*installconditions',
|
|
'*filelist',
|
|
), $rel, '<phprelease>');
|
|
}
|
|
}
|
|
foreach (array('', 'zend') as $prefix) {
|
|
$releasetype = $prefix . 'extsrcrelease';
|
|
if (isset($this->_packageInfo[$releasetype])) {
|
|
$release = $releasetype;
|
|
if (!isset($this->_packageInfo['providesextension'])) {
|
|
$this->_mustProvideExtension($release);
|
|
}
|
|
if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
|
|
$this->_cannotHaveSrcpackage($release);
|
|
}
|
|
$releases = $this->_packageInfo[$releasetype];
|
|
if (!is_array($releases)) {
|
|
return true;
|
|
}
|
|
if (!isset($releases[0])) {
|
|
$releases = array($releases);
|
|
}
|
|
foreach ($releases as $rel) {
|
|
$this->_stupidSchemaValidate(array(
|
|
'*installconditions',
|
|
'*configureoption->name->prompt->?default',
|
|
'*binarypackage',
|
|
'*filelist',
|
|
), $rel, '<' . $releasetype . '>');
|
|
if (isset($rel['binarypackage'])) {
|
|
if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) {
|
|
$rel['binarypackage'] = array($rel['binarypackage']);
|
|
}
|
|
foreach ($rel['binarypackage'] as $bin) {
|
|
if (!is_string($bin)) {
|
|
$this->_binaryPackageMustBePackagename();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$releasetype = 'extbinrelease';
|
|
if (isset($this->_packageInfo[$releasetype])) {
|
|
$release = $releasetype;
|
|
if (!isset($this->_packageInfo['providesextension'])) {
|
|
$this->_mustProvideExtension($release);
|
|
}
|
|
if (isset($this->_packageInfo['channel']) &&
|
|
!isset($this->_packageInfo['srcpackage'])) {
|
|
$this->_mustSrcPackage($release);
|
|
}
|
|
if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) {
|
|
$this->_mustSrcuri($release);
|
|
}
|
|
$releases = $this->_packageInfo[$releasetype];
|
|
if (!is_array($releases)) {
|
|
return true;
|
|
}
|
|
if (!isset($releases[0])) {
|
|
$releases = array($releases);
|
|
}
|
|
foreach ($releases as $rel) {
|
|
$this->_stupidSchemaValidate(array(
|
|
'*installconditions',
|
|
'*filelist',
|
|
), $rel, '<' . $releasetype . '>');
|
|
}
|
|
}
|
|
}
|
|
if (isset($this->_packageInfo['bundle'])) {
|
|
$release = 'bundle';
|
|
if (isset($this->_packageInfo['providesextension'])) {
|
|
$this->_cannotProvideExtension($release);
|
|
}
|
|
if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
|
|
$this->_cannotHaveSrcpackage($release);
|
|
}
|
|
$releases = $this->_packageInfo['bundle'];
|
|
if (!is_array($releases) || !isset($releases[0])) {
|
|
$releases = array($releases);
|
|
}
|
|
foreach ($releases as $rel) {
|
|
$this->_stupidSchemaValidate(array(
|
|
'*installconditions',
|
|
'*filelist',
|
|
), $rel, '<bundle>');
|
|
}
|
|
}
|
|
foreach ($releases as $rel) {
|
|
if (is_array($rel) && array_key_exists('installconditions', $rel)) {
|
|
$this->_validateInstallConditions($rel['installconditions'],
|
|
"<$release><installconditions>");
|
|
}
|
|
if (is_array($rel) && array_key_exists('filelist', $rel)) {
|
|
if ($rel['filelist']) {
|
|
|
|
$this->_validateFilelist($rel['filelist'], true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is here to allow role extension through plugins
|
|
* @param string
|
|
*/
|
|
function _validateRole($role)
|
|
{
|
|
return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType()));
|
|
}
|
|
|
|
function _pearVersionTooLow($version)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error',
|
|
array('version' => $version),
|
|
'This package.xml requires PEAR version %version% to parse properly, we are ' .
|
|
'version 1.9.4');
|
|
}
|
|
|
|
function _invalidTagOrder($oktags, $actual, $root)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error',
|
|
array('oktags' => $oktags, 'actual' => $actual, 'root' => $root),
|
|
'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"');
|
|
}
|
|
|
|
function _ignoreNotAllowed($type)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
|
|
'<%type%> is not allowed inside global <contents>, only inside ' .
|
|
'<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only');
|
|
}
|
|
|
|
function _fileNotAllowed($type)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
|
|
'<%type%> is not allowed inside release <filelist>, only inside ' .
|
|
'<contents>, use <ignore> and <install> only');
|
|
}
|
|
|
|
function _oldStyleFileNotAllowed()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'Old-style <file>name</file> is not allowed. Use' .
|
|
'<file name="name" role="role"/>');
|
|
}
|
|
|
|
function _tagMissingAttribute($tag, $attr, $context)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
|
|
'attribute' => $attr, 'context' => $context),
|
|
'tag <%tag%> in context "%context%" has no attribute "%attribute%"');
|
|
}
|
|
|
|
function _tagHasNoAttribs($tag, $context)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
|
|
'context' => $context),
|
|
'tag <%tag%> has no attributes in context "%context%"');
|
|
}
|
|
|
|
function _invalidInternalStructure()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'exception', array(),
|
|
'internal array was not generated by compatible parser, or extreme parser error, cannot continue');
|
|
}
|
|
|
|
function _invalidFileRole($file, $dir, $role)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(
|
|
'file' => $file, 'dir' => $dir, 'role' => $role,
|
|
'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())),
|
|
'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%');
|
|
}
|
|
|
|
function _invalidFileName($file, $dir)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(
|
|
'file' => $file),
|
|
'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."');
|
|
}
|
|
|
|
function _invalidFileInstallAs($file, $as)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(
|
|
'file' => $file, 'as' => $as),
|
|
'File "%file%" <install as="%as%"/> cannot contain "./" or contain ".."');
|
|
}
|
|
|
|
function _invalidDirName($dir)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(
|
|
'dir' => $file),
|
|
'Directory "%dir%" cannot begin with "./" or contain ".."');
|
|
}
|
|
|
|
function _filelistCannotContainFile($filelist)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
|
|
'<%tag%> can only contain <dir>, contains <file>. Use ' .
|
|
'<dir name="/"> as the first dir element');
|
|
}
|
|
|
|
function _filelistMustContainDir($filelist)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
|
|
'<%tag%> must contain <dir>. Use <dir name="/"> as the ' .
|
|
'first dir element');
|
|
}
|
|
|
|
function _tagCannotBeEmpty($tag)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
|
|
'<%tag%> cannot be empty (<%tag%/>)');
|
|
}
|
|
|
|
function _UrlOrChannel($type, $name)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
|
|
'name' => $name),
|
|
'Required dependency <%type%> "%name%" can have either url OR ' .
|
|
'channel attributes, and not both');
|
|
}
|
|
|
|
function _NoChannel($type, $name)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
|
|
'name' => $name),
|
|
'Required dependency <%type%> "%name%" must have either url OR ' .
|
|
'channel attributes');
|
|
}
|
|
|
|
function _UrlOrChannelGroup($type, $name, $group)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
|
|
'name' => $name, 'group' => $group),
|
|
'Group "%group%" dependency <%type%> "%name%" can have either url OR ' .
|
|
'channel attributes, and not both');
|
|
}
|
|
|
|
function _NoChannelGroup($type, $name, $group)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
|
|
'name' => $name, 'group' => $group),
|
|
'Group "%group%" dependency <%type%> "%name%" must have either url OR ' .
|
|
'channel attributes');
|
|
}
|
|
|
|
function _unknownChannel($channel)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel),
|
|
'Unknown channel "%channel%"');
|
|
}
|
|
|
|
function _noPackageVersion()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'package.xml <package> tag has no version attribute, or version is not 2.0');
|
|
}
|
|
|
|
function _NoBundledPackages()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'No <bundledpackage> tag was found in <contents>, required for bundle packages');
|
|
}
|
|
|
|
function _AtLeast2BundledPackages()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'At least 2 packages must be bundled in a bundle package');
|
|
}
|
|
|
|
function _ChannelOrUri($name)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
|
|
'Bundled package "%name%" can have either a uri or a channel, not both');
|
|
}
|
|
|
|
function _noChildTag($child, $tag)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag),
|
|
'Tag <%tag%> is missing child tag <%child%>');
|
|
}
|
|
|
|
function _invalidVersion($type, $value)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value),
|
|
'Version type <%type%> is not a valid version (%value%)');
|
|
}
|
|
|
|
function _invalidState($type, $value)
|
|
{
|
|
$states = array('stable', 'beta', 'alpha', 'devel');
|
|
if ($type != 'api') {
|
|
$states[] = 'snapshot';
|
|
}
|
|
if (strtolower($value) == 'rc') {
|
|
$this->_stack->push(__FUNCTION__, 'error',
|
|
array('version' => $this->_packageInfo['version']['release']),
|
|
'RC is not a state, it is a version postfix, try %version%RC1, stability beta');
|
|
}
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value,
|
|
'types' => $states),
|
|
'Stability type <%type%> is not a valid stability (%value%), must be one of ' .
|
|
'%types%');
|
|
}
|
|
|
|
function _invalidTask($task, $ret, $file)
|
|
{
|
|
switch ($ret[0]) {
|
|
case PEAR_TASK_ERROR_MISSING_ATTRIB :
|
|
$info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file);
|
|
$msg = 'task <%task%> is missing attribute "%attrib%" in file %file%';
|
|
break;
|
|
case PEAR_TASK_ERROR_NOATTRIBS :
|
|
$info = array('task' => $task, 'file' => $file);
|
|
$msg = 'task <%task%> has no attributes in file %file%';
|
|
break;
|
|
case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE :
|
|
$info = array('attrib' => $ret[1], 'values' => $ret[3],
|
|
'was' => $ret[2], 'task' => $task, 'file' => $file);
|
|
$msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '.
|
|
'in file %file%, expecting one of "%values%"';
|
|
break;
|
|
case PEAR_TASK_ERROR_INVALID :
|
|
$info = array('reason' => $ret[1], 'task' => $task, 'file' => $file);
|
|
$msg = 'task <%task%> in file %file% is invalid because of "%reason%"';
|
|
break;
|
|
}
|
|
$this->_stack->push(__FUNCTION__, 'error', $info, $msg);
|
|
}
|
|
|
|
function _unknownTask($task, $file)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file),
|
|
'Unknown task "%task%" passed in file <file name="%file%">');
|
|
}
|
|
|
|
function _subpackageCannotProvideExtension($name)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
|
|
'Subpackage dependency "%name%" cannot use <providesextension>, ' .
|
|
'only package dependencies can use this tag');
|
|
}
|
|
|
|
function _subpackagesCannotConflict($name)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
|
|
'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
|
|
'only package dependencies can use this tag');
|
|
}
|
|
|
|
function _cannotProvideExtension($release)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
|
|
'<%release%> packages cannot use <providesextension>, only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension');
|
|
}
|
|
|
|
function _mustProvideExtension($release)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
|
|
'<%release%> packages must use <providesextension> to indicate which PHP extension is provided');
|
|
}
|
|
|
|
function _cannotHaveSrcpackage($release)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
|
|
'<%release%> packages cannot specify a source code package, only extension binaries may use the <srcpackage> tag');
|
|
}
|
|
|
|
function _mustSrcPackage($release)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
|
|
'<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
|
|
}
|
|
|
|
function _mustSrcuri($release)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
|
|
'<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
|
|
}
|
|
|
|
function _uriDepsCannotHaveVersioning($type)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
|
|
'%type%: dependencies with a <uri> tag cannot have any versioning information');
|
|
}
|
|
|
|
function _conflictingDepsCannotHaveVersioning($type)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
|
|
'%type%: conflicting dependencies cannot have versioning info, use <exclude> to ' .
|
|
'exclude specific versions of a dependency');
|
|
}
|
|
|
|
function _DepchannelCannotBeUri($type)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
|
|
'%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' .
|
|
'dependencies only');
|
|
}
|
|
|
|
function _bundledPackagesMustBeFilename()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'<bundledpackage> tags must contain only the filename of a package release ' .
|
|
'in the bundle');
|
|
}
|
|
|
|
function _binaryPackageMustBePackagename()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'<binarypackage> tags must contain the name of a package that is ' .
|
|
'a compiled version of this extsrc/zendextsrc package');
|
|
}
|
|
|
|
function _fileNotFound($file)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
|
|
'File "%file%" in package.xml does not exist');
|
|
}
|
|
|
|
function _notInContents($file, $tag)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag),
|
|
'<%tag% name="%file%"> is invalid, file is not in <contents>');
|
|
}
|
|
|
|
function _cannotValidateNoPathSet()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'Cannot validate files, no path to package file is set (use setPackageFile())');
|
|
}
|
|
|
|
function _usesroletaskMustHaveChannelOrUri($role, $tag)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
|
|
'<%tag%> for role "%role%" must contain either <uri>, or <channel> and <package>');
|
|
}
|
|
|
|
function _usesroletaskMustHavePackage($role, $tag)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
|
|
'<%tag%> for role "%role%" must contain <package>');
|
|
}
|
|
|
|
function _usesroletaskMustHaveRoleTask($tag, $type)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type),
|
|
'<%tag%> must contain <%type%> defining the %type% to be used');
|
|
}
|
|
|
|
function _cannotConflictWithAllOs($type)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
|
|
'%tag% cannot conflict with all OSes');
|
|
}
|
|
|
|
function _invalidDepGroupName($name)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
|
|
'Invalid dependency group name "%name%"');
|
|
}
|
|
|
|
function _multipleToplevelDirNotAllowed()
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array(),
|
|
'Multiple top-level <dir> tags are not allowed. Enclose them ' .
|
|
'in a <dir name="/">');
|
|
}
|
|
|
|
function _multipleInstallAs($file)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
|
|
'Only one <install> tag is allowed for file "%file%"');
|
|
}
|
|
|
|
function _ignoreAndInstallAs($file)
|
|
{
|
|
$this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
|
|
'Cannot have both <ignore> and <install> tags for file "%file%"');
|
|
}
|
|
|
|
function _analyzeBundledPackages()
|
|
{
|
|
if (!$this->_isValid) {
|
|
return false;
|
|
}
|
|
if (!$this->_pf->getPackageType() == 'bundle') {
|
|
return false;
|
|
}
|
|
if (!isset($this->_pf->_packageFile)) {
|
|
return false;
|
|
}
|
|
$dir_prefix = dirname($this->_pf->_packageFile);
|
|
$common = new PEAR_Common;
|
|
$log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
|
|
array($common, 'log');
|
|
$info = $this->_pf->getContents();
|
|
$info = $info['bundledpackage'];
|
|
if (!is_array($info)) {
|
|
$info = array($info);
|
|
}
|
|
$pkg = &new PEAR_PackageFile($this->_pf->_config);
|
|
foreach ($info as $package) {
|
|
if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) {
|
|
$this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package);
|
|
$this->_isValid = 0;
|
|
continue;
|
|
}
|
|
call_user_func_array($log, array(1, "Analyzing bundled package $package"));
|
|
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
|
|
$ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package,
|
|
PEAR_VALIDATE_NORMAL);
|
|
PEAR::popErrorHandling();
|
|
if (PEAR::isError($ret)) {
|
|
call_user_func_array($log, array(0, "ERROR: package $package is not a valid " .
|
|
'package'));
|
|
$inf = $ret->getUserInfo();
|
|
if (is_array($inf)) {
|
|
foreach ($inf as $err) {
|
|
call_user_func_array($log, array(1, $err['message']));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function _analyzePhpFiles()
|
|
{
|
|
if (!$this->_isValid) {
|
|
return false;
|
|
}
|
|
if (!isset($this->_pf->_packageFile)) {
|
|
$this->_cannotValidateNoPathSet();
|
|
return false;
|
|
}
|
|
$dir_prefix = dirname($this->_pf->_packageFile);
|
|
$common = new PEAR_Common;
|
|
$log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
|
|
array(&$common, 'log');
|
|
$info = $this->_pf->getContents();
|
|
if (!$info || !isset($info['dir']['file'])) {
|
|
$this->_tagCannotBeEmpty('contents><dir');
|
|
return false;
|
|
}
|
|
$info = $info['dir']['file'];
|
|
if (isset($info['attribs'])) {
|
|
$info = array($info);
|
|
}
|
|
$provides = array();
|
|
foreach ($info as $fa) {
|
|
$fa = $fa['attribs'];
|
|
$file = $fa['name'];
|
|
if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
|
|
$this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file);
|
|
$this->_isValid = 0;
|
|
continue;
|
|
}
|
|
if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) {
|
|
call_user_func_array($log, array(1, "Analyzing $file"));
|
|
$srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
|
|
if ($srcinfo) {
|
|
$provides = array_merge($provides, $this->_buildProvidesArray($srcinfo));
|
|
}
|
|
}
|
|
}
|
|
$this->_packageName = $pn = $this->_pf->getPackage();
|
|
$pnl = strlen($pn);
|
|
foreach ($provides as $key => $what) {
|
|
if (isset($what['explicit']) || !$what) {
|
|
// 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;
|
|
}
|
|
$this->_stack->push(__FUNCTION__, 'warning',
|
|
array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
|
|
'in %file%: %type% "%name%" not prefixed with package name "%package%"');
|
|
} elseif ($type == 'function') {
|
|
if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
|
|
continue;
|
|
}
|
|
$this->_stack->push(__FUNCTION__, 'warning',
|
|
array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
|
|
'in %file%: %type% "%name%" not prefixed with package name "%package%"');
|
|
}
|
|
}
|
|
return $this->_isValid;
|
|
}
|
|
|
|
/**
|
|
* Analyze the source code of the given PHP file
|
|
*
|
|
* @param string Filename of the PHP file
|
|
* @param boolean whether to analyze $file as the file contents
|
|
* @return mixed
|
|
*/
|
|
function analyzeSourceCode($file, $string = false)
|
|
{
|
|
if (!function_exists("token_get_all")) {
|
|
$this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
|
|
'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer');
|
|
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 ($string) {
|
|
$contents = $file;
|
|
} else {
|
|
if (!$fp = @fopen($file, "r")) {
|
|
return false;
|
|
}
|
|
fclose($fp);
|
|
$contents = file_get_contents($file);
|
|
}
|
|
|
|
// Silence this function so we can catch PHP Warnings and show our own custom message
|
|
$tokens = @token_get_all($contents);
|
|
if (isset($php_errormsg)) {
|
|
if (isset($this->_stack)) {
|
|
$pn = $this->_pf->getPackage();
|
|
$this->_stack->push(__FUNCTION__, 'warning',
|
|
array('file' => $file, 'package' => $pn),
|
|
'in %file%: Could not process file for unkown reasons,' .
|
|
' possibly a PHP parse error in %file% from %package%');
|
|
}
|
|
}
|
|
/*
|
|
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 != '"' && $token != T_END_HEREDOC) {
|
|
continue;
|
|
} else {
|
|
$inquote = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch ($token) {
|
|
case T_WHITESPACE :
|
|
continue;
|
|
case ';':
|
|
if ($interface) {
|
|
$current_function = '';
|
|
$current_function_level = -1;
|
|
}
|
|
break;
|
|
case '"':
|
|
case T_START_HEREDOC:
|
|
$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)) {
|
|
if (isset($this->_stack)) {
|
|
$this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
|
|
'Parser error: invalid PHP found in file "%file%"');
|
|
} else {
|
|
PEAR::raiseError("Parser error: invalid PHP found in 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', 'throw')
|
|
)
|
|
) {
|
|
if (isset($this->_stack)) {
|
|
$this->_stack->push(__FUNCTION__, 'warning', array(
|
|
'file' => $file),
|
|
'Error, PHP5 token encountered in %file%,' .
|
|
' analysis should be in PHP5');
|
|
} else {
|
|
PEAR::raiseError('Error: PHP5 token encountered in ' . $file .
|
|
'packaging should be done in PHP 5');
|
|
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:
|
|
$token = $tokens[$i - 1][0];
|
|
if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) {
|
|
if (isset($this->_stack)) {
|
|
$this->_stack->push(__FUNCTION__, 'warning', array('file' => $file),
|
|
'Parser error: invalid PHP found in file "%file%"');
|
|
} else {
|
|
PEAR::raiseError("Parser error: invalid PHP found in 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,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 private
|
|
*
|
|
*/
|
|
function _buildProvidesArray($srcinfo)
|
|
{
|
|
if (!$this->_isValid) {
|
|
return array();
|
|
}
|
|
|
|
$providesret = array();
|
|
$file = basename($srcinfo['source_file']);
|
|
$pn = isset($this->_pf) ? $this->_pf->getPackage() : '';
|
|
$pnl = strlen($pn);
|
|
foreach ($srcinfo['declared_classes'] as $class) {
|
|
$key = "class;$class";
|
|
if (isset($providesret[$key])) {
|
|
continue;
|
|
}
|
|
|
|
$providesret[$key] =
|
|
array('file'=> $file, 'type' => 'class', 'name' => $class);
|
|
if (isset($srcinfo['inheritance'][$class])) {
|
|
$providesret[$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($providesret[$key])) {
|
|
continue;
|
|
}
|
|
|
|
$providesret[$key] =
|
|
array('file'=> $file, 'type' => 'function', 'name' => $function);
|
|
}
|
|
}
|
|
|
|
foreach ($srcinfo['declared_functions'] as $function) {
|
|
$key = "function;$function";
|
|
if ($function{0} == '_' || isset($providesret[$key])) {
|
|
continue;
|
|
}
|
|
|
|
if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
|
|
$warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
|
|
}
|
|
|
|
$providesret[$key] =
|
|
array('file'=> $file, 'type' => 'function', 'name' => $function);
|
|
}
|
|
|
|
return $providesret;
|
|
}
|
|
}
|